- Published on
Azure Key Vault 403 해결 - RBAC·방화벽·MI
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Azure Key Vault를 붙이는 순간 가장 흔하게 마주치는 오류가 403 Forbidden입니다. 문제는 403이 하나의 원인이 아니라 권한, 네트워크, 인증 주체(Managed Identity/Service Principal) 중 어느 단계에서든 동일하게 나타난다는 점입니다.
이 글은 “일단 권한 추가해볼까?” 같은 시행착오를 줄이기 위해, 로그에서 단서를 뽑고 원인을 세 갈래(RBAC·방화벽·MI) 로 빠르게 분리하는 방식으로 정리합니다.
참고: 인증/권한 문제는 증상이 비슷해도 원인이 다릅니다. OAuth 플로우에서의 오류 분리 접근은 Auth0 OAuth 400 invalid_grant - PKCE·redirect_uri 해결도 함께 보면 사고방식에 도움이 됩니다.
403을 세 종류로 나누는 기준
Key Vault 호출은 대략 다음 순서로 실패합니다.
- 인증(Who are you?): 토큰이 올바른지, 어떤 주체인지
- 권한(What can you do?): 해당 주체가
get/list/set권한이 있는지 - 네트워크(Where are you from?): 방화벽/Private Endpoint 정책에 의해 차단되는지
403이라도 응답 바디나 헤더, 그리고 진단 로그에 힌트가 있습니다.
- 권한 문제일 때:
Forbidden+Access denied류 메시지,Caller정보가 찍힘 - 네트워크 문제일 때:
ForbiddenByFirewall또는 IP/네트워크 관련 단서 - 인증 주체 문제일 때: 토큰은 있으나 의도한 주체가 아닌 다른 주체(예: 개발자 계정, 다른 MI)로 호출되어 권한이 어긋남
1) 먼저 진단 로그로 “어디서 막혔는지” 확인
가장 빠른 방법은 Key Vault의 Diagnostic settings를 켜서 AuditEvent를 Log Analytics로 보내고, 실패 이벤트를 조회하는 것입니다.
Log Analytics에서 403 이벤트 찾기(KQL)
아래 쿼리는 Key Vault 감사 로그에서 실패(403 포함)만 추려 봅니다.
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where Category == "AuditEvent"
| where toint(httpStatusCode_d) == 403
| project TimeGenerated, OperationName, ResultType, ResultSignature, identity_claim_appid_g, identity_claim_oid_g, CallerIPAddress, requestUri_s
| order by TimeGenerated desc
여기서 핵심은 다음 필드입니다.
ResultType,ResultSignature:Forbidden,ForbiddenByPolicy,ForbiddenByFirewall같은 분류가 나오는 경우가 많음identity_claim_oid_g,identity_claim_appid_g: 실제로 호출한 주체(앱/MI)의 OID, AppIdCallerIPAddress: 네트워크 차단 여부를 가늠하는 단서
로그가 없다면, 원인 규명이 “감”에 의존하게 됩니다. 운영 환경에서는 Key Vault에 진단 로그를 기본값으로 두는 편이 좋습니다.
2) RBAC vs Access Policy: 현재 모드부터 확인
Key Vault 권한 모델은 크게 두 가지입니다.
- Vault access policy 기반(레거시)
- Azure RBAC 기반(권장)
포털에서 Key Vault의 Access configuration을 확인해 현재 어떤 모델인지 먼저 고정해야 합니다. RBAC 모드인데 Access Policy만 추가해도 효과가 없고(반대도 마찬가지), 이 착각이 403의 대표 원인입니다.
Azure CLI로 권한 모델 확인
az keyvault show \
--name my-kv \
--resource-group my-rg \
--query "properties.enableRbacAuthorization"
true면 RBAC 모드false면 Access Policy 모드
3) RBAC 모드에서 403: 역할 할당 스코프와 역할 종류가 핵심
RBAC 모드에서 403이면 보통 아래 중 하나입니다.
- 역할이 잘못된 스코프에 부여됨(구독/리소스그룹/Key Vault 리소스)
- 역할이 데이터 플레인이 아닌 관리 플레인 역할임
- 역할 부여 후 전파 지연(수 분) 동안 실패
Key Vault는 “리소스 관리(ARM)”와 “데이터 접근(Secret/Key/Cert)”이 분리됩니다. Key Vault에서 시크릿을 읽으려면 데이터 플레인 역할이 필요합니다.
자주 쓰는 데이터 플레인 역할
Key Vault Secrets User: 시크릿 읽기(대개 런타임 앱에 권장)Key Vault Secrets Officer: 시크릿 읽기/쓰기/삭제Key Vault Administrator: 광범위(운영자용)
역할 할당(예: Managed Identity에 시크릿 읽기 부여)
아래는 Key Vault 리소스 스코프에 Key Vault Secrets User를 부여하는 예시입니다.
KV_ID=$(az keyvault show -n my-kv -g my-rg --query id -o tsv)
MI_PRINCIPAL_ID=$(az webapp identity show -n my-app -g my-rg --query principalId -o tsv)
az role assignment create \
--assignee-object-id "$MI_PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope "$KV_ID"
스코프 실수 체크
- 스코프가 Key Vault가 아니라 리소스 그룹에 걸려 있어도 동작은 할 수 있지만, 조직 정책에 따라 차단되거나 의도치 않은 권한 확장이 됩니다.
- 반대로 스코프가 너무 좁거나(다른 Key Vault에 줌) 구독이 달라서 실패하는 경우도 흔합니다.
4) Access Policy 모드에서 403: 권한 3종(Secrets/Keys/Certificates) 구분
Access Policy 모드에서는 “시크릿을 읽는데 키 권한만 준” 같은 단순 실수가 많습니다.
Access Policy 추가(시크릿 get/list)
az keyvault set-policy \
--name my-kv \
--object-id "00000000-0000-0000-0000-000000000000" \
--secret-permissions get list
--object-id는 호출 주체의 Object ID(OID)여야 합니다.- 앱 등록(App registration)의 AppId와 혼동하면 403이 지속됩니다.
5) 방화벽/네트워크로 인한 403: “권한을 줘도 계속 403”의 대표 원인
Key Vault는 네트워크 규칙에 의해 차단될 경우에도 403을 반환합니다. 이때 RBAC을 아무리 만져도 해결되지 않습니다.
다음 설정을 확인합니다.
Networking에서Public network access가Disabled인지Firewalls and virtual networks에서Selected networks인지- Private Endpoint를 쓰는지, 그리고 DNS가 올바른지
- 허용 IP에 현재 호출원(빌드 에이전트/AKS 노드/개발 PC)이 포함되는지
CLI로 네트워크 설정 확인
az keyvault show -n my-kv -g my-rg \
--query "properties.networkAcls"
임시로 원인 분리(주의: 운영에서 남기지 말 것)
문제 분리를 위해 아주 짧게 다음을 테스트할 수 있습니다.
- Public access를 잠깐
Enabled로 - 혹은
Allow trusted Microsoft services to bypass this firewall옵션을 켜서 Azure 서비스 경유 트래픽이 통과하는지 확인
이 테스트로 403이 사라지면, 원인은 거의 확실히 네트워크입니다.
Private Endpoint를 쓸 때 흔한 함정: DNS
Private Endpoint를 붙였는데도 403/타임아웃이 난다면, 실제로는 퍼블릭 엔드포인트로 나가다 방화벽에 막히는 경우가 많습니다.
privatelink.vaultcore.azure.net프라이빗 DNS 존 연결- VNet 링크
- 클라이언트(예: AKS, VM)에서 Key Vault FQDN이 사설 IP로 해석되는지 확인
nslookup my-kv.vault.azure.net
결과가 사설 IP가 아니라면 DNS 경로가 잘못된 것입니다.
6) Managed Identity(MI)에서 403: “내가 생각한 그 주체”가 맞는지 확인
MI 사용 시 403이 나는 케이스는 크게 두 가지입니다.
- MI가 아예 활성화되지 않음 또는 토큰이 다른 자격증명으로 발급됨
- System-assigned vs User-assigned 혼동으로 다른 Principal에 권한을 줌
(App Service/Functions) MI 확인
az webapp identity show -n my-app -g my-rg
principalId가 실제로 RBAC에 넣어야 하는 값입니다.
(AKS) Workload Identity 사용 시 주체 불일치
AKS에서 Workload Identity를 쓰면, Kubernetes ServiceAccount와 Entra ID 애플리케이션/MI 페더레이션이 맞물립니다.
- ServiceAccount annotation이 올바른지
- Federated credential의
subject가system:serviceaccount:namespace:sa-name형태로 정확한지 - 실제로 파드에서 발급받은 토큰의
oid가 기대값인지
파드 내부에서 Azure SDK가 어떤 자격증명을 선택했는지 로그로 확인하는 것도 중요합니다. (환경변수에 남아 있는 AZURE_CLIENT_ID가 다른 UAMI를 가리키는 경우가 꽤 흔합니다.)
7) 자주 나오는 “겉보기 동일 403” 시나리오 6가지
시나리오 A: RBAC 모드인데 Access Policy만 추가함
- 증상: 계속 403
- 해결:
enableRbacAuthorization확인 후 RBAC 역할 할당
시나리오 B: 역할을 줬는데도 즉시 403
- 증상: 수 분 내 재시도 시 성공
- 해결: 전파 지연 고려, 배포 파이프라인에 재시도(backoff) 추가
시나리오 C: Key Vault Contributor를 줬는데 시크릿 읽기 403
- 원인: 관리 플레인 역할이라 데이터 플레인 권한이 없음
- 해결:
Key Vault Secrets User등 데이터 플레인 역할 부여
시나리오 D: Private Endpoint인데 DNS가 퍼블릭으로 해석
- 원인: 프라이빗 DNS 존 미연결
- 해결:
privatelinkDNS 존/VNet 링크 구성
시나리오 E: User-assigned MI를 쓰는데 System-assigned에 권한을 줌
- 원인: 잘못된
principalId에 RBAC 부여 - 해결: 실제 런타임에서 쓰는 MI의
principalId로 재할당
시나리오 F: 빌드 에이전트/개발 PC IP가 방화벽 허용 목록에 없음
- 원인: 네트워크 ACL 차단
- 해결: 고정 NAT, 허용 IP 추가, 혹은 Private Endpoint 경유로 전환
8) 재발 방지 체크리스트(운영 관점)
- Key Vault 생성 시점에
Diagnostic settings를 기본 적용(AuditEvent 수집) - 권한 모델을 팀 표준으로 고정(RBAC 권장)하고 템플릿화(Bicep/Terraform)
- 앱 런타임은 최소 권한 원칙으로
Key Vault Secrets User부터 시작 - Private Endpoint 사용 시 DNS 구성을 IaC로 강제
- 배포 파이프라인에 “권한 전파 지연” 대비 재시도 로직 포함
재시도 설계는 외부 API에서도 유사하게 중요합니다. 네트워크/권한/일시 장애가 섞일 때의 접근은 OpenAI 429·5xx 재시도, Idempotency 키로 중복 결제 막기에서 다룬 패턴도 참고할 만합니다.
9) 빠른 결론: 403을 10분 안에 끝내는 순서
- Key Vault 진단 로그에서
ResultType와CallerIPAddress,oid/appid확인 enableRbacAuthorization로 권한 모델 확정- RBAC이면 데이터 플레인 역할을 Key Vault 스코프에 할당
- 네트워크가
Selected networks또는 Private Endpoint면 DNS/허용 경로부터 점검 - MI라면 “실제 호출 주체의
principalId”에 권한을 줬는지 재검증
이 순서대로 보면, 대부분의 Azure Key Vault 403은 “권한을 더 줘서”가 아니라 원인 축을 정확히 잡아서 빠르게 해결됩니다.
부록: Node.js에서 Managed Identity로 시크릿 읽기 예시
아래 예시는 Azure SDK가 기본 자격증명 체인으로 MI를 사용해 Key Vault 시크릿을 읽는 코드입니다.
import { DefaultAzureCredential } from "@azure/identity";
import { SecretClient } from "@azure/keyvault-secrets";
const vaultUrl = "https://my-kv.vault.azure.net";
const credential = new DefaultAzureCredential();
const client = new SecretClient(vaultUrl, credential);
const secret = await client.getSecret("MY_SECRET");
console.log(secret.value);
- 위 코드에서 403이 나면, 거의 항상 이 글의 세 축(RBAC, 방화벽, MI) 중 하나입니다.
- 로그에서 호출 주체 OID를 확인하고, 그 주체에 역할이 있는지부터 보세요.