Published on

EKS Pod에서 AWS Secrets Manager 403 해결 가이드

Authors

서론

EKS에서 애플리케이션을 Pod로 올리고 AWS Secrets Manager에서 시크릿을 읽어오려는 순간, 로그에 403(대부분 AccessDeniedException)이 떨어지는 경우가 많습니다. 문제는 “정말 IAM 권한이 없는 것”도 있지만, IRSA(WebIdentity) 설정, KMS 복호화 권한, VPC Endpoint 정책, 리전/STS 설정, 잘못된 실행 주체(노드 역할로 호출) 같은 다양한 축에서 403이 만들어진다는 점입니다.

이 글은 403을 증상 → 원인 후보 → 확인 명령 → 수정 순서로 쪼개서, 운영 환경에서 빠르게 복구할 수 있도록 정리합니다. (유사한 맥락의 403 트러블슈팅은 EKS에서 Pod는 되는데 SQS만 403 뜰 때도 함께 참고하면 좋습니다.)

1) 403의 “정확한 메시지”부터 확보하기

Secrets Manager 403은 에러 문자열에 힌트가 거의 다 들어있습니다. 애플리케이션 로그에 아래 중 무엇이 찍히는지부터 확인합니다.

  • AccessDeniedException: User: arn:aws:sts::...:assumed-role/... is not authorized to perform: secretsmanager:GetSecretValue
  • AccessDeniedException ... not authorized to perform: kms:Decrypt
  • InvalidSignatureException(리전/서명 문제)
  • The security token included in the request is invalid(IRSA/토큰/STS 문제)

가능하면 Pod 안에서 AWS CLI로 재현해 메시지를 더 명확히 합니다.

kubectl exec -it deploy/myapp -- sh

# 어떤 자격증명(주체)로 호출되는지 확인
aws sts get-caller-identity

# Secrets Manager 호출 재현
aws secretsmanager get-secret-value \
  --secret-id my/secret \
  --region ap-northeast-2

여기서 get-caller-identity 결과 ARN이 의도한 IRSA 역할인지가 1차 관문입니다.

2) 가장 흔한 원인: IRSA가 적용되지 않아 “노드 역할”로 호출됨

2.1 증상

  • sts get-caller-identityassumed-role/<NodeInstanceRole> 또는 Managed Node Group 역할로 보임
  • Pod에 ServiceAccount를 붙였는데도 계속 노드 역할로 호출됨

2.2 확인 포인트

  1. ServiceAccount에 role-arn annotation이 있는지
kubectl get sa myapp -o yaml

예상:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: myapp
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eks-myapp-irsa
  1. Pod가 해당 ServiceAccount를 쓰는지
kubectl get pod -l app=myapp -o jsonpath='{.items[0].spec.serviceAccountName}'; echo
  1. EKS OIDC Provider가 클러스터에 연결되어 있는지
aws eks describe-cluster --name <cluster> --query "cluster.identity.oidc.issuer" --output text

OIDC issuer가 나오는데도 IRSA가 안 붙는다면, IAM OIDC provider가 계정에 생성/연결되지 않았을 수 있습니다.

aws iam list-open-id-connect-providers

2.3 해결

  • ServiceAccount annotation을 올바르게 설정하고 Pod 재배포
  • IAM 역할의 Trust policy(assume role)에서 sub 조건이 정확한지 확인

예: IRSA 역할 Trust Policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXX"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXX:sub": "system:serviceaccount:default:myapp",
          "oidc.eks.ap-northeast-2.amazonaws.com/id/XXXXXXXX:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

namespaceserviceaccount 이름이 하나라도 다르면 IRSA가 적용되지 않습니다.

3) IAM 정책은 붙였는데도 403: 리소스 ARN/버전 스테이지 조건 실수

3.1 흔한 실수

  • SecretIdmy/secret인데 정책은 다른 ARN을 지정
  • secretsmanager:VersionStage 조건을 걸어두고 실제 호출은 다른 stage를 요청
  • 와일드카드가 부족(특히 Secrets Manager ARN suffix -AbCdEf 부분)

Secrets Manager ARN은 보통 다음 형태입니다.

  • arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:my/secret-AbCdEf

정책에서 secret:my/secret*처럼 suffix를 고려해야 합니다.

3.2 권장 최소 정책 예시

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadSecret",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:my/secret*"
    }
  ]
}

여러 시크릿을 prefix로 관리한다면:

{
  "Effect": "Allow",
  "Action": ["secretsmanager:GetSecretValue"],
  "Resource": "arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/*"
}

4) GetSecretValue가 403인데 “범인은 KMS:Decrypt”인 경우

Secrets Manager는 내부적으로 KMS로 암호화됩니다. 시크릿이 Customer Managed KMS Key(CMK) 로 암호화되어 있으면, secretsmanager:GetSecretValue 권한이 있어도 KMS 복호화 권한이 없으면 403이 납니다.

4.1 증상

에러에 다음이 포함됩니다.

  • not authorized to perform: kms:Decrypt on resource: arn:aws:kms:...

4.2 해결

IRSA 역할에 kms:Decrypt를 추가하고, KMS Key policy에도 해당 역할을 허용해야 합니다.

IAM 정책 예시:

{
  "Effect": "Allow",
  "Action": ["kms:Decrypt"],
  "Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

KMS Key policy(개념 예시)에도 역할을 Principal로 추가:

{
  "Sid": "AllowDecryptFromIRSA",
  "Effect": "Allow",
  "Principal": {"AWS": "arn:aws:iam::123456789012:role/eks-myapp-irsa"},
  "Action": ["kms:Decrypt", "kms:DescribeKey"],
  "Resource": "*"
}

주의: KMS는 IAM 정책 + Key policy 둘 다 통과해야 하는 경우가 많습니다(특히 CMK).

5) Private 서브넷에서 VPC Endpoint(Interface) 정책 때문에 403

EKS가 Private subnet에 있고 NAT 없이 com.amazonaws.<region>.secretsmanager VPC Interface Endpoint를 쓰는 구조라면, Endpoint policy가 403을 만들 수 있습니다. 이때는 IAM이 맞아도 403이 납니다.

5.1 확인

  • VPC 콘솔에서 Secrets Manager Interface Endpoint가 있는지
  • Endpoint policy가 * 허용이 아니라 제한되어 있는지

CLI로 정책 확인:

aws ec2 describe-vpc-endpoints --vpc-endpoint-ids vpce-xxxxxxxx \
  --query 'VpcEndpoints[0].PolicyDocument' --output text

5.2 해결

테스트로는 Endpoint policy를 넉넉히 열어 원인 분리부터 합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "secretsmanager:*",
      "Resource": "*"
    }
  ]
}

원인이 확정되면, Principal/Resource/aws:PrincipalArn 조건 등으로 다시 최소화합니다.

네트워크 비용/구조 점검이 필요하다면 NAT 경유 트래픽과 엔드포인트 구성을 함께 보게 되는데, 이때는 VPC NAT Gateway 비용 폭증 10분 진단·절감도 같이 보면 맥락을 잡는 데 도움이 됩니다.

6) 리전/STS 엔드포인트/시간 오차로 인한 서명 문제도 403처럼 보인다

Secrets Manager는 리전 서비스입니다. 다음 실수는 403 또는 SignatureDoesNotMatch류로 나타날 수 있습니다.

  • SDK/CLI가 다른 리전으로 호출 (AWS_REGION/AWS_DEFAULT_REGION 불일치)
  • STS Regional endpoint 사용 강제/비활성 이슈(특히 보안 정책으로 STS를 특정 방식으로 제한한 경우)
  • 노드 시간 오차(NTP 문제)로 서명 만료

Pod 환경변수 확인:

kubectl exec -it deploy/myapp -- env | egrep 'AWS_REGION|AWS_DEFAULT_REGION|AWS_STS_REGIONAL_ENDPOINTS'

권장:

  • AWS_REGION을 클러스터/시크릿 리전과 일치
  • 가능하면 AWS_STS_REGIONAL_ENDPOINTS=regional 사용
  • 노드의 시간 동기화 점검(관리형 노드면 보통 정상이나 커스텀 AMI면 확인)

7) 디버깅을 “관측 가능하게” 만드는 방법 (CloudTrail + SDK 로그)

7.1 CloudTrail에서 실제 Deny 원인 보기

CloudTrail 이벤트의 errorCode, userIdentity.arn, requestParameters.secretId를 보면 “누가 무엇을 왜 거절당했는지”가 명확해집니다.

  • 호출 주체가 IRSA 역할인지, 노드 역할인지
  • 실제 요청한 secret ARN이 무엇인지
  • KMS Decrypt가 같이 터지는지

7.2 boto3 예제: 호출 주체와 예외를 로그로 남기기

import os
import boto3
from botocore.exceptions import ClientError

region = os.getenv("AWS_REGION", "ap-northeast-2")
secret_id = os.getenv("SECRET_ID", "my/secret")

sts = boto3.client("sts", region_name=region)
print("caller:", sts.get_caller_identity())

sm = boto3.client("secretsmanager", region_name=region)

try:
    resp = sm.get_secret_value(SecretId=secret_id)
    print("ok, len:", len(resp.get("SecretString", "")))
except ClientError as e:
    print("error:", e.response.get("Error"))
    raise

이 코드를 임시 디버그 컨테이너나 Job으로 돌리면 애플리케이션 코드에 영향 없이 원인 분리가 됩니다.

8) 재발 방지 체크리스트

  • Pod에서 aws sts get-caller-identity항상 IRSA 역할로 나오는지(배포 파이프라인에 smoke test로 추가)
  • Secrets Manager 정책의 Resource가 실제 ARN suffix까지 포함하는지(secret-name*)
  • CMK 사용 시 kms:Decrypt + KMS Key policy에 역할 허용이 들어갔는지
  • Private 환경이면 VPC Endpoint policy로 403이 나지 않도록 최소권한 설계를 검증했는지
  • 리전 환경변수(AWS_REGION)가 애플리케이션/클러스터/시크릿과 일치하는지

결론

EKS Pod에서 Secrets Manager 403은 “권한 하나 추가”로 끝나는 경우도 있지만, 실제로는 IRSA 미적용(노드 역할로 호출), KMS 복호화 권한 누락, VPC Endpoint 정책 제한, 리전/STS/서명 이슈처럼 서로 다른 계층에서 발생합니다. 가장 빠른 접근은 Pod 안에서 sts get-caller-identity로 호출 주체를 확정하고, CloudTrail로 Deny의 정확한 원인을 확인한 뒤, IAM/KMS/Endpoint 정책을 순서대로 좁혀가는 것입니다.

운영 중 다른 AWS 서비스만 403이 나는 패턴(예: SQS만 403)도 근본 원인이 비슷하게 흘러가니, 필요하면 EKS에서 Pod는 되는데 SQS만 403 뜰 때에서 접근 프레임을 같이 적용해 보세요.