EKS Pod Identity + ABAC로 단일 IAM 역할에서 Secrets Manager 멀티테넌트 격리 구현하기
클러스터에 팀이 세 개 있으면 IAM 역할도 세 개여야 할까요? 팀이 열 개면 열 개? 저도 한동안 그렇게 믿었습니다. team-a-role, team-b-role… 비슷한 정책을 복붙하고 이름만 바꾸면서 "이게 맞나?" 싶었던 기억이 납니다. 결론부터 말하면, 그럴 필요 없습니다. 팀이 열 개로 늘어나도 IAM 역할 추가 없이 시크릿 태그 하나면 새 팀 온보딩이 끝납니다.
이 글은 EKS를 운영 중이며 IAM 기본 개념(AssumeRole, 정책 조건)에 익숙한 DevOps/클라우드 인프라 엔지니어를 대상으로 합니다.
EKS Pod Identity와 ABAC를 결합하면 IAM 역할은 하나만 유지하면서도 팀별, 네임스페이스별 시크릿 격리를 IAM 레이어에서 강제할 수 있습니다. Pod가 스스로 "나는 team-a야"라고 주장하는 게 아니라, EKS가 자격 증명에 네임스페이스 정보를 태그로 박아서 내려줍니다. 이 글에서는 세션 태그가 어떻게 멀티테넌트 보안 경계로 작동하는지, 그리고 ESO(External Secrets Operator)와 Secrets Manager를 연결하는 실전 구성까지 구체적으로 살펴봅니다.
이 패턴은 2023년 말 Pod Identity가 GA되면서 가능해졌습니다. 2024~2025년을 거치며 ESO, ASCP 등 주요 오픈소스 도구들도 공식 지원을 마쳤고, 멀티테넌트 클러스터 운영의 표준 패턴으로 자리잡는 중입니다. 지금이 본격적으로 도입을 고민해볼 시점입니다.
핵심 개념
IRSA에서 Pod Identity로 — 무엇이 달라졌나
IRSA(IAM Roles for Service Accounts)를 써보셨다면 그 번거로움을 아실 겁니다. EKS 클러스터마다 OIDC 프로바이더를 생성하고, IAM 역할의 신뢰 정책에 클러스터 OIDC URL을 직접 적어줘야 했죠. 클러스터가 여러 개라면? 신뢰 정책 편집 실수로 인증이 안 되는 상황도 꽤 자주 맞닥뜨렸을 겁니다.
IRSA를 아직 경험해보지 않으셨다면 이렇게 이해하면 됩니다. 기존 방식은 Pod가 "이 클러스터, 이 서비스 어카운트입니다"라는 걸 IAM 역할 설정에 하드코딩해서 증명했다면, Pod Identity는 EKS 클러스터 자체가 "이 Pod는 이 네임스페이스에서 왔습니다"를 보증해주는 방식입니다.
Pod Identity는 이 구조를 바꿨습니다. 클러스터에 eks-pod-identity-agent DaemonSet이 설치되면, 이 에이전트가 Pod에 임시 자격 증명을 직접 주입합니다. OIDC 프로바이더 설정이 필요 없고, 연결(association) 설정도 AWS 콘솔이나 CLI 한 줄로 끝납니다. Terraform 신뢰 정책에도 더 이상 OIDC URL을 적지 않아도 됩니다 — 이 부분은 뒤의 예시에서 직접 확인할 수 있습니다.
Pod Identity Association: EKS 네임스페이스 + 서비스 어카운트 조합에 IAM 역할을 연결하는 설정.
aws eks create-pod-identity-association명령으로 생성하며, IRSA의 신뢰 정책 수동 편집을 대체합니다.
세션 태그 — 이 패턴의 핵심 열쇠
Pod Identity가 IAM 역할을 Assume할 때, AWS는 다음 6개의 세션 태그를 자동으로 임시 자격 증명에 첨부합니다.
| 세션 태그 키 | 값 예시 |
|---|---|
eks-cluster-arn |
arn:aws:eks:ap-northeast-2:123456789:cluster/prod-cluster |
eks-cluster-name |
prod-cluster |
kubernetes-namespace |
team-a |
kubernetes-service-account |
app-sa |
kubernetes-pod-name |
app-pod-abc123 |
kubernetes-pod-uid |
abc-123-def-456 |
중요한 건 Pod 자신은 이 태그를 위조할 수 없다는 점입니다. EKS 컨트롤 플레인이 설정하고, AWS STS가 검증합니다. 애플리케이션 코드에서 환경변수를 조작해봤자 의미가 없습니다.
이 태그들은 IAM 정책 조건에서 aws:PrincipalTag/<tag-key> 형태로 참조할 수 있습니다. 여기서 ABAC가 작동합니다.
ABAC 정책 — 단 하나의 역할로 테넌트 경계 만들기
아래가 이 패턴의 핵심 IAM 정책입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
"Resource": "*",
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/eks-cluster-name": "${aws:PrincipalTag/eks-cluster-name}",
"secretsmanager:ResourceTag/kubernetes-namespace": "${aws:PrincipalTag/kubernetes-namespace}"
}
}
}
]
}${aws:PrincipalTag/kubernetes-namespace} 부분이 런타임에 세션 태그 값으로 치환됩니다. team-a 네임스페이스의 Pod가 요청을 보내면 이 값이 team-a로 평가되어, kubernetes-namespace=team-a 태그가 달린 시크릿에만 접근이 허용됩니다. team-b의 Pod는 동일한 IAM 역할을 사용하더라도 team-b 태그가 달린 시크릿에만 접근됩니다.
ABAC (Attribute-Based Access Control): 리소스와 주체(Principal)의 속성(태그)을 비교해 접근을 결정하는 방식. "역할이 무엇인가"가 아니라 "태그가 일치하는가"를 기준으로 합니다.
IAM 역할 신뢰 정책에 sts:TagSession이 필요한 이유
신뢰 정책에 sts:AssumeRole만 있으면 충분하다고 생각하기 쉬운데, 세션 태그를 활용하려면 sts:TagSession도 반드시 포함해야 합니다. AWS STS가 임시 자격 증명을 발급할 때 태그를 첨부하는 행위 자체에 이 권한이 필요하기 때문입니다. 이 권한이 없으면 Pod Identity 연결은 성공해도 ABAC 조건 평가에서 태그가 빠져 Access Denied가 발생합니다. Terraform 예시에서 올바른 신뢰 정책 형태를 확인할 수 있습니다.
실전 적용
예시 1: ESO ClusterSecretStore로 네임스페이스별 시크릿 격리
External Secrets Operator를 쓰고 있다면 이 패턴이 가장 자연스럽게 맞아떨어집니다. 단일 ClusterSecretStore를 여러 팀이 공유하면서도, 각 팀은 자신의 시크릿에만 접근하는 구성입니다.
1단계: Secrets Manager에 태그와 함께 시크릿 생성
# team-a 시크릿
aws secretsmanager create-secret \
--name "prod/team-a/db-password" \
--secret-string "super-secret-a" \
--tags Key=eks-cluster-name,Value=prod-cluster \
Key=kubernetes-namespace,Value=team-a
# team-b 시크릿
aws secretsmanager create-secret \
--name "prod/team-b/db-password" \
--secret-string "super-secret-b" \
--tags Key=eks-cluster-name,Value=prod-cluster \
Key=kubernetes-namespace,Value=team-b2단계: Pod Identity Association 설정
aws eks create-pod-identity-association \
--cluster-name prod-cluster \
--namespace external-secrets \
--service-account external-secrets \
--role-arn arn:aws:iam::123456789012:role/eso-abac-role3단계: ClusterSecretStore에 sessionTags 설정
여기서 한 가지 짚고 넘어갈 부분이 있습니다. ESO 컨트롤러는 external-secrets 네임스페이스에서 실행됩니다. 즉, Pod Identity 에이전트가 ESO 컨트롤러에 주입하는 세션 태그의 kubernetes-namespace 값은 external-secrets입니다. 그런데 우리가 격리하고 싶은 건 ExternalSecret 리소스가 있는 team-a 네임스페이스죠.
이 간극을 메우는 것이 ClusterSecretStore의 sessionTags 설정입니다. ESO가 각 ExternalSecret을 처리할 때 해당 리소스의 네임스페이스를 명시적으로 세션 태그에 주입하도록 지시하는 역할을 합니다.
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: aws-secrets-store
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-2
auth:
pod:
mountServiceAccountToken: true
# ESO가 ExternalSecret의 네임스페이스를 세션 태그로 명시적으로 주입
# {{ .namespace }}는 Go 템플릿으로, ExternalSecret이 배포된 네임스페이스로 평가됨
sessionTags:
- key: kubernetes-namespace
value: "{{ .namespace }}"
transitiveTagKeys:
- kubernetes-namespacevalue: "{{ .namespace }}" 는 Go 템플릿 문법으로, ESO가 각 ExternalSecret 요청을 처리할 때 해당 리소스가 속한 네임스페이스(team-a, team-b 등)로 치환됩니다. 이렇게 해서 AWS에서 평가되는 세션 태그의 kubernetes-namespace 값이 ExternalSecret이 있는 팀의 네임스페이스와 정확히 일치하게 됩니다.
| 구성 요소 | 역할 |
|---|---|
sessionTags |
ESO가 Secrets Manager API를 호출할 때 ExternalSecret 네임스페이스를 명시적으로 주입 |
transitiveTagKeys |
태그를 하위 세션으로도 전파 (중첩 Assume 시 필요) |
ClusterSecretStore |
단일 스토어를 모든 팀이 공유 — IAM 역할이 하나인 이유 |
4단계: 각 팀 네임스페이스에서 ExternalSecret 생성
# team-a 네임스페이스에 배포
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: team-a
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-store
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/team-a/db-passwordteam-a의 ExternalSecret이 prod/team-b/db-password를 참조하려 하면 어떻게 될까요? ESO의 세션 태그에는 kubernetes-namespace=team-a가 주입되고, 대상 시크릿의 태그는 kubernetes-namespace=team-b이므로 ABAC 조건이 불일치합니다. AWS는 다음과 같이 응답합니다.
An error occurred (AccessDeniedException) when calling the GetSecretValue operation:
User: arn:aws:sts::123456789012:assumed-role/eso-abac-role/session
is not authorized to perform: secretsmanager:GetSecretValue on resource:
arn:aws:secretsmanager:ap-northeast-2:123456789012:secret:prod/team-b/db-password
because no identity-based policy allows the secretsmanager:GetSecretValue action코드 단에서 막는 게 아니라 AWS가 강제하는 경계입니다.
팀 격리만으로 충분하다면 예시 1로 끝이지만, 실무에서는 여기에 또 다른 요청이 들어오더군요. "스테이징 Pod가 프로덕션 시크릿을 읽어버렸어요." 같은 상황이 생기면 클러스터 경계도 함께 잠가야 합니다.
예시 2: 클러스터 경계 추가 — 스테이징에서 프로덕션 시크릿 차단
팀 격리에 더해 클러스터 격리까지 필요한 경우입니다. IAM 정책에 eks-cluster-arn 조건을 추가하면 됩니다.
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "*",
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/allowed-cluster-arn": "${aws:PrincipalTag/eks-cluster-arn}",
"secretsmanager:ResourceTag/kubernetes-namespace": "${aws:PrincipalTag/kubernetes-namespace}"
}
}
}이 정책에 맞게 프로덕션 시크릿에 클러스터 ARN 태그를 붙입니다. 이미 생성된 시크릿에 태그를 추가하는 경우라면 tag-resource 명령을 사용합니다.
aws secretsmanager tag-resource \
--secret-id "prod/team-a/api-key" \
--tags \
Key=allowed-cluster-arn,Value=arn:aws:eks:ap-northeast-2:123456789012:cluster/prod-cluster \
Key=kubernetes-namespace,Value=team-a스테이징 클러스터(staging-cluster)의 Pod는 동일한 IAM 역할을 사용하더라도 eks-cluster-arn 세션 태그 값이 다르기 때문에 조건이 맞지 않아 접근이 차단됩니다.
참고: 네임스페이스 태그만 체크하고
eks-cluster-arn조건을 빠뜨리면, 다른 클러스터에서 동일한 네임스페이스 이름을 쓰는 Pod도 접근 가능해집니다. 멀티 클러스터 환경이라면eks-cluster-arn을 반드시 포함시키는 것을 권장합니다.
예시 3: Terraform으로 전체 구성 자동화
위 설정을 매번 콘솔에서 반복하는 건 현실적이지 않습니다. 인프라 코드로 관리하면 팀이 늘어날 때도 시크릿에 태그 하나만 추가하면 됩니다.
# IAM 역할 — Pod Identity 전용 신뢰 정책
resource "aws_iam_role" "eso_abac" {
name = "eso-abac-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = "pods.eks.amazonaws.com" }
# sts:TagSession이 있어야 세션 태그가 임시 자격 증명에 첨부됨
Action = ["sts:AssumeRole", "sts:TagSession"]
}
]
})
}
# ABAC IAM 정책
resource "aws_iam_role_policy" "eso_abac_policy" {
name = "eso-abac-secrets-policy"
role = aws_iam_role.eso_abac.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"]
Resource = "*"
Condition = {
StringEquals = {
# Terraform에서 ${}를 이스케이프하려면 $${} 형태로 작성
"secretsmanager:ResourceTag/eks-cluster-name" = "$${aws:PrincipalTag/eks-cluster-name}"
"secretsmanager:ResourceTag/kubernetes-namespace" = "$${aws:PrincipalTag/kubernetes-namespace}"
}
}
}
]
})
}
# Pod Identity Association
resource "aws_eks_pod_identity_association" "eso" {
cluster_name = aws_eks_cluster.main.name
namespace = "external-secrets"
service_account = "external-secrets"
role_arn = aws_iam_role.eso_abac.arn
}IRSA와 달리 신뢰 정책에 OIDC URL이 없습니다. pods.eks.amazonaws.com을 Principal로 지정하면 끝입니다. 신뢰 정책이 클러스터-독립적이기 때문에 여러 클러스터에서 동일한 역할을 재사용할 수 있다는 것도 장점입니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| IAM 역할 수 감소 | 팀 수에 비례해 역할이 늘어나던 패턴 대비 단일 역할로 운영 가능 |
| 위조 불가 보안 경계 | 세션 태그는 EKS 컨트롤 플레인이 설정 — 애플리케이션 코드로 조작 불가 |
| 설정 단순화 | OIDC 프로바이더 생성, 신뢰 정책 수동 편집 불필요 |
| 동적 권한 조정 | Pod 재시작 없이 IAM 정책 또는 시크릿 태그 변경만으로 권한 조정 가능 |
| 감사 추적 강화 | CloudTrail에 kubernetes-namespace, kubernetes-pod-name이 기록되어 접근 추적 용이 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 시크릿 태깅 운영 오버헤드 | 모든 시크릿에 올바른 태그 필요. 누락 시 접근 불가 | Terraform/CDK로 시크릿 생성과 태깅을 코드화. AWS Config 규칙으로 태그 없는 시크릿 생성 차단 권장 |
| EKS 전용 기능 | EKS Anywhere, 자체 관리 클러스터에서 사용 불가 | 범용 환경이라면 IRSA가 여전히 적합 |
| Pod Identity Agent 의존성 | eks-pod-identity-agent 애드온이 정상 작동해야 함 |
클러스터 생성 시 애드온 자동 설치 설정 권장 |
| 일부 서드파티 도구 미지원 | 오래된 Helm 차트나 오퍼레이터가 미지원일 수 있음 | 도구별 Pod Identity 지원 버전 확인 후 도입 |
이 중에서 실무에서 가장 자주 발목을 잡는 건 첫 번째입니다. 처음 구성할 때는 잘 되다가, 몇 달 뒤 누군가 시크릿을 추가하면서 태그를 빠뜨리면 애플리케이션이 조용히 실패합니다. Terraform 모듈로 시크릿 생성과 태깅을 묶어두는 게 제일 확실한 방어책입니다.
실무에서 가장 흔한 실수
-
sts:TagSession누락: 신뢰 정책에sts:AssumeRole만 있고sts:TagSession이 없으면 Pod Identity 연결은 성공하는데 세션 태그가 붙지 않아 ABAC 조건에서Deny가 납니다. "연결은 됐는데 왜 접근이 안 되지?"에서 한참 삽질하기 쉬운 지점입니다. -
신규 시크릿 태깅 미적용: 처음 구성할 때는 잘 되다가, 이후 팀원이 시크릿을 추가하면서 태그를 빠뜨리는 경우가 많습니다. AWS Config 규칙이나 SCP로 필수 태그 없이는 시크릿이 생성되지 않도록 가드레일을 걸어두는 것을 권장합니다.
-
StringLike오버매칭:StringEquals대신StringLike에 와일드카드(team-*)를 쓰면team-admin이team-a의 시크릿에도 접근 가능한 상황이 생길 수 있습니다. 정확한 매칭이 필요한 곳에는StringEquals를 사용하는 것이 안전합니다.
마치며
EKS Pod Identity와 ABAC의 조합은 "역할 하나, 경계는 태그로"라는 단순한 원칙으로 멀티테넌트 시크릿 격리를 AWS 레이어에서 강제합니다. 팀이 늘어나도 IAM 역할을 추가하는 게 아니라 시크릿에 태그를 붙이는 것으로 충분합니다.
지금 바로 시작해볼 수 있는 3단계:
-
EKS 클러스터에 Pod Identity Agent 애드온 활성화: AWS 콘솔 → EKS 클러스터 → Add-ons →
eks-pod-identity-agent설치, 또는 CLI로aws eks create-addon --cluster-name <cluster-name> --addon-name eks-pod-identity-agent명령을 실행합니다. 기존 클러스터라면 Terraformaws_eks_addon리소스에addon_name = "eks-pod-identity-agent"를 추가하는 방식도 있습니다. -
작은 범위에서 ABAC 정책 검증: 먼저 테스트 네임스페이스 두 개(
test-a,test-b)에 시크릿을 만들고 태그를 붙인 뒤, 위의 ABAC 정책을 단일 IAM 역할에 적용해봅니다. IAM Policy Simulator에서test-a서비스 어카운트 세션으로test-b태그가 달린 시크릿 ARN에GetSecretValue를 요청했을 때Deny가 나오는지 확인해보시면 좋습니다. -
ESO 또는 ASCP 통합: 검증이 끝나면 External Secrets Operator의
ClusterSecretStore에sessionTags와transitiveTagKeys를 설정하거나, ASCP SecretProviderClass에 Pod Identity 기반 인증을 적용해 애플리케이션이 시크릿을 파일로 마운트받도록 구성해볼 수 있습니다.
참고 자료
- Grant Pods access to AWS resources based on tags (ABAC) | Amazon EKS 공식 문서
- How to use AWS Secrets Manager and ABAC for enhanced secrets management in Amazon EKS | AWS Security Blog
- Amazon EKS Pod Identity: a new way for applications on EKS to obtain IAM credentials | AWS Containers Blog
- Announcing ASCP integration with Pod Identity | AWS Security Blog
- Session policies for Amazon EKS Pod Identity | AWS Containers Blog
- Secure Cross-Cluster Communication in EKS with VPC Lattice and Pod Identity IAM Session Tags | AWS Containers Blog
- How to Use EKS Pod Identity to Isolate Tenant Data in S3 With a Shared IAM Role | HackerNoon
- Control access to secrets using ABAC | AWS Secrets Manager 공식 문서
- Multi Tenancy | External Secrets Operator 공식 문서
- Identity and Access Management | EKS Best Practices Guides