IAM 권한 구조와 운영 실패 포인트
IAM은 AWS의 모든 접근을 통제하는 권한 시스템이며, 설정 실수는 단순 에러를 넘어 침해의 주요 진입로가 된다. 이 에피소드에서는 User, Role, Policy, 평가 순서, 임시 자격증명, ECS와 CI/CD에서의 흔한 실패를 중심으로 IAM을 정리한다.
Script Companion
오디오와 함께 스크립트 보기
- 01
IAM은 AWS에서 하는 거의 모든 작업 앞에 놓인 권한 검문소다. EC2를 띄우거나, S3에 파일을 올리거나, CLI로 리소스를 조회할 때도 권한이 없으면 작업은 진행되지 않는다. 그래서 IAM 문제는 흔한 Access Denied 에러의 원인이면서, 동시에 클라우드 보안 사고의 출발점이 되기도 한다. 문서의 수치를 기준으로 보면, 클라우드 보안 사고의 약 23%가 misconfiguration에서 비롯되고, 그중 82%는 사람 실수가 원인이다. 또 클라우드 침해의 70% 이상은 손상된 자격증명에서 시작되며, 잘못 설정된 identity policy가 침해의 약 1/3을 차지한다. IAM을 배운다는 것은 권한 에러를 고치는 법만 익히는 것이 아니라, 침해가 시작되는 지점을 줄이는 법을 익히는 일이다.
- 02
IAM의 기본 구성은 User, Group, Role, Policy로 잡을 수 있다. User는 AWS에 로그인하는 사람이고, 콘솔 로그인용 비밀번호와 CLI용 Access Key를 가질 수 있다. Group은 여러 User를 묶어서 개발팀처럼 권한을 한 번에 부여하는 단위다. Role은 사람이 아니라 서비스나 워크로드가 맡는 권한이고, Policy는 실제 허용과 거부 규칙을 담은 JSON 문서다. 다만 사람에게 IAM User를 장기적으로 쓰게 하는 방식은 AWS 공식 권고와 맞지 않는다. 사람은 IAM Identity Center, 구 AWS SSO, 또는 외부 IdP federation을 통해 임시 자격증명을 받는 것이 권장된다. 대신 Identity Center 자체 장애에 대비해 emergency access용 IAM User, 즉 root와 관리자 1명은 유지하라는 권고도 함께 있다.
- 03
Policy에서 가장 먼저 봐야 할 축은 누가, 무엇에, 어떤 작업을 할 수 있는가다. Resource 필드에는 ARN 패턴이 들어가며, S3 버킷 자체와 버킷 안 객체는 서로 다른 대상으로 취급된다. 예를 들어 버킷 안 모든 객체를 가리키는 패턴과 버킷 자체를 가리키는 패턴은 다르기 때문에, ListBucket 같은 버킷 레벨 액션과 GetObject 같은 객체 레벨 액션을 섞어 생각하면 권한이 붙어 있는데도 접근이 안 되는 상황이 생긴다. Resource에 별표를 쓰면 모든 리소스를 뜻하지만, 이것은 서비스에 따라 ARN을 지원하지 않는 액션에서만 필요한 경우가 있다. Condition 키는 특정 조건을 만족할 때만 권한을 적용하게 해 주므로, 단순히 허용하는 것보다 더 좁은 범위로 권한을 설계할 수 있다.
- 04
IAM 정책 평가는 기본적으로 모두 막고, 명시적으로 허용된 것만 통과시키는 구조다. 문서의 비유처럼 IAM은 보안 검문소이고, 기본 규칙은 모두 통과 금지, 즉 Implicit Deny다. 요청이 들어오면 먼저 어느 정책에라도 Explicit Deny가 있는지 확인한다. Deny가 있으면 다른 Allow가 아무리 많아도 즉시 거부된다. Deny가 없고 Allow가 있으면 허용되며, Deny도 Allow도 없으면 기본값인 Implicit Deny로 거부된다. 이 순서가 중요한 이유는 여러 정책이 동시에 적용되기 때문이다. 그룹 Policy, 인라인 Policy, SCP 같은 계층 중 하나라도 Deny를 내면 Allow 10개가 있어도 Deny 1개가 이긴다.
- 05
최소 권한 원칙은 필요한 권한만 필요한 만큼 부여하자는 기준이다. 일단 다 열어 놓고 나중에 줄이자는 방식은 문서가 말하듯 보안 사고의 지름길이다. 실무에서는 IAM Access Analyzer를 사용해 실제로 사용된 권한만 남기고 불필요한 권한을 식별할 수 있다. 하드닝 관점에서는 장기 Access Key를 주기적으로 교체하고, 90일 이상 된 Key는 교체하거나 삭제하는 기준을 둔다. MFA도 기본에 가깝다. IAM User가 비밀번호 하나만으로 콘솔에 로그인할 수 있으면 취약해지므로, 비밀번호와 OTP 앱을 함께 쓰는 다중 인증을 켜야 한다. 조직 단위에서는 SCP로 전체 가드레일을 걸고, 외부 공유 리소스는 Access Analyzer로 탐지한다.
- 06
서비스에는 Access Key를 직접 넣기보다 IAM Role을 붙이는 것이 원칙이다. EC2, ECS, Lambda 같은 워크로드가 AWS 리소스에 접근해야 한다면, 코드나 환경변수에 장기 키를 심는 대신 Role을 통해 임시 자격증명을 받아야 한다. 차이는 노출창의 길이다. 장기 Access Key는 수동 교체 전까지 무기한 유효하고, 공개 GitHub에 유출되면 1분에서 4분 안에 abuse가 시작될 수 있다는 Comparitech honeypot 실험과 Help Net Security 2024 요약이 문서에 나온다. 반면 IAM Role의 임시 자격증명은 기본 1시간, sts:AssumeRole DurationSeconds 기준 최대 12시간이다. 교체도 SDK가 자동으로 처리하므로, 사람 작업이 필요한 키 회수와 배포 과정을 줄일 수 있다.
- 07
Role을 쓸 때는 Permission Policy와 Trust Policy를 구분해야 한다. Permission Policy는 이 Role이 무엇을 할 수 있는지 정하고, Trust Policy는 누가 이 Role을 assume할 수 있는지 정한다. ECS 태스크에 Role을 달았는데도 Access Denied가 난다면, S3나 SQS 권한만 볼 것이 아니라 Trust Policy에 ecs-tasks.amazonaws.com이 Principal로 들어 있는지 확인해야 한다. ECS에서는 Task Role과 Execution Role도 자주 헷갈린다. Task Role, taskRoleArn은 컨테이너 안의 애플리케이션이 S3 파일 업로드나 SQS 메시지 전송처럼 AWS 서비스를 호출할 때 쓰는 권한이다. Execution Role, executionRoleArn은 ECS가 태스크를 시작하기 위해 ECR에서 이미지를 Pull하거나 CloudWatch에 로그를 전송할 때 쓰는 권한이다.
- 08
권한이 붙어 있는데도 동작하지 않는 IAM Silent Failure 패턴은 특히 주의해야 한다. 잘못된 Resource ARN은 대표적이다. s3:GetObject Allow를 버킷 자체 ARN에 붙이면 객체 접근은 실패한다. Trust Policy가 빠진 경우에는 Permission Policy가 있어도 ECS가 Role을 맡을 수 없어 작업 시도 시점에 에러가 난다. SCP 충돌도 흔하다. 개발자 계정의 IAM Policy는 Allow인데 조직 SCP가 해당 리전이나 서비스를 Deny하면 실제 요청은 거부된다. Permission Boundary가 Role에 붙어 있으면 Boundary에 없는 권한은 자동 차단된다. Policy Simulator는 특정 IAM 엔티티가 어떤 작업을 할 수 있는지 실제 요청 없이 확인하는 도구지만, SCP까지 함께 보려면 Simulation Settings에서 Organizations SCP 시뮬레이션을 포함해야 한다.
- 09
잘못 배포된 정책을 되돌리는 장치로 Managed Policy의 versioning도 기억해 둘 만하다. IAM Managed Policy는 최대 5버전을 보관하고, 잘못된 정책이 배포되었을 때 이전 버전으로 즉시 롤백할 수 있다. 다만 5버전 한도를 넘으면 가장 오래된 버전이 자동 삭제되므로, 중요한 버전은 별도 JSON 백업이 권장된다. 이 관점은 다른 권한 시스템으로도 전이된다. Kubernetes RBAC, OAuth 스코프, DB 권한을 만났을 때도 먼저 Subject, Resource, Action, 상위 Deny 레이어로 나누어 보면 진단이 쉬워진다. IAM에서는 User와 Role, S3 ARN과 ECS 서비스, s3:GetObject와 ecs:*가 이 축에 해당한다. K8s RBAC에서는 ServiceAccount, Namespace와 Pod, get과 list와 create가 대응된다.
- 10
프론트엔드 경험과 연결하면 IAM의 위치가 더 분명해진다. localStorage에 role: "admin"을 저장해서 UI 요소를 보여주거나 숨기는 방식은 클라이언트에서만 동작하는 UI 권한이다. 서버는 API 요청이 올 때마다 실제 사용자 권한을 다시 확인해야 한다. IAM은 이 서버 측 권한 확인을 AWS 인프라 전체에 적용한 시스템으로 볼 수 있다. localStorage 값은 클라이언트가 조작할 수 있지만, IAM Role은 AWS가 발급한 임시 자격증명이므로 외부에서 위조할 수 없다. React 빌드 결과물인 bundle.js에 포함된 값은 누구나 볼 수 있다는 점도 중요하다. 그래서 IAM Access Key를 프론트 코드에 넣으면 안 되고, 서버사이드나 워크로드의 임시 자격증명 경로로 다뤄야 한다.
- 11
트러블슈팅에서는 에러 메시지와 권한 계층을 함께 봐야 한다. ECS나 Lambda에서 S3, SQS 접근 중 AccessDeniedException이 나오고, 메시지에 assumed-role 형태가 보이면 먼저 어떤 Role로 실행 중인지 확인한다. 그 Role의 Permissions에 필요한 액션이 Allow되어 있는지 보고, Task Definition에 taskRoleArn이 실제로 설정되어 있는지도 확인한다. Role을 달았는데도 계속 거부되면 Trust Relationships에서 ECS 태스크가 Role을 assume할 수 있는지 봐야 한다. CLI에서 Unable to locate credentials가 나오면 자격증명이 설정되지 않았을 수 있고, ExpiredTokenException이면 STS AssumeRole로 받은 임시 자격증명이 만료되었을 수 있다. Policy Simulator에서는 Allow인데 실제 콘솔이나 API에서는 Denied라면 SCP, Permission Boundary, 리소스 기반 정책의 Deny를 의심한다.
- 12
CI/CD에서 AWS에 접근할 때도 같은 원칙이 적용된다. GitHub Actions나 Jenkins에 장기 Access Key를 저장하는 방식은 자격증명 수명이 무기한이고, 유출되면 발견과 교체 전까지 노출창이 열린다. 문서는 GitHub Secrets에 Access Key를 넣는 방식보다 OIDC, OpenID Connect를 통한 Role Assume이 더 좋다고 정리한다. OIDC와 AssumeRoleWithWebIdentity를 쓰면 자격증명은 워크플로 1회 실행 단위로 발급되고, role-duration-seconds 기준 기본 1시간 안에서 제한된다. 다만 설정 trade-off도 있다. IAM Role과 OIDC Provider를 한 번 구성해야 하고, GitHub OIDC 토큰의 sub, repository, ref 클레임을 Trust Policy Condition에 정확히 명시하지 않으면 다른 repo가 같은 Role을 assume하는 사고가 가능하다.
- 13
정리하면 IAM은 User나 Role에 Policy를 붙이는 단순한 메뉴가 아니라, Explicit Deny, Allow, Implicit Deny 순서로 요청을 판정하는 권한 평가 시스템이다. 사람은 IAM Identity Center나 federation을 통해 임시 자격증명을 쓰고, 서비스와 CI/CD는 장기 Access Key보다 Role 기반 임시 자격증명을 쓰는 쪽이 노출창을 줄인다. Access Denied가 날 때는 Permission Policy만 보지 말고 Trust Policy, SCP, Permission Boundary, 리소스 기반 정책까지 같은 흐름 안에서 확인해야 한다. 2025년 7월부터 IAM Identity Center의 CloudTrail 이벤트 구조가 바뀌어 userName과 principalId 대신 userId와 Identity Store ARN이 기록된다는 점도 운영 알림이나 SIEM 쿼리에서 함께 점검해야 한다.
같은 레이어
L3에서 이어 듣기
- 원본 문서
- content/topics/L3/iam.md
- 오디오 파일
- /podcasts/l3-iam.mp3