GitHub에 Azure 키를 제공하고 있습니다. 중지하는 방법
Source: Dev.to
예전 방식은 매 배포마다 비밀번호를 함께 전송합니다. Workload Identity Federation을 사용하면 이것이 마지막 문제가 됩니다.
오랫동안 나는 CI/CD 보안을 제대로 하고 있다고 생각했습니다.
- 앱 등록? 생성 완료.
- 클라이언트 시크릿? 생성 완료.
- GitHub 시크릿? 저장 완료.
- 파이프라인? 정상 작동.
깔끔하고, 정돈됐으며, 모든 체크박스를 채운 상태였습니다.
내가 눈치채지 못한 점: 나는 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인 경우) 또는 신중하게 범위가 지정된 클라이언트 비밀이 대안이 되며, 목표가 아닙니다.
Source: https://techworldofflorian.substack.com/p/youve-been-giving-github-a-key-to
짧게 요약
기존 방식
- Azure에서 클라이언트 비밀을 생성한다.
- GitHub에 저장한다.
- 수동으로 비밀을 교체한다.
- 유출되지 않기를 바란다.
- 매 12개월마다 반복한다.
새로운 방식
- Azure가 GitHub의 신원을 신뢰하도록 구성한다.
- GitHub이 실행마다 자신을 증명한다.
- 비밀이 생성·저장·전송되는 일은 전혀 없다.
워크플로우에서 바뀌는 점
- 권한에
id-token: write를 추가한다. AZURE_CLIENT_SECRET를 제거한다.
그게 전부다.
없애는 것들
- 비밀 교체.
- 만료에 대한 깜짝 놀라움.
- 유출 위험.
- 설계상 사라지는 전체 인증 위험 카테고리.
GitHub에 Azure 키를 주는 것이 아니라 Azure가 GitHub의 얼굴을 인식하도록 가르치는 것이다. 이 개념이 잡히면, 클라이언트 비밀로 돌아가는 것은 현관문 열쇠를 매트 밑에 두는 것과 같다.
설정은 5분 이하가 걸린다. 위험 감소는 영구적이다. 현대 배포 파이프라인에서 가장 높은 효과를 주는 보안 변화 중 하나이며, 대부분의 팀이 아직 구현하지 않았다.
Originally published on Techworld of Florian
