콘텐츠로 이동

IAM

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

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

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

User (사용자)

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

⚠️ 2025년 AWS 공식 권고: AWS는 이제 사람(Human User)에 대해서는 IAM User 대신 **IAM Identity Center(SSO)**를 사용하도록 권고한다. IAM Identity Center는 임시 자격증명을 자동 발급하므로 장기 Access Key 노출 위험이 없다. 팀 규모가 커지면 반드시 검토할 것.

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을 부여한다.

Access Key는 코드나 설정 파일에 노출될 위험이 있고, 유출 시 즉시 교체가 어렵다.

Role은 AWS가 임시 자격증명을 자동 발급/갱신하므로 더 안전하다.

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

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가 있으면 즉시 검토
  • 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)

🔧 “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 명령어에 사용

🔧 “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 연동이 있다면 이 변경에 맞게 쿼리를 업데이트해야 한다.