Claude Code × AWS IAM 가이드: 최소 권한 정책을 안전하게 설계하기
Claude Code로 AWS IAM 최소 권한을 설계하고 Access Analyzer와 CDK로 검증하는 실전 가이드.
AWS IAM은 “누가 어떤 AWS 리소스에 무엇을 할 수 있는지”를 정하는 권한 체계입니다. 정책은 작은 JSON 파일처럼 보이지만 Action: "*"나 Resource: "*"를 쉽게 허용하면 Lambda, CI 작업, 사람 계정이 필요한 범위를 훨씬 넘는 권한을 갖게 됩니다. Claude Code는 IAM 정책 초안과 리뷰에 유용하지만, 검증 없이 승인하는 도구가 되어서는 안 됩니다.
이 글은 실무에서 쓰는 절차를 정리합니다. 먼저 유스케이스를 문장으로 고정하고, Claude Code로 초안을 만들고, IAM Access Analyzer로 검증하고, 사람이 ARN과 업무 범위를 확인한 뒤, CDK로 코드화하고, 허용되어야 하는 작업과 거부되어야 하는 작업을 모두 테스트합니다. 초보자 관점에서는 정책은 권한 규칙, 역할은 워크로드가 임시로 맡는 신분, 주체는 권한을 사용하는 사용자나 서비스라고 보면 됩니다.
Masa는 운영 Lambda에 임시로 넓은 S3 권한을 붙였다가 몇 주 뒤 리뷰에서 발견한 경험이 있습니다. 유출은 없었지만, 그 뒤로 “Claude Code는 초안을 빠르게 만들 뿐, 책임은 사람이 진다”는 규칙을 적용하고 있습니다.
AWS 공식 문서를 기준으로 삼기
이 글의 기준은 다음 AWS 공식 문서입니다.
- Security best practices in IAM
- IAM JSON policy element reference
- Validate policies with IAM Access Analyzer
- Temporary security credentials in IAM
- Permissions boundaries for IAM entities
핵심은 명확합니다. 사람은 장기 액세스 키보다 페더레이션과 임시 자격 증명을 쓰고, 워크로드는 IAM 역할을 사용하고, 권한은 최소화하고, Access Analyzer로 정책을 검증하고, 쓰지 않는 사용자, 역할, 권한, 자격 증명을 정기적으로 제거합니다.
flowchart LR
A["유스케이스 작성"] --> B["Claude Code 초안"]
B --> C["사람이 ARN 확인"]
C --> D["IAM Access Analyzer 검증"]
D --> E["CDK 구현"]
E --> F["허용/거부 테스트"]
먼저 구체적인 유스케이스를 정한다
Claude Code에 “IAM 정책을 작성해줘”라고만 요청하지 마세요. 실행 주체, 리소스, 허용할 작업, 금지할 작업을 함께 줘야 합니다.
| 유스케이스 | 주체 | 필요한 권한 | 명확히 피할 권한 |
|---|---|---|---|
| 썸네일 생성 Lambda | Lambda 실행 역할 | 특정 S3 prefix 읽기/쓰기, DynamoDB 쓰기, SNS 게시, 로그 쓰기 | S3 삭제, 전체 버킷 읽기, IAM 작업 |
| 관리자 업로드 보조 | API용 Lambda 역할 | 특정 S3 prefix PutObject, DynamoDB GetItem | 버킷 전체 List, KMS 키 관리 |
| GitHub Actions 배포 | OIDC CI 역할 | 하나의 CloudFormation 스택과 대상 Lambda 업데이트 | AdministratorAccess, 모든 리전 작업 |
| 장애 조사 읽기 전용 역할 | 페더레이션 사용자 | CloudWatch Logs 검색, X-Ray 조회, 관련 테이블 GetItem | 업데이트, 삭제, Secret 읽기 |
특히 “피할 권한”을 적는 것이 중요합니다. 생성 도구는 필요한 권한은 잘 찾지만, 금지 경계는 명시해야 정확해집니다.
바로 쓸 수 있는 Lambda 실행 정책
아래 정책은 S3에서 이미지를 읽고, 다른 S3 prefix에 썸네일을 저장하고, DynamoDB에 메타데이터를 쓰고, 실패 시 SNS로 알림을 보내며, CloudWatch Logs에 로그를 남기는 Lambda용입니다. 로그 그룹은 인프라 코드로 미리 만든다고 가정하므로 실행 역할에 logs:CreateLogGroup을 주지 않습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadSourceImages",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::user-uploads-prod/incoming/*"
},
{
"Sid": "WriteThumbnails",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::user-thumbnails-prod/thumbnails/*",
"Condition": {
"StringEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
},
{
"Sid": "WriteMetadata",
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/image-metadata"
},
{
"Sid": "PublishAlerts",
"Effect": "Allow",
"Action": ["sns:Publish"],
"Resource": "arn:aws:sns:ap-northeast-1:123456789012:alert-topic"
},
{
"Sid": "WriteLambdaLogs",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/image-worker-prod:*"
}
]
}
policy-lambda-image-worker.json으로 저장한 뒤 실행합니다.
aws accessanalyzer validate-policy \
--policy-document file://policy-lambda-image-worker.json \
--policy-type IDENTITY_POLICY \
--query "findings[?findingType!='SUGGESTION']"
그다음 Claude Code에 구조화된 리뷰를 시킵니다.
claude -p "policy-lambda-image-worker.json을 AWS IAM 최소 권한 정책으로 리뷰해 주세요.
전제: Lambda는 S3 incoming/을 읽고 thumbnails/에 쓰며, DynamoDB image-metadata에 쓰고, SNS alert-topic에 게시하고, CloudWatch Logs에 로그를 씁니다.
확인: 와일드카드, 삭제 권한, 넓은 Resource, Condition 타당성, 로그 권한 범위.
Sid / 위험 / 이유 / 더 안전한 수정안 표로 출력해 주세요."
Access Analyzer는 문법, ARN, 작업 이름, 조건 키, 보안 발견 사항을 확인합니다. 하지만 이 권한이 비즈니스에 정말 필요한지는 알 수 없습니다. 그 판단은 엔지니어가 해야 합니다.
CDK로 역할을 코드화한다
콘솔에서 수동으로 고치면 리뷰된 상태가 남지 않습니다. TypeScript CDK를 사용하면 역할, 로그 그룹, 정책을 같은 코드 변경 리뷰에서 확인할 수 있습니다. 아래는lib/image-worker-iam-stack.ts예시입니다.
import * as cdk from "aws-cdk-lib";
import { Stack, StackProps, aws_iam as iam, aws_logs as logs } from "aws-cdk-lib";
import { Construct } from "constructs";
export class ImageWorkerIamStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const account = Stack.of(this).account;
const region = Stack.of(this).region;
new logs.LogGroup(this, "ImageWorkerLogGroup", {
logGroupName: "/aws/lambda/image-worker-prod",
retention: logs.RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
const role = new iam.Role(this, "ImageWorkerRole", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
description: "Execution role for image-worker-prod Lambda",
});
role.addToPolicy(new iam.PolicyStatement({
sid: "ReadSourceImages",
actions: ["s3:GetObject"],
resources: ["arn:aws:s3:::user-uploads-prod/incoming/*"],
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "WriteThumbnails",
actions: ["s3:PutObject"],
resources: ["arn:aws:s3:::user-thumbnails-prod/thumbnails/*"],
conditions: {
StringEquals: {
"s3:x-amz-server-side-encryption": "AES256",
},
},
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "WriteMetadataAndAlerts",
actions: ["dynamodb:PutItem", "dynamodb:UpdateItem", "sns:Publish"],
resources: [
`arn:aws:dynamodb:${region}:${account}:table/image-metadata`,
`arn:aws:sns:${region}:${account}:alert-topic`,
],
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "WriteLambdaLogs",
actions: ["logs:CreateLogStream", "logs:PutLogEvents"],
resources: [
`arn:aws:logs:${region}:${account}:log-group:/aws/lambda/image-worker-prod:*`,
],
}));
}
}
CI는 장기 키 대신 OIDC를 쓴다
CI/CD에서 Claude Code를 활용하더라도 저장소 Secret에 장기 AWS 액세스 키를 넣는 설계는 피해야 합니다. GitHub Actions는 OIDC로 IAM 역할을 맡아 임시 자격 증명을 받을 수 있습니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:example-org/example-repo:ref:refs/heads/main"
}
}
}
]
}
이것은 신뢰 정책입니다. 누가 역할을 맡을 수 있는지 정합니다. 권한 정책은 역할을 맡은 뒤 무엇을 할 수 있는지 정합니다. 두 파일은 분리해서 리뷰해야 합니다.
리뷰에서 잡아야 할 실패 사례
첫째, 임시 관리자 권한이 그대로 남는 경우입니다. 긴급하게 넓은 권한이 필요했다면 담당자, 만료일, CloudTrail 확인, Access Analyzer 재실행을 티켓에 남깁니다.
둘째, S3 ARN을 섞는 경우입니다. s3:ListBucket은arn:aws:s3:::bucket-name을 쓰고, s3:GetObject와s3:PutObject는arn:aws:s3:::bucket-name/prefix/*를 씁니다.
셋째, Lambda 실행 역할에 IP 조건을 무리하게 넣는 경우입니다. aws:SourceIp는 사람의 접근이나 공개 API에는 유용할 수 있지만, AWS 서비스 간 호출에서는 기대와 다르게 동작할 수 있습니다.
넷째, 성공 경로만 테스트하는 경우입니다. s3:DeleteObject, 관련 없는 버킷, 관련 없는 DynamoDB 테이블, IAM 작업이 거부되는지도 확인해야 합니다.
이어서 읽기와 다음 단계
Lambda 역할은 AWS Lambda 가이드, S3 prefix 설계는 AWS S3 입문, 로그 감사는 AWS CloudWatch 가이드, 전체 보안 점검은 Claude Code 보안 모범 사례와 함께 보세요.
팀에서 Claude Code, AWS IAM, CDK, CI 권한 리뷰를 표준화하려면 교육 및 도입 상담이 현실적인 출발점입니다. 개인 학습은 무료 치트시트로 시작하고, 반복해서 쓸 템플릿은 제품 목록에서 고르면 됩니다.
이 글의 절차를 실제로 적용해 보니, 먼저 유스케이스 표와 금지할 작업을 쓰는 것만으로 Claude Code의 초안 품질이 좋아졌습니다. 특히 넓은 S3 권한과 불필요한 로그 와일드카드를 배포 전에 발견하기 쉬워졌고, Access Analyzer와 거부 테스트가 리뷰의 증거가 되었습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.