Published on

EKS ExternalSecret 동기화 실패 - AccessDenied·InvalidIdentity 해결

Authors

서버리스/컨테이너 환경에서 시크릿 동기화는 곧 서비스 가용성입니다. EKS에서 External Secrets Operator(ESO)로 AWS Secrets Manager 또는 SSM Parameter Store를 읽어 Kubernetes Secret으로 동기화할 때, 가장 흔하게 터지는 에러가 AccessDenied, InvalidIdentityToken, InvalidClientTokenId 같은 STS/IAM 계열 오류입니다.

이 글에서는 “ExternalSecret이 계속 Pending/Failed이고, controller 로그에는 AccessDenied/InvalidI…(IdentityToken/ClientToken)” 상황을 전제로, 원인별로 어디를 봐야 하는지재현 가능한 해결 절차를 정리합니다. IRSA(OIDC) 기반이든, 노드 IAM Role 기반이든 모두 포함합니다.

> EKS 전반 트러블슈팅이 필요하다면: Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검


증상 패턴: ExternalSecret은 있는데 Secret이 안 생긴다

대표 증상은 다음과 같습니다.

  • ExternalSecret CR은 생성되었는데 Secret이 생성되지 않음
  • kubectl describe externalsecret ...SecretSyncedError / ProviderError 같은 이벤트
  • external-secrets 컨트롤러 로그에 다음 키워드가 반복
    • AccessDeniedException
    • AccessDenied
    • InvalidIdentityToken
    • InvalidClientTokenId
    • NoCredentialProviders

먼저 상태를 빠르게 확인합니다.

kubectl -n <ns> get externalsecret
kubectl -n <ns> describe externalsecret <name>

# ESO 컨트롤러 로그(설치 네임스페이스는 보통 external-secrets)
kubectl -n external-secrets logs deploy/external-secrets -f --tail=200

또한 SecretStore/ClusterSecretStore가 어떤 인증 방식을 쓰는지 확인하세요.

kubectl -n <ns> get secretstore
kubectl -n <ns> get secretstore <store> -o yaml

kubectl get clustersecretstore
kubectl get clustersecretstore <store> -o yaml

External Secrets Operator에서 AWS 인증 흐름 이해(문제의 80%)

AWS Provider를 사용할 때 ESO는 대체로 아래 중 하나로 자격증명을 얻습니다.

  1. IRSA(권장): ServiceAccount ↔ IAM Role을 OIDC로 AssumeRoleWithWebIdentity
  2. 노드 IAM Role(비권장/레거시): EC2 인스턴스 프로파일로 호출
  3. 정적 키(SecretRef): Access Key/Secret Key를 Kubernetes Secret에 저장

AccessDenied는 “권한 부족”이고, InvalidIdentityToken/InvalidClientTokenId는 “토큰/자격증명 자체가 유효하지 않음”에 가깝습니다. 즉,

  • AccessDenied → IAM Policy/Trust/리소스 정책/조건(Condition) 점검
  • InvalidIdentityToken / InvalidClientTokenId → OIDC/IRSA 구성, 토큰 audience, 클러스터 OIDC Issuer, 시간/리전/엔드포인트 점검

1) AccessDenied: 정책은 붙였는데 왜 거부될까?

체크포인트 A: 실제로 어떤 Role로 호출하고 있나?

가장 먼저 “ESO가 어떤 IAM Role로 AWS API를 때리는지”를 확인해야 합니다. IRSA라면 ESO가 사용하는 ServiceAccount에 role annotation이 있어야 합니다.

# ESO 설치 시 사용하는 SA 이름은 차이가 있을 수 있음
kubectl -n external-secrets get sa
kubectl -n external-secrets get sa external-secrets -o yaml

정상이라면 다음이 보입니다.

metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ESO_ROLE>

만약 annotation이 없으면, ESO는 노드 role로 떨어지거나(환경에 따라), 아예 자격증명 획득에 실패할 수 있습니다.

체크포인트 B: IAM Role 정책에 필요한 액션이 빠졌나?

Secrets Manager를 읽는 최소 권한 예시는 다음과 같습니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:ap-northeast-2:<ACCOUNT_ID>:secret:myapp/*"
    }
  ]
}

SSM Parameter Store라면:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParameters",
        "ssm:GetParametersByPath"
      ],
      "Resource": "arn:aws:ssm:ap-northeast-2:<ACCOUNT_ID>:parameter/myapp/*"
    },
    {
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:ap-northeast-2:<ACCOUNT_ID>:key/<KMS_KEY_ID>"
    }
  ]
}

여기서 흔한 함정:

  • SSM SecureString인데 kms:Decrypt가 없음
  • Secrets Manager ARN 패턴이 정확하지 않음(특히 secret name 뒤에 랜덤 suffix가 붙는 경우)
  • 리전이 달라 Resource ARN이 매칭되지 않음

체크포인트 C: 리소스 정책(Resource Policy)이나 KMS 키 정책이 막는다

권한을 role에 줬는데도 AccessDenied가 나면, Secrets Manager 리소스 정책 또는 KMS Key policy에서 거부하고 있을 수 있습니다.

  • 조직 보안 정책으로 특정 Principal만 허용
  • KMS 키 정책에서 role을 허용하지 않음

이 경우 IAM policy만으로는 해결되지 않습니다.


2) InvalidIdentityToken: OIDC/IRSA 토큰이 신뢰되지 않는다

InvalidIdentityToken은 IRSA에서 특히 자주 발생합니다. 핵심은 “EKS OIDC Issuer와 IAM OIDC Provider가 정확히 연결되어 있고, Trust Policy 조건이 맞는가”입니다.

체크포인트 A: 클러스터 OIDC Issuer 확인

aws eks describe-cluster \
  --name <CLUSTER_NAME> \
  --region <REGION> \
  --query "cluster.identity.oidc.issuer" \
  --output text

예: https://oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXXXXXX

체크포인트 B: IAM에 OIDC Provider가 등록되어 있나?

aws iam list-open-id-connect-providers
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <ARN>

Issuer URL의 id/XXXX가 다른 Provider를 바라보면 토큰 검증이 실패합니다(클러스터 재생성/복제/리전 혼동에서 자주 발생).

체크포인트 C: Role Trust Policy의 sub 조건이 ServiceAccount와 일치하는가?

IRSA Role의 Trust Relationship은 보통 다음 형태입니다.

{
  "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:external-secrets",
          "oidc.eks.<REGION>.amazonaws.com/id/<OIDC_ID>:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

여기서 가장 흔한 실수:

  • 네임스페이스/서비스어카운트 이름이 다름
  • aud 조건이 누락되거나 다른 값으로 설정됨
  • 여러 SA를 허용하려다 StringLike 패턴을 잘못 씀

예: SA가 external-secrets가 아니라 external-secrets-sasub가 달라져서 바로 InvalidIdentityToken이 납니다.

체크포인트 D: 컨트롤러 Pod가 SA 토큰을 제대로 마운트 받는가?

보안 설정으로 automountServiceAccountToken: false가 걸려 있으면 웹 아이덴티티 토큰 파일이 없어서 인증이 실패할 수 있습니다.

kubectl -n external-secrets get deploy external-secrets -o yaml | yq '.spec.template.spec.automountServiceAccountToken'
kubectl -n external-secrets get sa external-secrets -o yaml | yq '.automountServiceAccountToken'

3) InvalidClientTokenId: 자격증명 자체가 틀렸거나, 리전/엔드포인트가 어긋났다

InvalidClientTokenId는 보통 다음에서 발생합니다.

  • 정적 키(AccessKey/SecretKey)가 잘못됨
  • 키가 비활성화/삭제됨
  • 다른 AWS 계정의 키를 사용 중
  • IRSA가 아니라 노드 role로 호출되는데 해당 role이 STS 호출을 못 하거나, 잘못된 환경 변수가 주입됨

정적 키를 쓰는 경우(SecretRef) 점검

SecretStore에서 auth.secretRef를 쓰고 있다면, 해당 Kubernetes Secret 값이 맞는지 확인합니다.

kubectl -n <ns> get secret <aws-creds-secret> -o yaml

그리고 가능하면 정적 키 대신 IRSA로 전환하는 것을 권장합니다.

리전 불일치 점검

SecretStore에 region이 하드코딩되어 있는데 실제 시크릿이 다른 리전에 있으면, 존재하지 않는 리소스를 조회하면서 다른 형태의 오류가 섞여 나올 수 있습니다.


4) 빠른 복구 루틴: “진단 → 수정 → 재동기화” 체크리스트

1) 이벤트/로그에서 에러 문자열을 확정

kubectl -n <ns> describe externalsecret <name>
kubectl -n external-secrets logs deploy/external-secrets --tail=200 | rg -n "AccessDenied|InvalidIdentityToken|InvalidClientTokenId|NoCredential"

2) Store/SA/Role 연결 확인

kubectl -n <ns> get secretstore <store> -o yaml
kubectl -n external-secrets get sa external-secrets -o yaml

3) IAM Trust/Policy 확인

  • Trust: OIDC provider + sub/aud 조건
  • Policy: secretsmanager/ssm/kms 권한

4) 재동기화 트리거

ESO는 주기적으로 reconcile하지만, 즉시 확인하고 싶으면 annotation을 바꿔서 리컨실을 유도할 수 있습니다.

kubectl -n <ns> annotate externalsecret <name> reconcile-at="$(date -u +%Y-%m-%dT%H:%M:%SZ)" --overwrite

예시 구성: IRSA + ClusterSecretStore + ExternalSecret

아래는 가장 많이 쓰는 패턴(운영 권장)에 가까운 예시입니다.

ServiceAccount(IRSA)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-secrets
  namespace: external-secrets
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/eks-eso-role

ClusterSecretStore(AWS Secrets Manager)

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secretsmanager
spec:
  provider:
    aws:
      service: SecretsManager
      region: ap-northeast-2
      auth:
        jwt:
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets

ExternalSecret

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: myapp-secret
  namespace: myapp
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: myapp-secret
    creationPolicy: Owner
  data:
    - secretKey: DATABASE_URL
      remoteRef:
        key: myapp/prod/database_url

이 구성이면, 문제는 대개 IRSA Role trust/policy 또는 리소스/KMS 정책으로 좁혀집니다.


운영에서 자주 겪는 함정 6가지(실패율 높은 순)

  1. ServiceAccount 이름/namespace 변경 후 Trust Policy sub 미수정
  2. 클러스터를 새로 만들었는데 OIDC Provider가 예전 클러스터 ID를 가리킴
  3. SSM SecureString인데 kms:Decrypt 누락
  4. Secrets Manager secret ARN 매칭 실패(랜덤 suffix 고려 안 함)
  5. ESO 컨트롤러가 노드 IAM Role로 호출 중(의도와 다름)
  6. STS가 과도하게 호출되어 간헐적 실패(스로틀링)

STS 스로틀링이 보인다면 이 글도 함께 보세요: EKS IRSA는 되는데 STS 429 Throttling 해결


마무리: 에러 메시지로 분류하면 해결이 빨라진다

  • AccessDenied는 “권한/정책/리소스 정책/KMS 정책” 문제
  • InvalidIdentityToken은 “OIDC/IRSA 신뢰관계(sub/aud/issuer/provider)” 문제
  • InvalidClientTokenId는 “자격증명(키) 자체 또는 호출 주체/리전/환경” 문제

ExternalSecret 동기화 실패는 겉으로는 비슷해 보여도, AWS 인증 경로를 먼저 확정하면 원인이 급격히 좁혀집니다. 특히 EKS에서는 IRSA가 표준이므로, 문제가 생기면 가장 먼저 ServiceAccount annotation ↔ IAM Role trust ↔ OIDC provider 3점을 직선으로 연결해 확인하는 습관이 중요합니다.

EKS에서 다른 유형의 장애(노드/네트워크)로 인해 컨트롤러가 정상 동작하지 않는 경우도 있으니, Pod 상태가 이상하다면 다음 글도 참고해 보세요: EKS Pod Pending 0/XX nodes available 원인별 해결