콘텐츠로 이동

IAM

분류: Layer 3 - AWS 인프라 & 보안

AWS IAM(Identity and Access Management)은 “누가 AWS의 어떤 리소스에 어떤 작업을 할 수 있는지”를 결정하는 권한 시스템이다. 여기서 “누구”는 사람만이 아니라 ECS 태스크, Lambda 함수, GitHub Actions 워크플로처럼 AWS API를 호출하는 모든 주체를 포함한다.

IAM을 처음 볼 때는 기능 목록보다 한 문장을 먼저 잡는 편이 좋다. IAM은 자격증명(credentials)을 확인하고, 정책(policy)을 평가해, API 호출을 허용하거나 거부한다. EC2를 생성하는 콘솔 클릭, S3 객체 업로드, CI/CD 배포 명령, 애플리케이션의 SQS 전송은 모두 이 흐름을 지난다.

보안 경계도 여기서 바로 나온다. 브라우저에 내려가는 번들에는 AWS Access Key를 절대 넣지 않는다. 클라이언트는 애플리케이션 서버에 요청하고, AWS 접근은 서버 워크로드에 붙은 IAM Role이 맡게 한다.

AWS에서 권한은 부가 기능이 아니라 기본 전제다. 권한이 너무 좁으면 배포와 운영이 멈추고, 너무 넓으면 한 번 유출된 자격증명이 계정 전체 사고로 커진다. IAM 학습의 목표는 “AccessDenied를 없애는 법”이 아니라 허용해야 할 요청만 설명 가능한 방식으로 허용하는 법을 익히는 것이다.

아래 수치는 특정 보고서와 집계 자료 기준이므로 “최신 정답”이 아니라 위험의 크기를 잡는 참고치로 읽는다.

  • Exabeam의 2025 집계는 클라우드 보안 사고 중 misconfiguration 비중을 약 23%, 그 misconfiguration 안에서 사람 실수 비중을 약 **82%**로 정리한다 (Exabeam Cloud Security Statistics 2025 집계).
  • Cloud Security Alliance의 Top Threats to Cloud Computing 2025는 클라우드 침해의 70% 이상이 손상된 자격증명에서 시작되고, 잘못 설정된 identity policy가 클라우드 침해의 약 1/3을 차지한다고 설명한다 (CSA Top Threats 2025).

학습 관점에서 중요한 결론은 하나다. IAM 실수는 단순한 “권한 에러”가 아니라 누가 무엇을 할 수 있는지의 모델이 무너진 신호다. 그래서 IAM 문서는 명령어보다 모델을 먼저 익혀야 한다.

2.5 선행 기술의 한계 — root credential 단일 키에서 IAM으로

섹션 제목: “2.5 선행 기술의 한계 — root credential 단일 키에서 IAM으로”

IAM은 AWS 계정에 “여러 주체”와 “주체별 권한”을 도입하기 위해 등장했다. AWS의 회고 글에 따르면 IAM은 2010년 preview, 2011년 GA로 공개됐다 (AWS — Happy 10th Birthday IAM). 그 전에는 AWS 계정 소유자(root)의 이메일/비밀번호와 단일 Access Key 1쌍이 사실상 계정 접근의 중심이었다. root 계정은 AWS 계정의 최상위 소유자이므로 일상 작업자가 공유해서 쓰는 자격증명이 되면 안 된다.

root-only 방식은 작게는 단순해 보인다. 한 사람이 혼자 테스트 계정에서 S3 버킷 하나를 만질 때는 root만 있어도 일이 된다. 하지만 팀과 서비스가 늘어나는 순간 세 가지가 동시에 깨진다.

깨지는 지점root-only에서 생기는 문제IAM이 만든 해법
권한 세분화10명이 한 root 자격증명을 공유하면 모두가 계정 전체 권한을 가진다.User, Group, Role로 주체를 나누고 Policy로 권한 범위를 좁힌다
키 교체 비용1명 퇴사 때 root 비밀번호와 Access Key를 모든 노트북, CI, 서버에서 동시에 바꿔야 한다.사람/서비스 단위로 자격증명을 회수하거나 Role 세션을 만료한다
감사 추적CloudTrail에 같은 root 주체만 남으면 “누가 삭제했는지”를 끝까지 구분할 수 없다.userIdentity.arn으로 호출 주체를 분리해 추적한다
워크로드 권한 분리EC2, ECS, Lambda가 모두 같은 장기 키를 쓰면 한 서비스 침해가 전체 계정 침해로 번질 수 있다.서비스별 IAM Role과 임시 자격증명으로 노출창을 줄인다

CloudTrail은 AWS 계정에서 발생한 API 호출 이력을 남기는 감사 로그 서비스다. IAM을 공부할 때 CloudTrail을 함께 보는 이유는 권한 모델이 “누가 어떤 API를 호출했는가”라는 기록으로 검증되기 때문이다.

이 lineage가 이 문서 전체의 중심이다. IAM은 단순히 “권한 메뉴”가 아니라 AWS에 주체(subject), 리소스(resource), 작업(action), **조건(condition)**이라는 축을 만든 시스템이다. 뒤에서 보는 ARN(Amazon Resource Name) 패턴, Role 신뢰 정책(Trust Policy), 임시 자격증명 발급 서비스인 STS(Security Token Service), 조직 상한선인 SCP(Service Control Policy), 외부 토큰 신뢰 방식인 OIDC(OpenID Connect)도 모두 root-only 구조의 한계를 줄이기 위한 후속 장치다.

처음 읽을 때는 용어를 한꺼번에 외우지 말고 요청 한 건이 어떤 요소로 쪼개지는지부터 본다.

주체(Principal)
└─ 자격증명으로 자신을 증명한다
└─ 정책(Policy)이 Action, Resource, Condition을 평가한다
└─ AWS API 요청이 Allow 또는 Deny로 결정된다
용어첫 정의처음 잡아야 할 경계
IAMIdentity and Access Management. AWS API 호출의 주체와 권한을 관리하는 서비스다.리소스를 직접 실행하는 서비스가 아니라, 실행 가능 여부를 판정하는 권한 계층이다.
Principal요청을 보내는 주체다. IAM User, IAM Role, AWS 서비스, 외부 로그인 시스템에서 들어온 세션이 될 수 있다.사람 이름과 같지 않다. 같은 사람이 여러 Role 세션을 쓸 수 있다.
IdP / federationIdP(Identity Provider)는 사용자를 인증하는 외부 로그인 시스템이고, federation은 그 인증 결과를 AWS가 신뢰해 임시 자격증명을 발급하는 방식이다.AWS가 비밀번호 원본을 직접 보관하지 않아도 되지만, 신뢰 조건을 좁혀야 한다.
IAM UserAWS 계정 안에 만든 장기 사용자다. 비밀번호와 Access Key를 가질 수 있다.일반 사람 접근의 기본값으로 두기보다 federation이나 중앙 SSO 계층을 우선 검토한다.
IAM Group여러 User에 같은 정책을 붙이기 위한 묶음이다.Role은 Group에 들어가지 않는다. Group은 User 관리 도구다.
IAM Role직접 로그인하는 사용자가 아니라, 누군가가 임시로 assume해서 쓰는 권한 그릇이다.Role에는 “무엇을 할 수 있는지”와 “누가 맡을 수 있는지”가 모두 필요하다.
PolicyAllow 또는 Deny 규칙을 담은 JSON 문서다.Policy 자체는 실행 주체가 아니다. User나 Role 등에 붙어야 효과가 난다.
ARNAmazon Resource Name. AWS 리소스를 전역적으로 가리키는 문자열이다. 예: arn:aws:s3:::my-bucket/*.S3처럼 버킷 ARN과 객체 ARN이 다른 서비스가 있다.
STSSecurity Token Service. Role을 assume할 때 임시 자격증명을 발급하는 AWS 서비스다.임시 자격증명도 권한이 강하면 위험하다. 다만 수명이 짧아 노출창이 줄어든다.
Trust PolicyRole을 누가 assume할 수 있는지 정하는 정책이다.Permission Policy가 있어도 Trust Policy가 틀리면 Role을 맡을 수 없다.
Permission BoundaryPermission Boundary는 User나 Role이 가질 수 있는 최대 권한 상한선이다.Boundary는 권한을 부여하지 않는다. 상한을 좁힐 뿐이다.
SCPService Control Policy. AWS Organizations에서 계정이나 OU에 적용하는 조직 단위 권한 상한선이다.SCP도 권한을 부여하지 않는다. 계정 안의 Allow를 더 좁히는 가드레일이다.
OIDCOpenID Connect. 외부 시스템이 서명된 토큰으로 자신을 증명하고 Role을 assume하게 하는 표준 프로토콜이다.Trust Policy의 조건을 넓게 열면 다른 repo나 브랜치도 Role을 맡을 수 있다.
MFAMulti-Factor Authentication. 비밀번호 외 OTP, 보안 키 같은 두 번째 인증 요소를 요구하는 방식이다.권한 범위를 줄이지는 않지만, 사람 계정 탈취 확률을 낮춘다.
IAM Identity Center여러 AWS 계정의 사람 접근을 SSO와 임시 자격증명 중심으로 관리하는 서비스다.IAM User의 완전 대체라기보다 workforce 접근을 중앙화하는 상위 관리 계층이다.

4. Policy JSON 읽기 — Action, Resource, Condition

섹션 제목: “4. Policy JSON 읽기 — Action, Resource, Condition”

IAM Policy는 “누가”를 제외한 나머지 판단을 JSON으로 표현한다. 가장 중요한 세 필드는 다음이다.

  • Action: 어떤 API 작업인가. 예: s3:GetObject, ecs:UpdateService
  • Resource: 어떤 리소스에 대한 작업인가. 보통 ARN(Amazon Resource Name)으로 쓴다.
  • Condition: 어떤 조건에서만 적용되는가. 리전, MFA 여부, IP, 시간, OIDC 토큰 클레임 등을 제한한다.

아래 예시는 애플리케이션 업로드 버킷을 대상으로 한 작은 worked example이다.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowObjectReadWrite",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": [
"arn:aws:s3:::my-app-uploads/*",
"arn:aws:s3:::my-app-uploads-dev/*"
]
},
{
"Sid": "AllowListOnlySelectedPrefixes",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-app-uploads",
"Condition": {
"StringLike": {
"s3:prefix": ["uploads/*", "temp/*"]
}
}
},
{
"Sid": "DenyPublicObjectAcl",
"Effect": "Deny",
"Action": "s3:PutObjectAcl",
"Resource": "arn:aws:s3:::my-app-uploads/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": ["public-read", "public-read-write"]
}
}
}
]
}

이 JSON이 실제로 말하는 내용을 문장으로 풀면 다음과 같다.

  1. my-app-uploadsmy-app-uploads-dev 버킷 안의 객체는 읽고 쓰고 지울 수 있다.
  2. 버킷 목록 조회는 uploads/temp/ prefix에 대해서만 허용한다.
  3. 객체를 public ACL로 바꾸는 작업은 명시적으로 거부한다.

ARN 패턴은 IAM 초반에 가장 자주 틀리는 지점이다.

ARN 패턴의미자주 나는 실수
arn:aws:s3:::my-bucket/*버킷 안의 모든 객체s3:ListBucket에는 이 ARN이 맞지 않는다.
arn:aws:s3:::my-bucket버킷 자체s3:GetObject에는 객체 ARN이 필요하다.
arn:aws:ecs:ap-northeast-2:123456789012:service/my-cluster/*특정 ECS 클러스터의 모든 서비스계정 ID나 리전이 실제 리소스와 다르면 매칭되지 않는다
"Resource": "*"모든 리소스 또는 ARN 미지원 작업 대상편하지만 최소 권한과 충돌한다. 이유를 남겨야 한다.

Condition은 “허용하되 좁히는” 장치다. 아래 조건은 같은 Allow라도 리전, MFA(Multi-Factor Authentication), IP, 시간으로 더 제한한다.

{
"Condition": {
"StringEquals": { "aws:RequestedRegion": "ap-northeast-2" },
"Bool": { "aws:MultiFactorAuthPresent": "true" },
"IpAddress": { "aws:SourceIp": ["203.0.113.0/24"] },
"DateLessThan": { "aws:CurrentTime": "2026-12-31T23:59:59Z" }
}
}

반례를 하나 보자. s3:GetObject를 허용하려고 Resourcearn:aws:s3:::my-app-uploads만 넣으면 버킷 자체에는 매칭되지만 객체에는 매칭되지 않는다. 정책은 붙어 있고 문법 오류도 없지만 실제 객체 조회는 AccessDenied가 난다. 이 경우 필요한 것은 더 큰 권한이 아니라 올바른 객체 ARN인 arn:aws:s3:::my-app-uploads/*다.

5. 정책 평가 흐름 — Explicit Deny, Allow, Implicit Deny

섹션 제목: “5. 정책 평가 흐름 — Explicit Deny, Allow, Implicit Deny”

IAM은 기본적으로 닫힌 시스템이다. 요청을 허용하려면 명시적인 Allow가 필요하고, 아무 정책도 매칭되지 않으면 Implicit Deny, 즉 기본 거부가 된다. 그 위에 Explicit Deny가 있으면 모든 Allow보다 우선한다.

AWS가 요청을 평가하는 핵심 순서는 다음과 같다.

  1. Explicit Deny: 어느 적용 정책에서든 명시적 Deny가 있으면 즉시 거부한다.
  2. Allow: Deny가 없고 요청과 매칭되는 Allow가 있으면 허용한다.
  3. Implicit Deny: Deny도 Allow도 없으면 기본값으로 거부한다.
IAM 정책 평가 순서
flowchart TD
Request["AWS API 요청"] --> Deny{"Explicit Deny가 있는가?"}
Deny -->|Yes| Reject["즉시 거부"]
Deny -->|No| Allow{"Allow가 있는가?"}
Allow -->|Yes| Permit["허용"]
Allow -->|No| Implicit["Implicit Deny로 거부"]

이 흐름은 단일 Policy 안에서만 적용되는 것이 아니다. Identity Policy(주체에 붙는 정책), resource-based policy(버킷·큐처럼 리소스 자체에 붙는 정책), Permission Boundary, SCP(Service Control Policy), session policy(AssumeRole로 만든 임시 세션에 추가로 붙이는 일시적 상한 정책)가 함께 작동할 때도 “명시적 거부가 있으면 끝”이라는 원칙은 유지된다.

작은 사고 실험을 해보자.

상황결과이유
Role Policy가 s3:PutObject Allow허용 가능상위 Deny가 없고 Resource가 맞으면 Allow가 성립한다.
Role Policy가 Allow지만 SCP가 해당 리전에서 Deny거부SCP의 Explicit Deny가 Allow를 덮는다.
Policy에 s3:GetObject 언급이 없음거부아무 Allow도 없으므로 Implicit Deny다.
Allow 10개가 있고 Bucket Policy에 같은 요청 Deny 1개거부Deny는 개수 싸움이 아니다. 하나면 충분하다.

퀴즈

IAM Policy Simulator에서 Allow가 나왔는데 실제 API는 AccessDenied라면 무엇을 먼저 의심해야 할까?

힌트: Allow보다 위에 있거나 다른 정책 계층에서 오는 거부를 떠올린다.

정답 보기

SCP, Permission Boundary, 리소스 기반 정책 Deny, 세션 정책, Trust Policy 같은 다른 평가 계층을 먼저 확인한다. 단순히 Role Policy에 Allow를 하나 더 붙이면 원인이 가려질 수 있다.

더 보기: AWS IAM Policy Evaluation Logic 공식 문서

6. 장기 키보다 Role을 기본값으로 두는 이유

섹션 제목: “6. 장기 키보다 Role을 기본값으로 두는 이유”

Access Key는 AWS API를 호출하기 위한 장기 자격증명이다. 보통 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY 쌍으로 다룬다. 반대로 IAM Role은 STS(Security Token Service)가 발급한 임시 자격증명으로 사용된다. 핵심 차이는 권한의 강도가 아니라 **노출창(window of exposure)**이다.

Access Key와 Role의 노출창 비교

장기 Access Key

수동 비활성화나 교체 전까지 계속 유효하다. 공개 저장소에 노출되면 자동 수집과 abuse가 분 단위로 시작될 수 있고, 내부 로그나 private repo 유출은 발견이 훨씬 늦어질 수 있다.

임시 자격증명, federation, Roles Anywhere를 전혀 쓸 수 없는 예외적인 외부 시스템

IAM Role 임시 자격증명

보통 1시간 단위로 발급되고 설정에 따라 더 짧거나 길 수 있다. SDK가 갱신을 처리하며, 유출되어도 세션 만료가 피해 시간을 제한한다.

EC2, ECS, Lambda, GitHub Actions OIDC, 대부분의 서버 워크로드

수치 감각으로 보면 차이가 더 분명하다. 장기 Access Key가 유출되면 “탐지 시간 + 담당자 대응 시간 + 모든 사용처 교체 시간”이 노출창이 된다. 공개 GitHub 저장소에 노출된 AWS 키는 실험·honeypot 성격의 보고에서 1~4분 안에 abuse 시도가 관측되기도 했다. 반면 IAM Role 세션은 보통 약 1시간 단위로 쓰이고 Role의 max session duration으로 상한을 둘 수 있다. private repo, 내부 wiki, 로그 파일 유출은 발견이 훨씬 늦어질 수 있으므로 수치 자체보다 장기 키는 발견 전까지 계속 살아 있고, Role은 세션 만료가 기본 방어선이 된다는 직관을 잡는다.

그래서 AWS 공식 best practice는 워크로드에 장기 키를 넣기보다 Role 기반 임시 자격증명을 쓰라고 안내한다 (AWS IAM Best Practices). 사람 접근도 IAM User를 새로 늘리는 대신 IAM Identity Center나 외부 IdP federation으로 임시 자격증명을 발급하는 흐름을 우선 검토한다.

실무 선택 기준은 다음처럼 잡을 수 있다.

접근 주체기본 선택판단 기준
사람의 콘솔/CLI 접근IAM Identity Center 또는 외부 IdP federation장기 키를 개인 노트북에 남기지 않고, 입퇴사와 MFA를 중앙에서 관리한다
ECS, EC2, Lambda 워크로드IAM Role서비스가 실행되는 위치에 Role을 붙이고 SDK가 임시 자격증명을 받는다
GitHub Actions 등 외부 CI/CDOIDC(OpenID Connect) + AssumeRoleWithWebIdentity저장된 secret 없이 워크플로 실행 때마다 짧은 세션을 만든다
온프레미스 서버IAM Roles Anywhere 등 인증서 기반 임시 자격증명장기 키를 서버 파일에 두지 않는 방향을 우선 검토한다
비상 접근root와 제한된 break-glass 관리자 사용자, 강한 MFA 적용Identity Center 장애 같은 예외에 대비하되 사용 빈도를 낮게 유지한다
임시 자격증명을 못 쓰는 legacy최소 권한 Access Key + 회전 + 사용처 문서화예외 사유와 교체 책임자가 없으면 만들지 않는다

MFA(Multi-Factor Authentication)는 여기서 사람 계정의 탈취 가능성을 낮추는 장치다. MFA가 권한 범위를 좁히는 것은 아니지만, root와 콘솔 접근 계정에는 “비밀번호 하나가 곧 AWS 계정”이 되는 상황을 막아준다.

7. Role은 Permission Policy와 Trust Policy가 함께 있어야 한다

섹션 제목: “7. Role은 Permission Policy와 Trust Policy가 함께 있어야 한다”

Role은 권한을 담는 그릇이지만, 스스로 실행되지 않는다. 누군가가 Role을 assume해야 한다. 이때 필요한 정책은 두 종류다.

정책 종류묻는 질문예시
Permission Policy이 Role을 맡은 뒤 무엇을 할 수 있나?s3:PutObjectmy-app-uploads/*에 허용
Trust Policy누가 이 Role을 맡을 수 있나?ECS 태스크 서비스인 ecs-tasks.amazonaws.com이 assume 가능하다

ECS Task Role의 Trust Policy는 다음처럼 생겼다.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

여기서 sts:AssumeRole은 STS(Security Token Service)에 “이 Role의 임시 자격증명을 발급해도 되는가”라고 묻는 동작이다. Permission Policy가 아무리 넓어도 Trust Policy가 ECS를 신뢰하지 않으면 컨테이너는 그 Role을 사용할 수 없다.

ECS에서는 Task Role과 Execution Role도 반드시 구분해야 한다.

ECS의 Task Role과 Execution Role

Task Role taskRoleArn

컨테이너 안의 애플리케이션 코드가 AWS 서비스에 접근할 때 쓰는 Role이다.

애플리케이션이 S3 업로드, SQS 송신, Secrets Manager 조회를 수행할 때

Execution Role executionRoleArn

ECS 플랫폼이 태스크를 시작하기 위해 쓰는 Role이다.

ECR 이미지 pull, CloudWatch Logs 전송, 태스크 시작 시 secret 주입을 수행할 때

반례: 애플리케이션이 S3에 업로드해야 하는데 Execution Role에만 s3:PutObject를 붙이면 컨테이너 코드의 권한은 늘지 않는다. ECR pull과 로그 전송은 될 수 있지만, 애플리케이션의 S3 호출은 여전히 AccessDenied가 난다. 이때 필요한 것은 Execution Role 권한 추가가 아니라 Task Role에 올바른 Permission Policy를 붙이는 것이다.

더 보기: Amazon ECS Task IAM Role 공식 문서

8. CI/CD에서는 OIDC와 Trust Policy 조건이 핵심이다

섹션 제목: “8. CI/CD에서는 OIDC와 Trust Policy 조건이 핵심이다”

GitHub Actions 같은 외부 CI/CD가 AWS에 배포해야 할 때 과거에는 AWS Access Key를 GitHub Secrets에 저장하는 방식이 흔했다. 더 나은 기본값은 OIDC(OpenID Connect)로 GitHub가 발급한 토큰을 AWS가 검증하고, 조건에 맞을 때만 Role을 assume하게 하는 것이다.

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: ap-northeast-2

이 YAML은 secret을 읽는 것이 아니라 GitHub 워크플로 실행 컨텍스트를 근거로 AWS Role 세션을 받는다. 보안상 중요한 부분은 YAML보다 IAM Role의 Trust Policy다. OIDC claim은 토큰 안에 들어 있는 속성이다. sub는 subject, 즉 “어떤 repo/ref에서 온 실행인가”를 나타내고, aud는 audience, 즉 토큰을 받을 대상 서비스를 뜻한다. GitHub에서는 repositoryref claim으로 특정 저장소와 브랜치·태그 범위를 더 명확히 좁힐 수 있다.

OIDC vs GitHub Secrets — 결정 기준

항목GitHub Secrets에 Access Key 저장OIDC + AssumeRoleWithWebIdentity
자격증명 수명저장된 secret은 사람이 rotate하거나 비활성화할 때까지 장기 유지워크플로 실행 단위의 임시 세션. 보통 짧은 duration으로 발급한다
유출 시 노출창발견과 키 비활성화 전까지 지속세션 만료까지의 잔여 시간으로 제한
교체 비용repo/조직 secret 갱신, 키 비활성화, 파이프라인 재검증 필요Trust Policy 조건 변경만으로 미래 assume을 막을 수 있고 앱 compute 재배포가 필요 없다
감사 단위같은 키를 여러 workflow가 쓰면 호출자 구분이 흐려진다repo, ref, workflow claim을 CloudTrail과 조건에서 함께 추적할 수 있다
실수 반례secret이 로그, fork, 빌드 산출물에 새면 장기 키가 남는다Trust Policy를 repo:*처럼 넓게 열면 원치 않는 repo도 assume 가능
적합한 경우OIDC를 지원하지 않는 legacy CI의 예외 경로GitHub Actions, GitLab, 외부 SaaS 배포의 기본값

OIDC도 자동으로 안전해지는 마법은 아니다. Trust Policy 조건이 너무 넓으면 “Access Key를 없앴다”는 장점만 남고, 어떤 외부 주체가 Role을 맡을 수 있는지의 경계가 흐려진다. 따라서 CI/CD Role은 권한 정책보다 먼저 누가 assume할 수 있는가를 좁혀야 한다.

9. 거버넌스 계층 — SCP > Boundary > Role > Policy

섹션 제목: “9. 거버넌스 계층 — SCP > Boundary > Role > Policy”

IAM을 디버깅할 때 가장 흔한 착각은 “Role Policy에 Allow가 있으니 허용될 것”이라고 생각하는 것이다. 실제 AWS 계정에서는 여러 상한선이 겹친다.

조직 전체 가드레일
└─ SCP (Service Control Policy): 조직/OU/계정에 적용되는 최대 허용 범위
└─ 계정 내부 상한
└─ Permission Boundary: 특정 User/Role이 가질 수 있는 최대 권한
└─ IAM Role: 워크로드나 사람이 assume하는 권한 그릇
└─ IAM Policy: 실제 Allow/Deny 규칙

이 계층은 “위에서 아래로 권한을 더한다”가 아니다. 위 계층은 대체로 하위 계층의 상한을 정한다. SCP가 us-east-1ap-northeast-2 외 리전 생성을 Deny하면, 계정 안의 관리자 Role에 ec2:* Allow가 있어도 다른 리전의 EC2 생성은 거부된다. Permission Boundary도 마찬가지다. Boundary에 없는 작업은 Role Policy가 Allow해도 결과적으로 불가능하다.

작은 예시를 보자.

{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["ap-northeast-2", "us-east-1"]
}
}
}

이 SCP가 적용된 계정에서 도쿄 리전 외부에 리소스를 만들려 하면, Role Policy의 Allow 여부와 무관하게 거부된다. 그래서 AccessDenied를 만났을 때는 아래 순서로 생각한다.

  1. 이 요청을 명시적으로 Deny하는 상위 계층이 있는가?
  2. Boundary나 세션 정책이 Allow의 범위를 자르고 있는가?
  3. Role의 Permission Policy가 Action과 Resource를 정확히 허용하는가?
  4. 리소스 기반 정책이 별도의 Deny를 갖고 있는가?
  5. 아무 Allow도 없어서 Implicit Deny인 것인가?

10. 최소 권한은 “적게 주기”가 아니라 “설명 가능하게 좁히기”다

섹션 제목: “10. 최소 권한은 “적게 주기”가 아니라 “설명 가능하게 좁히기”다”

최소 권한(Least Privilege)은 필요한 권한만 주는 원칙이다. 하지만 실무에서는 “권한을 적게”보다 “권한을 설명 가능한 축으로 좁히기”가 더 유용하다.

넓은 권한의 예좁힌 권한의 예실패 신호
Actions3:*s3:GetObject, s3:PutObject읽기만 필요한 작업이 삭제 권한까지 가진다
Resource"Resource": "*"arn:aws:s3:::my-app-uploads/*테스트 버킷 권한이 운영 버킷까지 닿는다
Condition조건 없음aws:RequestedRegion, aws:SourceIp, aws:MultiFactorAuthPresent같은 Role이 어디서나 같은 권한을 가진다
Principal모든 계정 또는 모든 repo특정 AWS 계정, 특정 GitHub repositoryref의도하지 않은 외부 주체가 Role을 assume한다
Time장기 Access Key 무기한STS 세션, 정기 회전, 미사용 키 제거사용하지 않는 키가 90일 이상 남아 있다

IAM Access Analyzer는 실제 사용 이력이나 외부 공유 가능성을 바탕으로 권한을 줄일 후보를 찾는 데 도움을 준다. 단, 도구가 “unused”라고 표시한다고 바로 삭제하기보다 그 권한이 비상 절차, 드문 배포 경로, 계절성 작업에 필요한지 확인해야 한다. 최소 권한의 목표는 권한을 없애는 것이 아니라 필요한 권한을 근거와 함께 남기는 것이다.

11. IAM Silent Failure — 에러보다 위험한 조용한 실패

섹션 제목: “11. IAM Silent Failure — 에러보다 위험한 조용한 실패”

IAM에서는 “정책을 붙였는데 동작하지 않음”이 자주 생긴다. 문법 오류가 없고 콘솔에도 정책이 보이기 때문에 더 위험하다. 이때는 실패 메시지보다 평가 축을 다시 분해해야 한다.

패턴발생 시나리오왜 조용한가먼저 확인할 것
잘못된 Resource ARNs3:GetObjectarn:aws:s3:::bucket에 Allow해서 객체 접근이 계속 실패한다.정책은 유효하지만 요청 객체 ARN과 매칭되지 않는다.실제 요청 ARN이 arn:aws:s3:::bucket/key 형태인지 시뮬레이션한다.
Trust Policy 누락Permission Policy는 있는데 ECS가 Role을 assume하지 못한다.Role 권한만 보면 충분해 보이지만 세션 발급 전 단계가 막힌다.Trust Policy의 Principal이 ecs-tasks.amazonaws.com인지 확인한다.
SCP 충돌개발 계정 Role은 Allow인데 조직 SCP가 리전이나 서비스를 Deny한다.Role Policy Simulator만 보면 Allow처럼 보일 수 있다.Organizations SCP 포함 시뮬레이션 또는 CloudTrail의 deny context를 확인한다.
Permission Boundary개발자가 새 Policy를 붙였는데 Boundary 밖 Action이라 효과가 없다.Policy attach는 성공하지만 effective permission은 늘지 않는다.Role/User의 PermissionsBoundary를 확인한다.
Task Role/Execution 혼동애플리케이션 S3 권한을 Execution Role에 붙인다.태스크 시작은 정상이라 권한 설정이 맞아 보인다.앱 로그의 assumed-role/... ARN이 Task Role인지 확인한다.
OIDC 조건 과소/과대GitHub Actions Role의 sub 조건이 너무 좁아 배포가 실패하거나, 너무 넓어 다른 repo가 assume할 수 있다.IAM Role과 OIDC Provider는 존재하므로 설정 완료처럼 보인다.CloudTrail의 AssumeRoleWithWebIdentity와 Trust Policy Condition을 함께 본다.

실패 신호를 줄이면 다음 세 문장으로 요약된다.

  • AccessDenied인데 Role Policy가 Allow라면 상위 Deny나 다른 정책 계층을 의심한다.
  • Unable to locate credentials라면 권한 평가 이전에 자격증명 공급 경로가 끊긴 것이다.
  • CloudTrail에서 호출자가 예상한 Role이 아니라면 정책 내용보다 “누가 호출했는가”를 먼저 고친다.

CloudTrail 이벤트 필드는 서비스와 인증 경로에 따라 달라질 수 있다. 알림이나 SIEM(Security Information and Event Management) 쿼리를 만들 때는 특정 표시 이름 하나에만 의존하지 말고 account, ARN, session issuer(임시 세션을 발급한 원래 Role 정보), event name, error code를 함께 보도록 설계한다.

12. 잘못 배포된 정책 회수 — Policy Versioning Rollback

섹션 제목: “12. 잘못 배포된 정책 회수 — Policy Versioning Rollback”

IAM Managed Policy는 버전 기록을 가진다. 최대 5개 버전을 보관하며, 기본 버전(default version)을 바꾸면 이전 정책으로 빠르게 되돌릴 수 있다 (AWS IAM Policy Versioning 공식 문서).

이 기능은 단순 편의 기능이 아니라 권한 변경의 안전장치다. 권한 정책은 배포 직후에 “너무 좁아서 장애”와 “너무 넓어서 사고”를 모두 만들 수 있다. Managed Policy 버전을 쓰면 새 버전을 만든 뒤 문제가 생겼을 때 이전 정상 버전을 default로 되돌리는 방식으로 blast radius(잘못된 변경이나 침해가 영향을 미치는 범위)를 줄일 수 있다.

정량 감각도 필요하다.

  • 보관 버전은 최대 5개다. 오래된 정상 버전이 밀려나지 않도록 배포 전후 버전을 의식해야 한다.
  • default version 전환은 정책을 다시 붙이는 작업보다 빠르지만, 이미 발급된 임시 자격증명과 서비스 캐시는 약간의 전파 지연을 가질 수 있다.
  • rollback이 가능하다고 해도 Resource: "*"로 넓힌 정책을 검토 없이 배포해도 된다는 뜻은 아니다. rollback은 사고 대응 장치이지 리뷰 대체물이 아니다.

13. 다른 권한 시스템으로 옮겨 가는 사고법

섹션 제목: “13. 다른 권한 시스템으로 옮겨 가는 사고법”

IAM에서 배운 원칙은 Kubernetes RBAC, OAuth scope, PostgreSQL 권한을 읽을 때도 쓸 수 있다. 이름은 달라도 대부분의 권한 시스템은 네 질문으로 분해된다.

질문IAM 예시Kubernetes RBACPostgreSQL
누가 접근하는가?IAM User, IAM Role, Role sessionServiceAccountDB User, DB Role
무엇에 접근하는가?S3 ARN, ECS service ARNNamespace, Pod, SecretTable, Schema
무엇을 할 수 있는가?s3:GetObject, ecs:UpdateServiceget, list, createSELECT, INSERT
기본값은 허용인가 거부인가?Implicit Deny보통 명시 권한만 허용부여된 권한만 허용
상위에서 덮는 제어가 있는가?SCP, Boundary, Resource PolicyAdmission, OPA, PSP 대체 정책Superuser, Row-Level Security

이 표의 목적은 IAM을 다른 도구에 억지로 비유하는 것이 아니다. 권한 에러를 만나면 늘 Subject → Action → Resource → Condition → 상위 Deny 순서로 쪼개 볼 수 있다는 사고 습관을 만드는 것이다.

개념 A개념 B차이점
UserRoleUser는 장기 자격증명을 가질 수 있는 계정 내 사용자, Role은 assume 대상
PolicyRolePolicy는 권한 규칙, Role은 그 규칙을 가진 임시 권한 그릇
Permission PolicyTrust PolicyPermission은 맡은 뒤 할 일, Trust는 누가 맡을 수 있는지
Inline PolicyManaged PolicyInline은 한 엔티티에 종속, Managed는 재사용과 버전 관리 가능
Root AccountIAM UserRoot는 계정 최상위 권한, IAM User는 제한 가능한 개별 주체
IAM UserIAM Identity CenterUser는 계정 안 장기 사용자, Identity Center는 사람 접근 중앙 관리 계층
Permission BoundarySCPBoundary는 User/Role 상한, SCP는 조직/계정 상한
Task RoleExecution RoleTask Role은 앱 코드 권한, Execution Role은 ECS 플랫폼 시작 권한
  • AWS 콘솔과 CLI 접근 권한 관리
  • ECS, EC2, Lambda 같은 워크로드가 S3, SQS, Secrets Manager에 접근할 때
  • GitHub Actions, Jenkins, 배포 SaaS가 AWS에 배포할 때
  • 팀원 온보딩/오프보딩 시 권한 부여와 회수
  • 보안 감사에서 외부 공유 리소스, 미사용 권한, 장기 키를 점검할 때
  • 장애 대응 중 AccessDenied, ExpiredTokenException, Unable to locate credentials 원인을 분해할 때

16. 선택 부록 — 확인 절차와 짧은 런북

섹션 제목: “16. 선택 부록 — 확인 절차와 짧은 런북”

본문 이해에 필수인 내용은 위에 설명했다. 아래는 실제 계정에서 확인할 때 펼쳐 보는 절차다.

IAM Policy Simulator와 Access Analyzer 확인
Policy Simulator:
IAM User/Role 선택 → 서비스 선택 → 작업 선택 → Resource ARN 입력 → Run Simulation
필요 시 Organizations SCP 포함 시뮬레이션을 켠다.
Access Analyzer:
IAM → Access Analyzer → Findings
External access: 외부 계정이나 public 접근 가능성 확인
Unused access: 장기간 사용되지 않은 권한과 Access Key 후보 확인

주의: Simulator에서 Allow가 나와도 실제 요청이 거부될 수 있다. 리소스 기반 정책, SCP, Permission Boundary, 세션 정책, 실제 호출 주체가 같은지 함께 확인한다.

현재 호출 주체와 자격증명 확인
Terminal window
aws sts get-caller-identity

예상 출력에서 Arn이 자신이 의도한 User 또는 assumed Role인지 확인한다.

{
"UserId": "AIDAXXXXXXXXXXXXXXXXX",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/my-task-role/session"
}

Unable to locate credentials는 권한 부족이 아니라 자격증명 공급 경로가 없는 상태다. SSO 로그인, profile, 환경변수, ECS Task Role 연결부터 확인한다.

AccessDenied 한 건을 CloudTrail에서 짧게 추적
Terminal window
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=PutObject \
--max-results 10

찾은 이벤트에서 errorCode, 호출자 userIdentity.arn, sessionIssuer, 대상 리소스를 함께 본다. 호출자가 예상한 Role이 아니면 정책을 더 붙이기 전에 자격증명 공급 경로부터 고친다.

Managed Policy 버전 롤백
Terminal window
aws iam list-policy-versions \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--query 'Versions[*].{Version:VersionId,IsDefault:IsDefaultVersion,Created:CreateDate}'

예상 결과에서 이전 정상 버전이 v2, 현재 문제 버전이 v3이라면 default version을 되돌린다.

Terminal window
aws iam set-default-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--version-id v2

문제 버전은 기본 버전이 아닐 때만 삭제할 수 있다.

Terminal window
aws iam delete-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--version-id v3
직접 확인해볼 항목
  • AWS 콘솔에서 내 User 또는 SSO 세션이 어떤 권한을 갖는지 확인한다.
  • 팀에서 쓰는 ECS Task Role과 Execution Role을 하나씩 열어 역할 차이를 확인한다.
  • S3 객체 작업 하나를 골라 버킷 ARN과 객체 ARN을 바꿔 Policy Simulator 결과를 비교한다.
  • CloudTrail에서 AccessDenied 이벤트 하나를 찾아 호출 주체 ARN과 error code를 읽어본다.
  • IAM Access Analyzer에서 외부 공유와 미사용 권한 finding을 확인한다.

IAM 복습 체크리스트

  • IAM을 Subject, Action, Resource, Condition, 상위 Deny 계층으로 분해해 설명할 수 있다.
  • root-only credential 구조가 왜 권한 분리, 키 교체, 감사 추적에서 실패하는지 말할 수 있다.
  • ARN에서 S3 버킷 ARN과 객체 ARN을 구분할 수 있다.
  • Explicit Deny, Allow, Implicit Deny 평가 순서를 예시로 설명할 수 있다.
  • Access Key와 Role의 차이를 노출창 관점에서 비교할 수 있다.
  • Trust Policy와 Permission Policy의 질문이 어떻게 다른지 구분할 수 있다.
  • ECS Task Role과 Execution Role을 혼동하지 않고 적용 위치를 구분할 수 있다.
  • GitHub Secrets 방식과 OIDC 방식의 trade-off를 설명할 수 있다.
  • SCP, Permission Boundary, Role, Policy의 거버넌스 계층을 따라 AccessDenied를 추적할 수 있다.
  • Managed Policy 버전 롤백이 언제 도움이 되고 어떤 한계를 갖는지 말할 수 있다.

AWS STS, AssumeRole, AssumeRoleWithWebIdentity, MFA 적용, IAM Policy Simulator, Service Control Policy(SCP), Permission Boundary, Resource-based Policy, Cross-Account Access, IAM Identity Center, OIDC, IAM Access Analyzer

  1. IAM은 AWS API 요청을 Subject, Action, Resource, Condition으로 나누고 정책 평가 결과에 따라 허용하거나 거부한다.
  2. IAM의 출발점은 root-only 단일 자격증명의 한계다. 사용자별 권한 분리, 감사 추적, 워크로드 임시 자격증명이 이 문제를 푼다.
  3. 정책 평가는 Explicit Deny > Allow > Implicit Deny 순서이며, SCP와 Permission Boundary 같은 상위 계층은 Allow를 덮을 수 있다.
  4. 서비스와 CI/CD에는 장기 Access Key보다 Role, STS 임시 자격증명, OIDC를 기본값으로 두어 노출창을 줄인다.
  5. AccessDenied를 만나면 권한을 더 붙이기 전에 호출 주체, ARN 매칭, Trust Policy, Boundary, SCP, 리소스 정책을 순서대로 분해한다.