- Published on
EKS ExternalSecret 미동작 - IRSA·KMS·권한 10분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 마이크로서비스든, EKS에서 External Secrets Operator(ESO)를 붙여 AWS Secrets Manager/SSM Parameter Store 값을 Kubernetes Secret으로 동기화하는 패턴은 거의 표준이 됐습니다. 그런데 운영에서 자주 겪는 문제가 하나 있습니다. ExternalSecret 리소스는 적용됐는데 Secret이 안 생기거나, 계속 Error/NotReady로 남는 현상입니다.
대부분의 경우 원인은 세 갈래로 수렴합니다.
- IRSA(OIDC/TrustPolicy/ServiceAccount) 불일치
- KMS 복호화 권한(kms:Decrypt) 또는 키 정책 문제
- Secrets Manager/SSM IAM 권한/리소스 ARN 스코프 오류
이 글은 “10분 안에” 원인을 좁히는 순서로 구성했습니다. (깊게 파기 전에, 먼저 어디서 깨지는지 빠르게 분기)
관련해서 IRSA에서 AccessDenied가 날 때의 더 상세한 체크리스트는 다음 글도 함께 보면 좋습니다: EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검
0) 먼저 용어 정리: 어디가 실패할 수 있나
ESO를 기준으로 보면 동기화는 대략 이렇게 흐릅니다.
ExternalSecret(또는PushSecret)이SecretStore/ClusterSecretStore를 참조- ESO 컨트롤러 Pod가 ServiceAccount로 실행
- Pod는 IRSA로 AWS STS AssumeRoleWithWebIdentity
- 얻은 Role로 Secrets Manager/SSM 읽기
- (해당 시) KMS로 복호화
- Kubernetes API로 Secret 생성/업데이트
따라서 진단도 (A) 쿠버네티스 리소스 참조 문제 → (B) IRSA → (C) AWS 권한(Secrets/SSM) → (D) KMS 순으로 가면 빠릅니다.
1) 1분 컷: ExternalSecret/Store 상태와 이벤트로 1차 분기
가장 먼저 “어디서” 실패하는지 확인합니다.
# ExternalSecret 상태/이벤트
kubectl -n <ns> describe externalsecret <name>
# SecretStore/ClusterSecretStore 상태
kubectl -n <ns> get secretstore
kubectl -n <ns> describe secretstore <store-name>
# 또는
kubectl describe clustersecretstore <store-name>
여기서 흔히 보이는 힌트:
could not get secret data from provider: AWS 호출(권한/네트워크/리전)AccessDenied/Not authorized: IRSA 또는 IAM 정책InvalidIdentityToken: OIDC/TrustPolicy/SA 매칭kms:Decrypt관련 메시지: KMS 권한/키 정책SecretSyncedError인데 K8s 이벤트에forbidden이 뜨면: Kubernetes RBAC 문제(ESO가 Secret 생성 권한이 없음)
> 참고: RBAC 문제는 IRSA/KMS/IAM과 별개로 “마지막 단계”에서 터집니다. 이벤트에 secrets is forbidden 류가 보이면 AWS가 아니라 K8s 권한부터 고치세요.
2) 3분 컷: ESO 컨트롤러 로그에서 에러 문자열로 원인 고정
컨트롤러 로그는 거의 정답지를 줍니다.
# 설치 방식에 따라 deployment 이름이 다를 수 있음
kubectl -n external-secrets get pods
kubectl -n external-secrets logs deploy/external-secrets -f --tail=200
# 특정 ExternalSecret 관련 키워드로 필터
kubectl -n external-secrets logs deploy/external-secrets --tail=500 | \
egrep -i "accessdenied|invalididentitytoken|assumerole|kms|decrypt|secretsmanager|ssm|throttl|timeout"
로그에서 자주 나오는 패턴과 의미:
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity- IRSA TrustPolicy/OIDC/SA annotation 문제
InvalidIdentityToken: No OpenIDConnect provider found in your account- 클러스터 OIDC provider 미생성 또는 잘못된 issuer
AccessDeniedException: User is not authorized to perform: secretsmanager:GetSecretValue- IAM 정책(리소스 ARN/액션) 문제
AccessDeniedException: ... kms:Decrypt- KMS 권한 또는 키 정책 문제
3) 5분 컷: IRSA(서비스어카운트 ↔ IAM Role)부터 검증
ExternalSecret이 안 붙는 케이스의 절반 이상은 IRSA입니다. 아래 3가지만 빠르게 확인해도 대부분 잡힙니다.
3-1) ESO가 사용하는 ServiceAccount 확인
# ESO 컨트롤러가 어떤 SA로 뜨는지
kubectl -n external-secrets get deploy external-secrets -o jsonpath='{.spec.template.spec.serviceAccountName}{"\n"}'
# SA에 role-arn annotation이 있는지
kubectl -n external-secrets get sa <sa-name> -o yaml | sed -n '/annotations:/,/^$/p'
정상이라면 보통 아래가 있어야 합니다.
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ESO_ROLE>
3-2) Pod에 WebIdentity 토큰/환경변수가 주입됐는지
POD=$(kubectl -n external-secrets get pod -l app.kubernetes.io/name=external-secrets -o name | head -n1)
kubectl -n external-secrets exec -it ${POD} -- sh -lc '
env | egrep "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION";
ls -l $AWS_WEB_IDENTITY_TOKEN_FILE
'
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE가 없으면: SA annotation 미적용, 또는 Pod가 해당 SA를 안 씀.
3-3) IAM Role TrustPolicy의 sub/aud 조건이 SA와 일치하는지
TrustPolicy에서 가장 자주 틀리는 건 sub(namespace/name)입니다.
aws iam get-role --role-name <ESO_ROLE> --query 'Role.AssumeRolePolicyDocument' --output json
예시(핵심만):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>:sub": "system:serviceaccount:external-secrets:<sa-name>",
"oidc.eks.<region>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
}
}
}
]
}
namespace가 다르거나 SA 이름이 다르면 100% 실패합니다.aud가 누락/불일치여도 실패할 수 있습니다.
IRSA에서 더 자주 터지는 케이스(클러스터 OIDC provider 미생성, issuer mismatch, 조건 키 오타 등)는 위 내부 링크 글을 참고하세요: EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검
4) 7분 컷: Secrets Manager/SSM 권한(리소스 ARN 스코프) 확인
IRSA가 통과하면 다음은 “무엇을 읽을 권한이 있나”입니다.
4-1) 필요한 최소 액션
- Secrets Manager 사용 시(일반적인 경우)
secretsmanager:GetSecretValuesecretsmanager:DescribeSecret- (옵션)
secretsmanager:ListSecrets는 보통 불필요(운영 최소권한에선 제외)
- SSM Parameter Store 사용 시
ssm:GetParameter,ssm:GetParameters,ssm:GetParametersByPath
4-2) 가장 흔한 실수: Secret ARN 형식/와일드카드
Secrets Manager의 ARN은 종종 뒤에 랜덤 suffix가 붙습니다.
- 예:
arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/db-password-AbCdEf
정책에서 리소스를 ...:secret:prod/db-password로만 잡으면 매칭이 안 됩니다. 보통은 suffix를 고려해 *를 붙입니다.
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": [
"arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/*"
]
}
4-3) 리전 불일치도 의외로 자주 발생
- EKS는
ap-northeast-2인데 Secret은us-east-1에 있음 - ESO
SecretStore에 region을 안 적어서 기본값이 다르게 잡힘
SecretStore 예시(Secrets Manager):
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
namespace: app
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
5) 9분 컷: KMS 복호화(kms:Decrypt)와 Key Policy 함정
Secrets Manager/SSM이 **KMS CMK(고객 관리형 키)**로 암호화된 경우, IAM에 Secrets 권한만 있어서는 부족합니다. 최종적으로 KMS에서 복호화가 막힙니다.
5-1) IAM 정책에 kms:Decrypt 추가
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/<KEY_ID>"
}
- SSM SecureString이면
kms:Decrypt가 거의 필수입니다. - Secrets Manager도 CMK를 쓰면 필요합니다.
5-2) Key Policy가 더 강하게 막는 케이스
KMS는 IAM 정책 + Key policy 둘 다 통과해야 합니다.
- IAM에
kms:Decrypt가 있어도 Key policy에서 Role을 허용하지 않으면 실패 - 특히 보안팀이 키 정책을 “특정 principal만” 허용하도록 잠가둔 환경에서 자주 발생
점검 방법:
aws kms get-key-policy --key-id <KEY_ID> --policy-name default --output text
Key policy에 최소한 해당 Role(또는 계정 루트에 대한 적절한 위임)이 들어가야 합니다.
5-3) 조건(EncryptionContext) 때문에 막히는 케이스
조직에서 KMS에 kms:EncryptionContext:* 조건을 걸어두면, 서비스가 넣는 컨텍스트와 맞지 않아 AccessDenied가 납니다. 이 경우는 로그에 컨텍스트 관련 단서가 나오며, 키 정책/조건 설계를 다시 봐야 합니다.
6) 마지막 1분: Kubernetes RBAC(Secret 생성 권한) 확인
AWS 쪽이 다 맞아도, ESO가 K8s Secret을 만들 권한이 없으면 최종 결과는 “ExternalSecret은 있는데 Secret이 없음”입니다.
# external-secrets 네임스페이스에서 컨트롤러 SA가 secrets를 만들 수 있는지
kubectl auth can-i create secrets \
--as system:serviceaccount:external-secrets:<sa-name> \
-n <target-namespace>
kubectl auth can-i update secrets \
--as system:serviceaccount:external-secrets:<sa-name> \
-n <target-namespace>
no가 나오면 설치된 ClusterRole/RoleBinding 범위가 부족한 것입니다.
7) 현장에서 바로 쓰는 “10분 진단” 체크리스트
아래 순서대로 보면, 보통 10분 안에 원인 범위를 고정할 수 있습니다.
kubectl describe externalsecret에서 이벤트 확인 (에러 문자열 확보)- ESO 컨트롤러 로그에서
AccessDenied / InvalidIdentityToken / kms:Decrypt키워드 확인 - ESO Pod의 SA 확인 → SA annotation(
eks.amazonaws.com/role-arn) 확인 - Pod 내부에
AWS_WEB_IDENTITY_TOKEN_FILE,AWS_ROLE_ARN주입 확인 - IAM Role TrustPolicy의
sub=system:serviceaccount:<ns>:<sa>일치 확인 - IAM 정책에서
secretsmanager:GetSecretValue또는ssm:GetParameter*리소스 ARN 스코프 확인(특히 Secrets ARN suffix) - CMK 사용 시
kms:Decrypt+ Key policy에서 Role 허용 확인 - 마지막으로
kubectl auth can-i create secrets로 K8s RBAC 확인
8) (부록) 재현 가능한 예시: ExternalSecret + IRSA 최소 구성
8-1) ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-secrets-sa
namespace: app
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eso-app-role
8-2) SecretStore
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets
namespace: app
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
8-3) ExternalSecret
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: SecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod/db-password
8-4) IAM 정책(예시)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/*"
},
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey"
],
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/<KEY_ID>"
}
]
}
마무리
ExternalSecret이 “안 붙는다”는 증상은 같아도, 실제 원인은 IRSA(신원) → IAM(권한) → KMS(복호화) → K8s RBAC(생성) 중 한 군데에서 끊긴 결과입니다. 핵심은 감으로 고치지 말고, describe 이벤트와 컨트롤러 로그에서 에러 문자열을 먼저 확보한 뒤 위 순서대로 분기하는 것입니다.
특히 sts:AssumeRoleWithWebIdentity류의 에러가 보이면 IRSA 쪽이 거의 확정이며, 이 경우 더 자세한 점검 포인트는 다음 글에서 확장해서 확인할 수 있습니다: EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검