콘텐츠로 이동

IAM

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

AWS IAM은 “누가(사용자/서비스) AWS의 어떤 리소스에 어떤 작업을 할 수 있는지”를 관리하는 서비스이다.

AWS에서 하는 모든 작업은 IAM 권한을 거친다. EC2를 띄우든, S3에 파일을 올리든, 권한이 없으면 불가능하다. 권한 문제는 가장 흔한 AWS 에러 원인 중 하나이고, 클라우드 보안 사고의 상당 부분이 IAM 설정 실수에서 시작한다.

정량적 근거 (출처 명시):

  • 클라우드 보안 사고의 약 23%가 misconfiguration에 기인하며, 그 중 misconfiguration 사례의 82%가 사람 실수가 원인이다 (Exabeam Cloud Security Statistics 2025 집계).
  • **클라우드 침해의 70% 이상이 손상된 자격증명(compromised identity)**에서 시작되며, 잘못 설정된 identity policy가 클라우드 침해의 약 1/3을 차지한다 (Cloud Security Alliance, Top Threats to Cloud Computing 2025).
  • 한 번의 misconfiguration 사고당 평균 손해는 약 $3.86M으로 추정된다 (2025년 IBM/CSA 합산 기준, Fidelis Security 요약).

즉 IAM 잘못 설정은 단순한 “에러 메시지” 수준이 아니라 침해 통계의 핵심 진입로다. “보안 사고의 대부분”이라는 막연한 주장 대신 위 수치를 기준으로 트레이드오프를 판단해야 한다.

2.5 IAM은 왜 등장했나 — root credential 단일 키의 한계

섹션 제목: “2.5 IAM은 왜 등장했나 — root credential 단일 키의 한계”

IAM은 2010년 9월 preview, 2011년 5월 GA로 출시됐다 (AWS — Happy 10th Birthday IAM). 그 이전 약 5년간(2006년 S3 출시 ~ 2011년) AWS 계정에 접근하는 유일한 수단은 계정 소유자(root)의 이메일/패스워드 + 단일 Access Key 1쌍이었다. lineage_oneliner의 “모든 AWS 작업에 권한 필요”가 가리키는 출발선이 바로 이 단일 키 구조다.

선행 방식(root-only)의 한계 — 정량/구조적 증거:

  • 권한 세분화 0: 10명의 개발자가 한 계정을 쓰면 전원이 동일한 root 자격증명 공유 → “S3 read만 가능한 인턴” 같은 분리 자체가 표현 불가능. 권한은 전부 또는 무 두 상태뿐.
  • 이탈 시 비용 폭증: 직원 1명 퇴사 시 root 패스워드 + Access Key를 교체하면 모든 사용처(개발자 노트북, CI, 운영 서버)에 동기 배포 필요. 사람 1명 이벤트가 조직 전체의 무중단 키 교체 작업으로 증폭.
  • 감사 추적 불가: CloudTrail이 호출을 기록해도 모든 이벤트의 userIdentity가 동일한 root 계정 → “어제 S3 버킷을 지운 사람이 누구인가”에 영구 답할 수 없음. 사고 발생 시 책임 소재 식별 자체가 봉쇄됨.

IAM이 도입한 해결 메커니즘과 본문 연결점:

  • User/Group/Role로 정체성 분리 → 본문 §3 “User”, “Group”, “Role” 정의가 이 분리의 직접 산물.
  • Policy JSON으로 (Action × Resource × Condition) 차원 분해 → §3의 “IAM 정책 JSON 실습”이 보여주는 ARN 와일드카드·Condition 키가 root-only에서는 표현조차 불가능했던 축이다.
  • CloudTrail의 userIdentity.arn호출자 식별 → §6.5 “This policy would have no effect” 트러블슈팅에서 CloudTrail로 거부 이벤트를 찾을 수 있는 전제 조건.
  • 후속 단계인 IAM Identity Center 권고(§3 상단)는 같은 lineage의 다음 layer다 — IAM User가 푼 “공유 root” 문제를 한 칸 더 밀어, “장기 Access Key 보유 자체”의 노출창을 임시 자격증명으로 좁힌다. §3 “Access Key vs Role” 표의 노출창 수치가 이 진화의 정량적 근거다.

요약: 이 토픽이 사라지면 무엇이 깨지나 — 사용자별 권한 분리, 감사 추적, 워크로드 임시 자격증명(STS/Role), Identity Center·OIDC federation까지의 모든 후속 메커니즘이 동시에 무너진다. IAM은 “AWS에서 누구라는 개념을 만든” 레이어다.

User (사용자)

AWS에 로그인하는 사람. 각 개발자마다 IAM User가 있다. 콘솔 로그인용 비밀번호 + CLI용 Access Key를 가진다.

⚠️ AWS 공식 권고: 사람(Human User)에 대해서는 IAM User 대신 IAM Identity Center(구 AWS SSO) 또는 외부 IdP federation으로 임시 자격증명을 발급받도록 권고한다. 원문은 “Require human users to use federation with an identity provider to access AWS using temporary credentials” (AWS IAM Best Practices 공식 문서). IAM Identity Center는 SAML/OIDC IdP와 결합해 콘솔·CLI 자격증명을 모두 임시화하므로 장기 Access Key 노출 위험이 없다. 단 AWS 공식 가이드는 emergency access용 IAM User(root + 관리자 1명)는 유지할 것을 함께 권고한다 — Identity Center 자체 장애에 대비.

Group (그룹)

User를 묶은 단위. “개발팀” 그룹에 권한을 부여하면, 그룹 내 모든 User가 그 권한을 갖는다.

Role (역할)

사람이 아닌 “서비스”에 부여하는 권한. ECS 태스크가 S3에 접근해야 한다면, 그 태스크에 IAM Role을 부여한다.

Policy (정책)

실제 권한 규칙을 정의한 JSON 문서.

{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}

IAM 정책 JSON 실습 — Resource ARN 패턴과 Condition 키

Resource 필드에는 와일드카드(*)를 활용해 ARN 범위를 정밀하게 지정할 수 있다.

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

ARN 패턴 규칙:

  • arn:aws:s3:::my-bucket/* — 버킷 내 모든 객체 (버킷 자체 제외)
  • arn:aws:s3:::my-bucket — 버킷 자체만 (ListBucket 등 버킷 레벨 액션에 필요)
  • arn:aws:ecs:ap-northeast-2:123456789:service/my-cluster/* — 특정 클러스터의 모든 서비스
  • "Resource": "*" — 모든 리소스 (서비스에 따라 ARN 미지원 액션에만 사용)

Condition 키를 사용하면 특정 조건을 만족할 때만 권한을 적용할 수 있다. 주요 Condition 연산자와 글로벌 키:

{
"Condition": {
"StringEquals": { "aws:RequestedRegion": "ap-northeast-2" }, // 특정 리전만 허용
"Bool": { "aws:MultiFactorAuthPresent": "true" }, // MFA 인증된 경우만
"IpAddress": { "aws:SourceIp": ["203.0.113.0/24"] }, // 특정 IP 대역만
"DateLessThan": { "aws:CurrentTime": "2026-12-31T23:59:59Z" } // 기간 제한
}
}

IAM 정책 평가 로직 — 왜 이렇게 동작하는가

비유: IAM은 “보안 검문소”이다. 검문소의 기본 규칙은 **모두 통과 금지(Implicit Deny)**이다. 통과하려면 명시적인 허가(Allow)가 필요하다. 단, 누군가 “이 사람은 통과 금지”라고 명시(Explicit Deny)했으면, 아무리 허가증이 있어도 막힌다.

AWS가 요청을 평가하는 순서:

  1. Explicit Deny 확인: 어느 정책에라도 Deny가 있으면 즉시 거부 (다른 Allow가 있어도 무시)
  2. Allow 확인: Deny가 없고 Allow가 있으면 허용
  3. Implicit Deny: Deny도 Allow도 없으면 거부 (기본값)
요청 → [Explicit Deny?] YES → 거부
↓ NO
[Allow 있음?] YES → 허용
↓ NO
Implicit Deny → 거부

이 규칙이 중요한 이유: 여러 정책이 동시에 적용될 때(그룹 Policy + 인라인 Policy + SCP 등), 어느 하나에서라도 Deny가 나오면 무조건 거부된다. Allow가 10개 있어도 Deny 1개에 진다.

📖 더 보기: AWS IAM Policy Evaluation Logic 공식 문서 — Explicit Deny, Allow, Implicit Deny의 평가 순서와 교차 계정 시나리오 설명

최소 권한 원칙 (Least Privilege)

필요한 권한만 딱 그만큼만 부여한다. “일단 다 열어놓고 나중에 줄이자”는 보안 사고의 지름길.

실무 적용: AWS 콘솔에서 IAM Access Analyzer를 사용하면, 실제로 사용된 권한만 남기고 불필요한 권한을 자동으로 식별할 수 있다.

경로: IAM → Access Analyzer → Unused access
→ 90일간 사용되지 않은 권한 목록을 자동으로 제안

Access Key vs Role — 서비스에서는 Role을 써야 한다

EC2, ECS, Lambda 같은 서비스에는 Access Key를 직접 넣지 말고 IAM Role을 부여한다.

❌ 나쁜 예: ECS Task 환경변수에 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 직접 설정
✅ 좋은 예: Task Definition에 taskRoleArn 설정 → AWS SDK가 자동으로 임시 자격증명 획득

왜 “더 안전”인지 — 정량 trade-off

“Role이 더 안전하다”는 정성적 주장에 그치지 않고, 유출 시 실제로 어떤 차이가 나는지 수치로 본다.

항목장기 Access Key (IAM User)IAM Role (임시 자격증명)
유효 기간무기한 (수동 교체까지)기본 1시간, 최대 12시간 (sts:AssumeRole DurationSeconds)
공개 GitHub 유출 시 abuse 시작 시간1~4분 — 자동 스캐너가 commit 직후 키를 수집·시도 (Comparitech honeypot 실험, Help Net Security 2024)동일 시점에 유출돼도 만료까지의 시간만 유효 — 대개 분 단위 → 시간 단위 잔여
AWS quarantine policy 자동 적용 시점유출 후 약 2분 이내 GitHub 공개 저장소 한정 (Pawel Rzepa 분석) — abuse 속도가 더 빠를 수 있음quarantine 대상 아님 — 만료 자체로 차단
교체(rotation) 비용Key 재발급 → 모든 사용처(.env, CI/CD secret, SDK 설정)에 배포 → 무중단 보장 위해 두 키 동시 활성 후 회수 → 사람 작업 단위(분~시간)자동 갱신 — SDK가 백그라운드에서 처리, 운영 개입 0
유출 시 MTTR키 비활성화는 즉시지만, 실제로 어떤 키가 어디 노출됐는지 식별 + 의존 서비스 영향 파악에 보통 수십 분~수 시간”Role 자체 무력화” 사례는 드물고, 영향 범위가 컴퓨트 인스턴스 단위로 국한

핵심: “안전” = 노출창(window of exposure)이 짧다. Access Key는 노출과 교체 사이가 사람의 반응속도이지만, IAM Role은 길어야 12시간 안에 자동 만료된다. AWS 공식 best practice도 이 차이 때문에 “workloads should use temporary credentials with IAM roles”를 명시한다 (AWS IAM Best Practices).

⚠️ 위 14분 수치는 공개 GitHub repo 기준이다. private repo·내부 wiki·로그 파일에서의 유출은 탐지가 더 늦으므로 노출창이 며칠수개월로 길어질 수 있다. 그래도 결론은 같다 — 장기 키는 노출창 최소화가 본질적으로 불가능.

Trust Policy — Role을 누가 assume할 수 있는지 정의한다

Role에는 권한 규칙(Permission Policy)과, 누가 이 Role을 맡아도 되는지(Trust Policy)가 함께 필요하다.

ECS Task에 Role을 부여할 때의 Trust Policy 예시:

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

ECS 태스크에 Role을 달았는데 여전히 Access Denied라면 Trust Policy를 먼저 확인하자.

ECS Task Role vs Execution Role 구분

ECS에는 두 가지 Role이 있어 자주 혼동된다:

Role목적예시
Task Role (taskRoleArn)컨테이너 안의 애플리케이션이 AWS 서비스에 접근할 때NestJS 코드에서 S3 파일 업로드, SQS 메시지 전송
Execution Role (executionRoleArn)ECS가 태스크를 시작하기 위한 권한ECR에서 이미지 Pull, CloudWatch에 로그 전송

📖 더 보기: Amazon ECS Task IAM Role 공식 문서 — Task Role과 Execution Role의 차이 및 설정 방법

IAM Policy Simulator로 권한 테스트

AWS 콘솔에서 “특정 IAM 엔티티가 이 작업을 할 수 있는가”를 실제 요청 없이 시뮬레이션할 수 있다.

경로: IAM → Policy Simulator (https://policysim.aws.amazon.com/)
사용법: IAM User/Role 선택 → 서비스 선택 → 작업 선택 → Run Simulation

MFA(다중 인증) — 계정 보안의 기본

IAM User는 Password 하나만으로 콘솔에 로그인할 수 있어 보안 취약점이 된다. MFA를 활성화하면 비밀번호 + OTP 앱(Google Authenticator 등)의 2단계 인증이 필요해진다.

설정 경로: IAM → Users → <내 이름> → Security credentials → Assign MFA device
권고: Root 계정과 모든 콘솔 접근 IAM User에 MFA 필수 적용

IAM 보안 하드닝 — 2025년 주요 권고사항

  1. Access Key 주기적 교체 및 미사용 Key 삭제

    장기 Access Key는 유출될수록 위험도가 올라간다. 90일 이상 된 Key는 교체하거나 삭제한다.

    Terminal window
    # 현재 모든 Access Key와 마지막 사용일 확인
    aws iam list-users --query 'Users[].UserName' --output text | \
    xargs -I{} aws iam list-access-keys --user-name {} \
    --query 'AccessKeyMetadata[].{User:`{}`,KeyId:AccessKeyId,Status:Status,Created:CreateDate}'
    # IAM Access Analyzer로 미사용 키 자동 탐지
    # 경로: IAM → Access Analyzer → Findings → Unused access
  2. AWS Organizations SCP로 조직 전체 가드레일 설정

    비유: SCP(Service Control Policy)는 “건물 전체에 적용되는 마스터 키 제한”이다. 아무리 개별 IAM Policy에서 허용해도, SCP에서 금지된 작업은 불가능하다.

    // 예: 특정 리전 외 리소스 생성 금지 SCP
    {
    "Effect": "Deny",
    "Action": "*",
    "Resource": "*",
    "Condition": {
    "StringNotEquals": {
    "aws:RequestedRegion": ["ap-northeast-2", "us-east-1"]
    }
    }
    }
  3. IAM Access Analyzer로 외부 공유 리소스 탐지

    누군가 실수로 IAM Role을 외부 계정에 공유하거나, S3 버킷을 퍼블릭으로 열었을 때 자동으로 탐지한다.

    경로: IAM → Access Analyzer → Findings
    → "External access" 타입의 Findings가 있으면 즉시 검토

IAM Silent Failure — 에러 없이 권한이 잘못 적용되는 패턴

섹션 제목: “IAM Silent Failure — 에러 없이 권한이 잘못 적용되는 패턴”

명시적 에러가 없는데 의도한 동작이 안 되는 경우가 IAM에서 가장 위험한 패턴이다.

유형발생 시나리오감지 방법
잘못된 Resource ARNs3:GetObject Allow를 arn:aws:s3:::bucket (버킷 자체)에 붙임 → 객체 접근 불가. 에러는 AccessDenied지만 정책은 “붙어 있음”Policy Simulator에서 실제 객체 ARN(arn:aws:s3:::bucket/key)으로 시뮬레이션
Trust Policy 누락Permission Policy는 있는데 Trust Policy에 Principal 미등록 → ECS가 Role을 assume 불가. 태스크 시작은 성공, 작업 시도 시에야 에러aws iam get-role --role-name <name> --query Role.AssumeRolePolicyDocument
SCP 충돌개발자 계정의 IAM Policy는 Allow인데 조직 SCP가 해당 리전/서비스를 Deny → Policy Simulator가 “허용”으로 오인Policy Simulator → Simulation Settings → “Simulate Organizations SCP” 체크
Permission BoundaryRole에 Boundary 설정 시 Boundary에 없는 권한은 자동 차단. 개발자가 Boundary 존재 자체를 모를 때 발생aws iam get-role --role-name <name> --query Role.PermissionsBoundary

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

IAM Managed Policy는 최대 5버전을 보관한다. 잘못된 정책을 배포했을 때 이전 버전으로 즉시 롤백 가능하다.

Terminal window
# 현재 정책의 모든 버전 조회
aws iam list-policy-versions \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--query 'Versions[*].{Version:VersionId,IsDefault:IsDefaultVersion,Created:CreateDate}'
# 예상 출력:
# [
# { "Version": "v3", "IsDefault": true, "Created": "2026-04-29" }, ← 현재 (잘못됨)
# { "Version": "v2", "IsDefault": false, "Created": "2026-04-28" }, ← 이전 정상 버전
# { "Version": "v1", "IsDefault": false, "Created": "2026-04-01" }
# ]
# 이전 버전으로 즉시 롤백
aws iam set-default-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--version-id v2
# → 반영 즉시 (30초 내). ECS Task는 다음 자격증명 갱신 시 새 권한 적용
# 잘못된 버전 삭제 (기본 버전은 삭제 불가, 비기본 버전만)
aws iam delete-policy-version \
--policy-arn arn:aws:iam::123456789012:policy/MyPolicy \
--version-id v3

운영 주의: 5버전 한도 초과 시 가장 오래된 버전이 자동 삭제된다. 중요한 버전은 따로 JSON 백업 권장.

새 권한/인증 시스템을 만났을 때 — 전이 분석 체크리스트

IAM에서 배운 원칙은 다른 권한 시스템(Kubernetes RBAC, OAuth 스코프, DB 권한 등)에 그대로 이식된다.

질문IAM 예시K8s RBACPostgreSQL
1. 누가 접근하는가? (Subject)IAM User/RoleServiceAccountDB User/Role
2. 무엇에 접근하는가? (Resource)S3 ARN, ECS 서비스Namespace, PodTable, Schema
3. 무엇을 할 수 있는가? (Action)s3:GetObject, ecs:*get, list, createSELECT, INSERT
4. 기본값이 허용인가 거부인가?Implicit DenyImplicit Deny부여된 권한만 허용
5. 덮어쓰기 가능한 상위 제어가 있는가?SCP > Boundary > PolicyPodSecurityPolicy, OPA슈퍼유저, Row-Level Security

공통 원칙: 어떤 권한 시스템이든 (a) Subject → (b) Resource → (c) Action → (d) 상위 Deny 레이어 4축으로 분해하면 “왜 안 되는지”를 진단할 수 있다.

📖 참고: AWS IAM Policy Versioning 공식 문서

  • AWS 콘솔/CLI 접근 권한 관리
  • 서비스(ECS, Lambda 등)가 다른 AWS 리소스에 접근할 때
  • CI/CD 파이프라인이 AWS에 배포할 때 사용하는 권한
  • 팀원 온보딩/오프보딩 시 권한 부여/회수
  • “Access Denied” 에러 대응 시 IAM 정책 확인이 필요
  • 새로운 서비스 배포 시 적절한 Role 생성/부여
  • 팀원의 AWS 접근 권한 관리
  • 보안 감사 시 IAM 설정 점검
개념 A개념 B차이점
UserRoleUser는 사람에게 부여, Role은 서비스에 부여(사람도 임시 사용 가능)
PolicyRolePolicy는 “권한 규칙”, Role은 “Policy를 가진 역할”
인라인 Policy관리형 Policy인라인은 하나의 User/Role에 직접 붙임, 관리형은 재사용 가능한 독립 Policy
Root AccountIAM UserRoot는 모든 권한을 가진 최상위 계정, IAM User는 제한된 권한의 개별 계정
IAM UserIAM Identity CenterUser는 장기 자격증명(Access Key), Identity Center는 임시 자격증명(SSO)

언제 무엇을 쓸 것인가 — AWS 공식 decision guide 흐름

AWS 공식 Choosing an AWS identity service (2025-08-15 갱신)는 신규 IAM User를 일반 인간 접근에 만들지 말 것을 권고한다. 결정 흐름:

  1. 사람(workforce)이 콘솔/CLI에 접근IAM Identity Center 또는 외부 IdP federation. SAML/OIDC IdP를 이미 운영 중이라면 IAM Identity Center로 통합, 단일 워크포스 디렉터리에서 다중 계정 권한 일괄 프로비저닝.
  2. 워크로드(ECS/EC2/Lambda)가 AWS API 호출IAM Role (Task Role, Instance Profile, Lambda execution role). Access Key 임베드 금지.
  3. GitHub Actions·Vercel 등 외부 클라우드/SaaS가 AWS 호출sts:AssumeRoleWithWebIdentity + OIDC. 본문 §6.5 “CI/CD 파이프라인” 예시가 여기에 해당.
  4. 온프레미스 서버가 AWS 호출IAM Roles Anywhere (X.509 인증서 기반).
  5. Emergency access 안전망 → root 사용자 + 1명의 관리자 IAM User만 유지하고 MFA·물리 키 강제. Identity Center 자체 장애 대비 백업 경로.

선택 기준의 두 축은 자격증명 수명식별 단위다. 수명이 길수록(장기 Access Key) 노출창이 길어지고, 식별 단위가 거칠수록(공유 계정) 감사 추적이 끊긴다. 위 1~5번은 두 축을 동시에 좁히는 우선순위 순서다 — §3 “Access Key vs Role” 표의 노출창 수치가 이 우선순위를 정량으로 뒷받침한다.

언제 IAM User가 여전히 합당한가 (역명제):

  • Identity Center 미배포 + IdP 미보유 + 단일 계정 환경의 emergency break-glass 사용자 (위 5번)
  • AWS와 통신하는 외부 시스템이 OIDC/SAML/IAM Roles Anywhere를 전혀 지원하지 못하고, 워크플로 1회용 인증조차 불가능한 경우 (드묾, 그래도 90일 rotation 강제)
  • 그 외 일반 개발자/운영자 계정에는 IAM User 생성을 default로 거절하는 것이 2025년 AWS 공식 입장

🔧 “AccessDeniedException” — ECS/Lambda에서 S3, SQS 등 접근 거부

섹션 제목: “🔧 “AccessDeniedException” — ECS/Lambda에서 S3, SQS 등 접근 거부”

증상: 애플리케이션 로그에 AccessDeniedException: User: arn:aws:sts::123456789:assumed-role/... 에러가 찍힘

원인: ECS Task Role에 해당 작업의 Allow 정책이 없거나, Task Role 자체가 설정되지 않음

해결:

  1. 에러 메시지에서 assumed-role/<Role이름> 부분 확인
  2. IAM → Roles → 해당 Role → Permissions 탭에서 필요한 액션이 Allow되어 있는지 확인
  3. 없으면 정책 추가 (예: s3:PutObject가 빠진 경우)
  4. Task Definition에 taskRoleArn이 설정되어 있는지 확인 (미설정 시 애플리케이션에서 AWS SDK가 권한 없음)

🔧 “Role을 달았는데 여전히 Access Denied” — Trust Policy 누락

섹션 제목: “🔧 “Role을 달았는데 여전히 Access Denied” — Trust Policy 누락”

증상: Task Role에 모든 Permission Policy를 붙였는데도 ECS Task가 시작 시 또는 실행 중에 AccessDenied 발생

원인: IAM Role의 Trust Policy에 ecs-tasks.amazonaws.com이 Principal로 없어서 ECS가 해당 Role을 assume(맡기) 불가

해결:

  1. IAM → Roles → 해당 Role → Trust Relationships 탭 클릭
  2. Principal이 "Service": "ecs-tasks.amazonaws.com"인지 확인
  3. 없으면 Edit trust policy에서 아래 내용 추가:
    {
    "Effect": "Allow",
    "Principal": { "Service": "ecs-tasks.amazonaws.com" },
    "Action": "sts:AssumeRole"
    }

🔧 CLI에서 “Unable to locate credentials” 또는 만료된 임시 자격증명

섹션 제목: “🔧 CLI에서 “Unable to locate credentials” 또는 만료된 임시 자격증명”

증상: aws s3 ls 등 CLI 명령 실행 시 Unable to locate credentials 또는 ExpiredTokenException 에러

원인 1: ~/.aws/credentials 또는 환경변수에 Access Key가 설정되지 않음

원인 2: STS AssumeRole로 발급한 임시 자격증명이 만료됨 (기본 1시간)

해결:

Terminal window
# 현재 자격증명 확인
aws sts get-caller-identity
# 출력 예시 (정상):
# {
# "UserId": "AIDAXXXXXXXXXXXXXXXXX",
# "Account": "123456789012",
# "Arn": "arn:aws:iam::123456789012:user/my-user"
# }
# 에러 시: 프로필 재설정
aws configure --profile my-profile

🔧 CI/CD 파이프라인에서 AWS 권한 에러

섹션 제목: “🔧 CI/CD 파이프라인에서 AWS 권한 에러”

증상: GitHub Actions 또는 Jenkins에서 aws 명령어 실행 시 Unable to locate credentials 또는 권한 에러 발생

원인: CI/CD 파이프라인에 AWS 자격증명이 설정되지 않았거나, IAM 권한이 부족함

해결: Access Key를 GitHub Secrets에 저장하는 방식보다 OIDC(OpenID Connect)를 통한 Role Assume이 보안상 더 좋다.

# GitHub Actions에서 OIDC로 AWS Role Assume (Access Key 없이)
- 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
# → 임시 자격증명을 자동으로 발급받아 후속 aws 명령어에 사용

OIDC vs GitHub Secrets — 왜 더 좋은가 (정량 비교)

항목GitHub Secrets에 Access Key 저장OIDC + AssumeRoleWithWebIdentity
자격증명 수명무기한 (수동 rotate까지)워크플로 1회 실행 단위 (role-duration-seconds, 기본 1시간)
유출 시 abuse 노출창발견·교체 전까지 무제한 — 공개 노출이면 분 단위 안에 abuse 시작만료까지 잔여 시간 (최악 ≤ 1시간)
교체 비용Repo별/조직별 Secret 갱신, 파이프라인 재실행 검증 필요 → 사람 작업 수십 분갱신 자체가 워크플로마다 자동 — 운영 개입 0
MTTR (탐지 후 봉쇄)Secret 삭제 + 키 비활성화 + CloudTrail 영향 조사 (보통 분~시간)Role의 trust policy sub 조건만 좁히면 즉시 차단 — 컴퓨트 재배포 불필요
상호 신뢰 모델”GitHub이 secret을 새지 않는다”는 단일 가정AWS가 매번 GitHub의 OIDC JWT 서명을 검증 — 신뢰가 토큰 단위로 짧음

trade-off도 있다: OIDC는 한 번의 IAM Role + OIDC Provider 설정이 필요하고, GitHub OIDC 토큰 클레임(sub, repository, ref)을 trust policy Condition에 정확히 명시하지 않으면 다른 repo가 같은 Role을 assume하는 사고가 가능하다. AWS 공식 가이드는 “use temporary credentials”를 워크로드 best practice 원칙으로 못박는다 (AWS IAM Best Practices — workloads use roles).

🔧 “This policy would have no effect” — 의도한 대로 권한이 안 먹히는 경우

섹션 제목: “🔧 “This policy would have no effect” — 의도한 대로 권한이 안 먹히는 경우”

증상: IAM Policy를 붙였는데 실제로 작동하지 않음. Policy Simulator에서는 Allow인데 콘솔/API에서는 Denied

원인 1: SCP(Service Control Policy)가 조직 레벨에서 해당 작업을 Deny하고 있음

원인 2: 권한 경계(Permissions Boundary)가 Policy의 범위를 제한하고 있음

원인 3: 리소스 기반 정책(Bucket Policy, SQS Policy 등)에서 해당 Role을 Deny하고 있음

해결:

Terminal window
# 1. IAM Policy Simulator에서 SCP 포함 시뮬레이션
# 경로: https://policysim.aws.amazon.com/
# → "Simulation Settings"에서 "Simulate Organizations SCP" 체크
# 2. AWS CloudTrail에서 실제 거부된 이벤트 확인
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
--start-time "2024-01-15T09:00:00Z" \
--query 'Events[?contains(CloudTrailEvent, `AccessDenied`)].{Time:EventTime,Event:CloudTrailEvent}'
# 3. IAM Access Analyzer로 정책 검증
aws accessanalyzer validate-policy \
--policy-document file://my-policy.json \
--policy-type IDENTITY_POLICY
# → 정책 문법 오류나 잠재적 문제를 자동으로 탐지
  • User, Group, Role, Policy의 차이를 설명할 수 있다
  • 최소 권한 원칙이 뭔지 설명할 수 있다
  • “Access Denied” 에러가 나면 어디를 확인해야 하는지 안다
  • 서비스에 Role을 부여하는 이유를 설명할 수 있다
  • ECS/EC2/Lambda에서 Access Key 대신 Role을 써야 하는 이유를 설명할 수 있다
  • Explicit Deny가 Allow보다 우선한다는 것을 설명할 수 있다

AWS STS, AssumeRole, MFA 적용, IAM Policy Simulator, Service Control Policy(SCP), Cross-Account Access, IAM Identity Center, OIDC

  • AWS 콘솔에서 내 IAM User의 권한 확인
    경로: IAM → Users → <내 이름> → Permissions 탭
    확인: 어떤 그룹에 속해있고 어떤 정책이 붙어있는지
  • 팀에서 사용 중인 IAM Role 목록 훑어보기
    경로: IAM → Roles → 검색창에 서비스명 입력 (예: "ecs", "lambda")
  • IAM Policy Simulator로 특정 작업의 허용/거부 테스트
  • aws sts get-caller-identity로 현재 내 자격증명 확인
    Terminal window
    aws sts get-caller-identity
    # 예상 출력:
    # {
    # "UserId": "AIDAXXXXXXXXXXXXXXXXX",
    # "Account": "123456789012",
    # "Arn": "arn:aws:iam::123456789012:user/my-user"
    # }
  • IAM Access Analyzer에서 Unused access Findings 확인
    경로: IAM → Access Analyzer → Findings → Unused access 탭
    확인: 90일 이상 사용하지 않은 권한/Access Key 목록
  1. IAM은 AWS의 모든 접근을 통제하는 핵심이며, 보안 사고의 대부분은 IAM 설정 실수에서 시작된다
  2. User(사람) → Role(서비스) → Policy(규칙) 조합으로 권한을 관리하되, 사람은 IAM Identity Center(SSO)로 관리하는 것이 2025년 AWS 공식 권고이다
  3. 최소 권한 원칙 — Access Analyzer의 Unused Access 기능으로 90일 미사용 권한을 자동 탐지해 정기 정리한다
  4. “Access Denied”가 나면 정책 평가 순서(Explicit Deny → Allow → Implicit Deny)를 따라 CloudTrail + Policy Simulator로 추적한다
  5. CI/CD에서 AWS 접근은 Access Key 대신 OIDC(GitHub Actions) 또는 Instance Profile(EC2/ECS)을 통한 임시 자격증명을 사용한다

localStorage 권한 vs IAM Role — 왜 비슷한가

프론트엔드 개발 시 사용자 권한을 관리하는 방식을 생각해보자. 예를 들어 localStorage에 role: "admin"을 저장해서 UI 요소를 보여주고 숨기는 방식이 있다. 하지만 이건 클라이언트에서만 동작하는 “UI 권한”이고, 서버는 API 요청이 올 때마다 실제로 사용자 권한을 다시 확인한다.

IAM은 바로 이 “서버가 권한을 확인하는 시스템”을 AWS 인프라 전체에 적용한 것이다:

[프론트엔드 권한 흐름]
사용자 로그인 → JWT 토큰 발급 → API 요청마다 토큰 검증
서버: "이 토큰의 역할이 admin인가?"
[AWS IAM 권한 흐름]
ECS Task 시작 → IAM Role 부여 → AWS API 요청마다 자격증명 검증
AWS: "이 Role이 s3:GetObject를 허용받았는가?"

핵심 차이: localStorage는 클라이언트가 조작할 수 있지만, IAM Role은 AWS가 발급한 임시 자격증명이라 외부에서 위조할 수 없다.

“이 API Key를 React 코드에 넣으면 안 된다”의 이유

React 빌드 결과물(bundle.js)에 포함된 값은 누구나 볼 수 있다. 이것이 IAM Access Key를 서버사이드에만 두고, 프론트에는 절대 내려주면 안 되는 이유다:

❌ 잘못된 패턴:
.env → REACT_APP_AWS_ACCESS_KEY=AKIA... → bundle.js에 포함
브라우저 개발자 도구 → Sources → bundle.js → Access Key 노출!
✅ 올바른 패턴:
NestJS 서버 → IAM Role (ECS Task Role)
NestJS 코드 → AWS SDK → Role 기반 임시 자격증명 자동 사용
프론트 → NestJS API (인증 토큰만) → NestJS가 AWS 서비스 대신 호출

실무 아키텍처 패턴 — IAM 거버넌스 계층

섹션 제목: “실무 아키텍처 패턴 — IAM 거버넌스 계층”
조직 전체 가드레일
└─ SCP (Service Control Policy) — 금지 리전, 금지 서비스 등 조직 전체 상한선
└─ 계정 레벨
└─ IAM Permission Boundary — 개발자가 부여할 수 있는 권한 상한선
└─ IAM Role (Task/EC2/Lambda별)
└─ IAM Policy — 실제 허용/거부 규칙

어느 레이어에서든 Deny가 나오면 무조건 거부다. “IAM Policy는 있는데 왜 안 되지?”라는 상황은 위 계층에서 막히고 있는 것이다.

2025년 변경사항: 2025년 7월부터 IAM Identity Center의 CloudTrail 이벤트 구조가 변경됐다. userName/principalId 필드 대신 userId와 Identity Store ARN이 기록된다. CloudTrail 기반 알림이나 SIEM 연동이 있다면 이 변경에 맞게 쿼리를 업데이트해야 한다.