GitHub에 Azure 키를 제공하고 있습니다. 중단하는 방법
Source: Dev.to
The old way ships a password with every deploy. Workload Identity Federation makes that the last problem you’ll ever have.
오랫동안 나는 CI/CD 보안을 제대로 하고 있다고 생각했다.
- App registration? Created.
- Client secret? Generated.
- GitHub secret? Stored.
- Pipeline? Green.
깨끗하고, 깔끔하며, 모든 체크박스를 채운 상태.
내가 눈치채지 못했던 점: 나는 GitHub에 Azure 환경에 대한 마스터 키를 비밀 저장소에 보관하고 있었고, 이 키는 12개월마다 만료되며, 잘못 구성된 권한 화면 하나만으로도 큰 문제가 발생할 수 있다는 사실이다.
대부분의 Azure 배포는 정확히 이와 같은 방식으로 이루어진다. 대부분의 팀은 이 점에 대해 한 번도 고민해 본 적이 없으며, 솔직히 나도 그랬다. 그런데 나는 어쩐지 바보 같은 질문을 던졌다:
왜 우리는 기계에게 비밀번호를 줘야 할까?
그 질문은 내가 예상한 것보다 훨씬 흥미로운 방향으로 이어졌다. 이 글은 내가 발견한 내용과 그 답이 서비스‑대‑서비스 인증에 대한 생각을 완전히 바꾸게 될 이유에 대해 다룬다.
모두가 편하게 여기지만 (그렇지 않아야 하는) 것
오늘날 거의 모든 GitHub‑to‑Azure 배포가 작동하는 방식은 다음과 같습니다.
- Entra ID 앱 등록을 생성합니다.
- 클라이언트 비밀을 생성합니다.
- 이를 GitHub Actions 비밀에 복사합니다. 보통
AZURE_CLIENT_SECRET와 같은 이름을 사용합니다. - 파이프라인이 이를 사용해 로그인하고, 접근 권한을 얻어 배포합니다.
이렇게 하면 괜찮아 보입니다. 많은 튜토리얼에서 권장하고 있습니다. 실제로 동작합니다.
하지만 실제로 여러분이 만든 것은 다음과 같습니다:
| 당신이 생각하는 것 | 실제로 가지고 있는 것 |
|---|---|
| 보안된 배포 파이프라인 | 장기 비밀번호 |
| 자격 증명이 안전하게 저장됨 | 외부 시스템에 보관됨 |
| Azure에 대한 감사 가능한 접근 | 수동으로 회전해야 함 |
| — | 그리고 조용히 유출될 수 있음 |
장기 비밀은 위험 범주이며, 단순한 설정 세부 사항이 아닙니다. 로그에 노출될 수 있고, 팀 구성원 변경 이후에도 남아 있으며, 조용히 만료되어 가장 안 좋은 순간에 파이프라인을 중단시킵니다.
보다 근본적으로는: 파이프라인에 비밀을 제공할 필요가 없어야 합니다.
그 통찰은 사소해 보일 수 있습니다. 하지만 충분히 깊이 파고들면 전체 인증 모델을 재작성하게 됩니다.
모든 것을 바꾸는 변화: 비밀 대신 신뢰
사람을 신뢰하는 방식과 기계를 신뢰하는 방식을 생각해 보세요.
GitHub 엔지니어링 팀의 사람이 사무실에 방문하면 비밀번호를 건네주지 않습니다. 대신 신분증을 보여줍니다. 신분증은 그 사람이 어디서 왔는지, 누구인지, 언제 발급됐는지를 증명합니다. 유효 기간이 있고, 취소될 수도 있습니다. 비밀을 전달하지 않고도 신원을 증명합니다.
Workload Identity Federation은 이와 같은 논리를 파이프라인에 적용합니다.
비밀을 저장하는 대신 Azure가 GitHub의 신원을 인식하고, GitHub이 각 실행마다 새로 생성하는 단기간 유효 증명 토큰을 받아들이도록 합니다.
- 비밀이 저장되지 않습니다.
- 비밀이 시스템 간에 전송되지 않습니다.
- 비밀이 유출될 수 없습니다.
전체 비밀 설정을 대체하는 흐름
GitHub Action 실행.
워크플로가 정상적으로 트리거됩니다 (main푸시, PR 병합, 수동 디스패치 등).GitHub이 OIDC 토큰 발행.
짧은 수명과 암호화 서명이 포함된 신원 증명: 이 워크플로, 이 저장소, 이 브랜치, 이 커밋.Azure가 토큰 검증.
Entra ID가 확인합니다: 이 저장소가 신뢰 목록에 있나요? 브랜치가 일치하나요? 토큰이 최신인가요?Azure가 액세스 토큰 발행.
검증에 성공하면 Azure가 해당 실행을 위한 범위가 제한된 시간 제한 액세스 토큰을 워크플로에 제공합니다.배포가 진행됨.
워크플로가 토큰을 사용해 배포하고 종료됩니다. 토큰은 만료되고, 정리할 것이 없습니다.
비밀이 Azure 테넌트를 떠나는 일은 없습니다. GitHub은 절대 자격 증명을 보관하지 않으며, 여러분은 아무것도 회전시킬 필요가 없습니다.
설정은 약 5분 정도 걸리며, 실제 워크플로 변경은 세 줄입니다.
실제 환경 설정
여기에 최소 구현 예시가 있습니다. 두 부분으로 구성됩니다.
Part 1 – Azure에서 연합 자격 증명 만들기
Entra ID 애플리케이션을 일반적으로 등록합니다. 클라이언트 비밀을 생성하는 대신 연합 자격 증명을 추가합니다.
# 1️⃣ Create the app registration
az ad app create --display-name "github-actions-pipeline"
# 2️⃣ Capture the app ID
APP_ID=$(az ad app list --display-name "github-actions-pipeline" \
--query "[0].appId" -o tsv)
# 3️⃣ Create a service principal for the app
az ad sp create --id "$APP_ID"
# 4️⃣ Add the federated credential
az ad app federated-credential create \
--id "$APP_ID" \
--parameters '{
"name": "github-main",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:/:ref:refs/heads/main",
"audiences": ["api://AzureADTokenExchange"]
}'이렇게 하면 다음과 같은 신뢰 정책이 생성됩니다: “이 특정 GitHub 저장소와 브랜치에서 온 토큰만 허용합니다. 그 외는 허용하지 않습니다.” 비밀도, 인증서도 없이—오직 신원 신뢰만으로 동작합니다.
Part 2 – GitHub Actions 워크플로 업데이트
name: Deploy to Azure
on:
push:
branches: [ main ]
permissions:
id-token: write # required – enables OIDC token generation
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login to Azure (no secret)
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy
run: az group list빠진 부분을 확인하세요
- ❌
AZURE_CLIENT_SECRET없음 - ❌ 인증서 참조 없음
- ❌ 장기 인증 정보 전혀 없음
이제 파이프라인은 단기간 유효한 OIDC 기반 토큰을 사용해 인증하므로, 비밀번호 유출 위험을 없애면서도 동일한 배포 기능을 유지합니다.
전달하는 세 값 — 클라이언트 ID, 테넌트 ID, 구독 ID — 은 비밀이 아닙니다
이들은 식별자에 불과합니다. 유효한 OIDC 토큰 없이 이 값들을 알더라도 공격자는 전혀 활용할 수 없습니다.
하나의 권한 플래그(id-token: write)만이 전체 흐름을 열어주는 유일한 요소입니다.
대부분의 팀이 생각하지 못하는 점
이 모델을 본 후에 스스로에게 물어볼 가치가 있는 질문은 다음과 같습니다: 우리가 왜 비밀을 사용했을까?
솔직한 답은 OIDC 연합이 아직 이 수준에서는 비교적 새롭다는 것입니다. 대부분의 도구가 클라이언트 비밀을 기본값으로 사용했는데, 이는 생태계가 이를 지원했기 때문입니다. 튜토리얼에서도 가르쳤고, 템플릿에도 포함되었습니다. 그래서 기본값이 되었습니다.
하지만 “기계에 비밀번호를 부여한다”는 패턴은 클라우드 인프라에서 가장 오래되고 지속적인 자격 증명 노출 원인 중 하나입니다. 이는 틈새 문제라기보다 대부분의 공급망 공격이 시작되는 방식입니다.
Workload Identity Federation은 보안을 조금씩 향상시키는 것이 아니라 전체 위험군을 제거합니다.
- 비밀이 없음 → 회전할 것이 없음 → 만료할 것이 없음 → 유출될 것이 없음 → 우발적 노출에 대한 감사도 필요 없음.
이는 작은 업그레이드가 아닙니다. 범주 자체가 바뀐 것입니다.
언제 사용하면 안 되는지
이 접근 방식은 외부 시스템이 OIDC 토큰 발급을 지원해야 합니다.
- GitHub – ✅
- GitLab – ✅
- 대부분의 최신 CI 플랫폼 – ✅
레거시 파이프라인, 내부 러너, 또는 OIDC 연합이 일반화되기 전에 구축된 도구는 이를 지원하지 않을 수 있습니다. 이러한 경우, 관리형 ID(러너가 Azure‑hosted인 경우) 또는 신중하게 범위가 지정된 클라이언트 비밀이 대체 수단이 되며, 목표가 아닙니다.
짧은 버전
기존 방식
- Azure에서 클라이언트 비밀을 생성합니다.
- 이를 GitHub에 저장합니다.
- 수동으로 회전시킵니다.
- 절대 유출되지 않기를 바랍니다.
- 매 12개월마다 반복합니다.
새로운 방식
- Azure가 GitHub의 ID를 신뢰하도록 구성합니다.
- GitHub은 실행마다 자신의 신원을 증명합니다.
- 비밀이 생성되거나, 저장되거나, 전송되는 일이 전혀 없습니다.
워크플로우에서 변경되는 사항
- 권한에
id-token: write를 추가합니다. AZURE_CLIENT_SECRET를 제거합니다.
그게 전부입니다.
제거되는 항목
- 비밀 회전.
- 만료 서프라이즈.
- 유출 노출.
- 설계상 사라진 전체 자격 증명 위험 카테고리.
GitHub에 Azure 키를 주는 것이 아니라, Azure가 GitHub의 얼굴을 인식하도록 가르치는 것입니다. 이 개념이 잡히면, 클라이언트 비밀로 돌아가는 것은 마치 현관문 열쇠를 매트 밑에 두는 것과 같습니다.
설정은 5분 미만에 완료됩니다. 위험 감소는 영구적입니다. 이는 현대 배포 파이프라인에서 할 수 있는 가장 높은 레버리지를 가진 보안 변화 중 하나이며, 대부분의 팀이 아직 구현하지 못하고 있습니다.
Originally published on Techworld of Florian
