학습 목표

상황

사내 이미지 변환 서비스가 Amazon EC2 Auto Scaling 그룹에서 12대로 운영되고 있습니다. 각 인스턴스는 원본을 Amazon S3에서 읽고 변환 결과를 다른 버킷에 씁니다. 2년 전 첫 배포 때 담당자가 IAM 사용자를 하나 만들고 액세스 키를 발급해 AMI 안의 공유 자격 증명 파일(~/.aws/credentials)에 적어 두었습니다. 그 뒤로 배포는 이 AMI를 복제하는 방식으로 이어졌습니다.

문제는 그 IAM 사용자에게 AmazonS3FullAccess가 붙어 있고, 같은 키가 다음 네 곳에 동시에 존재한다는 점입니다.

이번 주 AWS CloudTrail 이벤트에서 그 액세스 키 ID로 평소 쓰지 않는 리전의 s3:ListAllMyBuckets 호출이 관측되었습니다. 보안 담당자는 즉시 키를 비활성화하려 하지만, 키를 끄는 순간 운영 중인 12대의 변환 작업이 전부 실패한다는 것도 알고 있습니다.

장기 액세스 키의 확산 경로와 임시 자격 증명의 차단 지점 위쪽은 인스턴스에 장기 액세스 키를 파일로 심어 둔 경우입니다. 키가 담긴 인스턴스에서 네 갈래로 복사본이 번집니다. AMI 를 떠서 만든 다른 인스턴스, 소스 저장소에 함께 올라간 커밋, 애플리케이션 로그와 환경 변수 덤프, 그리고 볼륨 스냅샷과 백업입니다. 네 갈래를 모으는 오른쪽 상자가 결과입니다. 복사본 하나하나가 새로운 출발점이 되므로 어디까지 퍼졌는지 셀 수 없고, 되돌릴 회수 지점 자체가 사라집니다. 그 지점을 가위표로 표시했습니다. 아래쪽은 같은 인스턴스가 IAM 역할과 인스턴스 프로파일을 쓰는 경우입니다. 인스턴스에는 키 파일이 없고, 필요할 때마다 IMDSv2 를 통해 만료 시각이 있는 임시 자격 증명을 받습니다. 복사되더라도 곧 무효가 되므로 확산 경로 자체가 성립하지 않습니다. 이 사고의 본질은 키가 유출된 것이 아니라 회수할 수 없는 자격 증명을 처음부터 썼다는 데 있습니다. 장기 액세스 키를 인스턴스에 심은 경우 EC2 인스턴스 .aws/credentials AMI → 다른 인스턴스 소스 저장소 커밋 로그 · 환경 변수 덤프 볼륨 스냅샷 · 백업 회수 지점이 사라집니다 복사본마다 새 출발점이 되고 어디까지 퍼졌는지 모릅니다 IAM 역할과 인스턴스 프로파일을 쓴 경우 EC2 인스턴스 키 파일이 없습니다 인스턴스 프로파일 IMDSv2 로 발급 임시 자격 증명 만료 시각이 붙어 있습니다 복사돼도 곧 무효가 됩니다 본질은 키가 유출된 것이 아니라 회수할 수 없는 자격 증명을 썼다는 데 있습니다 역할이 발급하는 임시 자격 증명은 확산 경로를 원천에서 끊습니다
인스턴스에 심긴 장기 액세스 키가 AMI 복제, 저장소 커밋, 로그 캡처를 통해 네 곳으로 번지면서 회수 지점이 사라지는 확산 경로와, 인스턴스 프로파일로 발급되는 임시 자격 증명이 그 확산을 원천에서 끊는 지점의 대비

요구사항과 제약

이 케이스가 만족시켜야 하는 요구사항과 제약
구분 내용 설계에 주는 함의
요구 1 유출된 자격 증명을 확실히 무효화할 수 있어야 한다 회수 지점이 한 곳으로 모여야 한다. 복사본이 여러 곳에 있으면 회수가 성립하지 않는다
요구 2 앞으로 같은 사고가 재발하지 않아야 한다 사람이 키를 옮기는 절차가 남아 있으면 재발한다. 절차 자체를 없애야 한다
요구 3 누가 무엇을 호출했는지 추적할 수 있어야 한다 공유 자격 증명 하나로는 주체를 구분할 수 없다
제약 1 애플리케이션 코드 변경을 최소화한다 SDK의 기본 자격 증명 체인을 그대로 쓰는 방법이 유리하다
제약 2 전환 중 변환 작업이 중단되면 안 된다 키 비활성화가 전환의 첫 단계가 될 수 없다. 순서가 중요하다
제약 3 권한은 필요한 버킷과 동작으로 제한한다 전환하는 김에 AmazonS3FullAccess를 좁힌다

배경 개념은 2장 IAM과 자격 증명에 있습니다. 정책 평가 로직과 역할의 두 축(신뢰 정책과 권한 정책)을 먼저 확인하면 아래 비교가 훨씬 빨리 읽힙니다.

후보 설계안 비교

현장에서 실제로 제안되는 안은 넷입니다. 네 안 모두 "보안을 개선한다"고 주장하지만, 요구 1(확실한 무효화)을 통과하는 것은 하나뿐입니다.

후보 설계안 4종 비교 — 판정 기준은 요구 1~3과 제약 1~3
항목 A · 키 유지 + 정기 교체 자동화 B · AWS Secrets Manager에 키 저장 후 조회 C · IAM 역할 + 인스턴스 프로파일 D · IAM Identity Center 도입
자격 증명 수명 교체 주기까지 유효한 장기 키 여전히 장기 키. 저장 위치만 바뀜 만료되는 임시 자격 증명 사람 세션은 임시. 기계 자격 증명은 그대로
유출 시 회수 불가 — 복사본을 셀 수 없다 부분 — 조회용 자격 증명이 또 필요하다 역할 신뢰 정책과 권한만 바꾸면 즉시 해당 없음 — 이 문제를 다루지 않는다
부트스트랩 문제 없음(이미 심어 둠) 발생 — 비밀을 읽을 자격 증명을 다시 인스턴스에 줘야 한다 없음 — 인스턴스 메타데이터가 대신한다 해당 없음
코드 변경 없음 조회 코드 추가 필요 없음 — 기본 자격 증명 체인이 처리 없음
운영 부담 교체 파이프라인을 계속 유지해야 한다 비밀 관리와 조회 권한을 함께 관리 가장 낮음 — 교체를 AWS가 처리 사용자 수명주기 관리가 새로 생김
주체 추적 공유 키 하나 — 구분 불가 공유 키 하나 — 구분 불가 역할 세션 단위로 구분 사람 단위 구분(기계는 미해결)
판정 오답 — 문제를 미룰 뿐 오답 — 이 시나리오에서는 과잉이자 미해결 선택 범위가 다름 — 별도로 추진

B안이 왜 정답이 아닌가

AWS Secrets Manager는 좋은 서비스이고 자동 교체까지 지원합니다. 그러나 이 시나리오에서는 부트스트랩 문제를 만듭니다. 인스턴스가 비밀을 읽으려면 그 자체로 자격 증명이 필요한데, 그 자격 증명을 인스턴스에 주는 올바른 방법이 결국 IAM 역할입니다. 역할을 붙일 수 있다면 처음부터 S3 권한을 역할에 주면 되고, 중간에 액세스 키를 둘 이유가 사라집니다.

Secrets Manager가 정답이 되는 자리는 따로 있습니다 — AWS가 발급하지 않는 자격 증명, 예를 들어 데이터베이스 사용자 비밀번호나 외부 서비스의 API 키입니다. 그 구분은 16장 보안 · 암호화 · 거버넌스에서 다룹니다.

선택과 근거

C안 — IAM 역할을 만들어 인스턴스 프로파일로 연결합니다. 근거는 세 가지입니다.

전환 순서 — 순서를 틀리면 서비스가 멈춘다

제약 2(무중단) 때문에 순서가 설계의 일부입니다. 키 비활성화는 마지막이고, 자격 증명 파일 제거는 역할 연결 다음입니다.

무중단 전환 순서와 각 단계의 확인 지점
순서 작업 무엇을 확인하고 다음으로 넘어가는가
1 필요한 버킷과 동작만 허용하는 권한 정책으로 역할을 만든다 정책 시뮬레이터나 스테이징에서 필요한 동작이 전부 허용되는지
2 인스턴스 프로파일을 만들어 역할을 넣고, 실행 중인 인스턴스에 연결한다 인스턴스 안에서 역할 자격 증명이 조회되는지
3 AMI와 인스턴스에서 공유 자격 증명 파일과 환경 변수를 제거한다 호출 주체가 IAM 사용자가 아니라 수임한 역할로 바뀌었는지
4 시작 템플릿을 새 AMI로 갱신하고 인스턴스를 교체한다 새로 뜬 인스턴스가 키 없이 정상 동작하는지
5 액세스 키를 먼저 비활성화하고, 관측 기간을 둔 뒤 삭제한다 비활성화 후에도 실패한 호출이 없는지 CloudTrail로 확인
verify-identity.sh — 인스턴스가 무엇으로 호출하고 있는지 확인 (리소스 값은 예시입니다)
# 전환 전: IAM 사용자 ARN 이 나온다 (아직 파일의 키를 쓰고 있다)
# 전환 후: assumed-role 형태의 ARN 이 나와야 한다
aws sts get-caller-identity --query 'Arn' --output text
# arn:aws:sts::123456789012:assumed-role/ImageWorkerRole/i-0123456789abcdef0

역할과 인스턴스 프로파일 구성

trust-policy.json — EC2 서비스만 이 역할을 맡을 수 있게 하는 신뢰 정책
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "ec2.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}
create-role.sh — 역할 생성과 인스턴스 프로파일 연결 (계정 ID와 리소스 ID는 예시입니다)
aws iam create-role \
  --role-name ImageWorkerRole \
  --assume-role-policy-document file://trust-policy.json

aws iam put-role-policy \
  --role-name ImageWorkerRole \
  --policy-name ImageBucketAccess \
  --policy-document file://permissions-policy.json

aws iam create-instance-profile \
  --instance-profile-name ImageWorkerProfile

aws iam add-role-to-instance-profile \
  --instance-profile-name ImageWorkerProfile \
  --role-name ImageWorkerRole

# 실행 중인 인스턴스에 연결합니다. 재부팅은 필요하지 않습니다.
aws ec2 associate-iam-instance-profile \
  --instance-id i-0123456789abcdef0 \
  --iam-instance-profile Name=ImageWorkerProfile

권한 정책은 요구된 버킷과 동작만 남깁니다. AmazonS3FullAccess를 그대로 옮겨 붙이면 사고 범위가 그대로 유지됩니다.

permissions-policy.json — 원본 읽기와 결과 쓰기만 허용 (버킷 이름은 예시입니다)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadSourceObjects",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::example-image-source/*"
    },
    {
      "Sid": "WriteDerivedObjects",
      "Effect": "Allow",
      "Action": ["s3:PutObject"],
      "Resource": "arn:aws:s3:::example-image-derived/*"
    }
  ]
}

메타데이터 경로 잠그기 — IMDSv2 강제

역할로 바꾸면 자격 증명이 인스턴스 메타데이터 서비스(IMDS)를 통해 전달됩니다. 즉 메타데이터 엔드포인트가 새로운 회수 대상이 됩니다. 애플리케이션에 서버 측 요청 위조(SSRF) 취약점이 있으면 공격자가 애플리케이션을 시켜 메타데이터를 읽게 만들 수 있기 때문입니다.

IMDSv2는 먼저 PUT으로 세션 토큰을 받고, 그 토큰을 헤더에 실은 요청만 응답하는 방식입니다. 단순히 URL을 대신 호출하게 만드는 전형적인 SSRF로는 토큰 발급 단계를 통과할 수 없습니다. 여기에 홉 제한을 1로 두면 컨테이너처럼 한 단계 더 떨어진 곳에서의 접근도 막힙니다.

enforce-imdsv2.sh — 인스턴스 단위 강제와 계정 기본값 설정 (인스턴스 ID는 예시입니다)
# 개별 인스턴스에 IMDSv2 를 강제합니다.
aws ec2 modify-instance-metadata-options \
  --instance-id i-0123456789abcdef0 \
  --http-tokens required \
  --http-endpoint enabled \
  --http-put-response-hop-limit 1

# 이후 시작되는 인스턴스의 계정 수준 기본값으로 IMDSv2 를 요구합니다.
aws ec2 modify-instance-metadata-defaults \
  --http-tokens required

비용과 운영에 미치는 영향

이 전환은 아키텍처를 바꾸지 않으므로 컴퓨팅이나 스토리지 사용량은 그대로입니다. 달라지는 것은 운영 항목의 개수입니다.

전환 전후의 비용 항목과 운영 항목 변화 — 금액이 아니라 항목의 증감입니다
항목 전환 전 전환 후
추가되는 과금 리소스 없음 없음 — 역할과 인스턴스 프로파일은 리소스를 새로 띄우지 않는다
B안을 골랐다면 — 비밀 저장과 조회 API 호출이 새로운 과금 항목이 된다. 얻는 것 없이 항목만 늘어난다
정기 작업 키 교체, 배포 파이프라인 갱신, 교체 실패 대응 없음 — 자격 증명 교체를 AWS가 처리한다
사고 대응 시간 복사본 추적에 상한이 없다 역할 권한 변경 한 번으로 종료된다
감사 공유 키 하나 — 주체 구분 불가 역할 세션 이름으로 인스턴스까지 좁혀진다

예방 체크리스트

새 워크로드를 설계할 때와, 기존 워크로드를 점검할 때 각각 확인합니다. 체크 상태는 저장되지 않으므로 점검용으로만 씁니다.

설계할 때

점검할 때

audit-keys.sh — 액세스 키 보유 사용자와 키 상태 확인
# 계정의 모든 IAM 사용자에 대해 액세스 키와 생성 시각, 상태를 나열합니다.
for u in $(aws iam list-users --query 'Users[].UserName' --output text); do
  aws iam list-access-keys --user-name "$u" \
    --query 'AccessKeyMetadata[].[UserName,AccessKeyId,Status,CreateDate]' \
    --output text
done

# 회수는 삭제가 아니라 비활성화부터 시작합니다.
aws iam update-access-key \
  --user-name image-worker \
  --access-key-id AKIAEXAMPLEEXAMPLE \
  --status Inactive

관련 시험 포인트

이 케이스에서 뽑아내는 문항 판별 규칙
문제문의 신호 고르는 것 소거하는 것
EC2가 다른 AWS 서비스를 호출해야 한다 IAM 역할 + 인스턴스 프로파일 액세스 키를 사용자 데이터·환경 변수·AMI에 넣는 안
교차 계정으로 접근해야 한다 대상 계정에 역할을 만들고 sts:AssumeRole 대상 계정의 IAM 사용자 키를 발급해 전달하는 안
자격 증명을 주기적으로 교체해야 한다 가능하면 역할. 데이터베이스 비밀번호 등은 Secrets Manager 자체 교체 스크립트를 만드는 안(운영 부담 한정어와 충돌)
SSRF로 메타데이터가 노출된다 IMDSv2 필수화, 홉 제한 조정 메타데이터 엔드포인트를 보안 그룹으로 차단하는 안(링크 로컬 주소라 성립하지 않는다)
모바일 앱이 AWS 리소스를 직접 호출해야 한다 Amazon Cognito 자격 증명 풀로 임시 자격 증명 교환 앱에 액세스 키를 포함하는 안

한정어별 선호 선택지는 한정어 키워드 사전에, 헷갈리는 쌍은 함정 사전에 모여 있습니다.

이어서 볼 곳

공식 문서 출처