S3 버킷 정책 작성법: IAM 정책과 뭐가 다를까?
S3 버킷 정책과 IAM 정책의 차이점, JSON 작성법, 크로스 계정 액세스 설정까지 실전 예제로 완벽 정리합니다.
관련 시험 도메인
- Domain 1: Design Secure Architectures
핵심 요약
버킷 정책은 "이 버킷에 누가 접근할 수 있는가"를 정의하고, IAM 정책은 "이 사용자가 무엇을 할 수 있는가"를 정의합니다. 크로스 계정 액세스나 퍼블릭 액세스가 필요하면 버킷 정책을, 내부 사용자 권한 관리는 IAM 정책을 사용하세요.
시험 팁
시험 핵심: 버킷 정책은 Principal(누가) 요소가 필수입니다. IAM 정책에는 Principal이 없습니다(연결된 사용자가 주체). 크로스 계정 액세스 시 버킷 정책 AND 상대방 IAM 정책 둘 다 Allow해야 접근 가능합니다.
| 구분 | 버킷 정책 | IAM 정책 |
|---|---|---|
| 연결 대상 | S3 버킷 (리소스) | IAM 사용자/역할/그룹 |
| Principal 요소 | 필수 (누가 접근하는가) | 없음 (연결된 주체가 암묵적) |
| 퍼블릭 액세스 | 가능 ("Principal": "*") | 불가능 |
| 크로스 계정 | 단독 가능 | 역할 위임 필요 |
| 적용 범위 | 특정 버킷만 | 사용자의 모든 리소스 |
버킷 정책 vs IAM 정책: 언제 무엇을 써야 할까?
버킷 정책을 사용해야 할 때
버킷 정책 사용 케이스:
├── 퍼블릭 웹사이트 호스팅 (모든 사용자 읽기 허용)
├── 크로스 계정 액세스 (다른 AWS 계정에 권한 부여)
├── 특정 IP/VPC에서만 접근 허용
├── CloudFront OAC 연동
└── 서비스 Principal 접근 (CloudTrail, ELB 로그 등)
IAM 정책을 사용해야 할 때
IAM 정책 사용 케이스:
├── 동일 계정 내 사용자/역할 권한 관리
├── 여러 S3 버킷에 대한 권한을 한 곳에서 관리
├── EC2 인스턴스 프로파일로 S3 접근
├── Lambda 함수 실행 역할로 S3 접근
└── 세밀한 사용자별 권한 제어
선택 기준 플로우차트
S3 권한 설정이 필요한가?
│
├── 동일 계정 내부 사용자인가?
│ └── YES → IAM 정책 (+ 필요시 버킷 정책 보완)
│
├── 다른 AWS 계정에서 접근해야 하는가?
│ └── YES → 버킷 정책 필수 (+ 상대방 IAM 정책)
│
├── 퍼블릭 액세스가 필요한가?
│ └── YES → 버킷 정책 필수 (+ Block Public Access OFF)
│
└── AWS 서비스가 접근해야 하는가? (CloudTrail, ELB 등)
└── YES → 버킷 정책 (Service Principal 사용)
버킷 정책 JSON 구조
기본 구조
{
"Version": "2012-10-17",
"Id": "ExampleBucketPolicy",
"Statement": [
{
"Sid": "StatementIdentifier",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucket-name/*",
"Condition": {}
}
]
}
각 요소 설명
| 요소 | 필수 | 설명 |
|---|---|---|
| Version | ✅ | 정책 언어 버전 (항상 "2012-10-17" 사용) |
| Id | ❌ | 정책 식별자 (선택사항) |
| Statement | ✅ | 권한 규칙 배열 (여러 개 가능) |
| Sid | ❌ | 문장 식별자 (가독성용) |
| Effect | ✅ | Allow 또는 Deny |
| Principal | ✅ | 권한 대상 (누가) |
| Action | ✅ | 허용/거부할 작업 (무엇을) |
| Resource | ✅ | 적용 대상 리소스 (어디에) |
| Condition | ❌ | 조건부 적용 (어떤 조건에서) |
Principal 작성법
Principal 유형별 작성 예시
// 1. 모든 사용자 (퍼블릭)
"Principal": "*"
// 2. 특정 AWS 계정
"Principal": {"AWS": "arn:aws:iam::123456789012:root"}
// 3. 특정 IAM 사용자
"Principal": {"AWS": "arn:aws:iam::123456789012:user/username"}
// 4. 특정 IAM 역할
"Principal": {"AWS": "arn:aws:iam::123456789012:role/rolename"}
// 5. AWS 서비스
"Principal": {"Service": "cloudfront.amazonaws.com"}
// 6. 여러 Principal
"Principal": {
"AWS": [
"arn:aws:iam::111111111111:root",
"arn:aws:iam::222222222222:root"
]
}
시험 팁
시험 포인트: "Principal": "*"는 **모든 사용자(익명 포함)**를 의미합니다. "Principal": {"AWS": "*"}는 인증된 모든 AWS 계정을 의미합니다. 둘은 다릅니다!
Action 작성법
자주 사용하는 S3 Action
| Action | 설명 | 적용 대상 |
|---|---|---|
s3:GetObject | 객체 읽기 | 객체 |
s3:PutObject | 객체 업로드 | 객체 |
s3:DeleteObject | 객체 삭제 | 객체 |
s3:ListBucket | 버킷 내 객체 목록 조회 | 버킷 |
s3:GetBucketLocation | 버킷 리전 조회 | 버킷 |
s3:* | 모든 S3 작업 | 전체 |
Action과 Resource 매핑
// 객체 레벨 작업 → Resource에 /* 필요
{
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::bucket-name/*"
}
// 버킷 레벨 작업 → Resource에 버킷만
{
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bucket-name"
}
// 둘 다 필요한 경우 → Resource 배열 사용
{
"Action": ["s3:ListBucket", "s3:GetObject"],
"Resource": [
"arn:aws:s3:::bucket-name",
"arn:aws:s3:::bucket-name/*"
]
}
시험 팁
시험 함정: s3:ListBucket은 버킷에, s3:GetObject는 객체에 적용됩니다. Resource ARN을 잘못 지정하면 권한이 작동하지 않습니다!
실전 버킷 정책 예제
1. 정적 웹사이트 퍼블릭 읽기
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-website-bucket/*"
}
]
}
2. 크로스 계정 액세스 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:root"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::shared-bucket",
"arn:aws:s3:::shared-bucket/*"
]
}
]
}
3. 특정 IP 범위에서만 접근 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IPAllow",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::secure-bucket",
"arn:aws:s3:::secure-bucket/*"
],
"Condition": {
"NotIpAddress": {
"aws:SourceIp": ["192.168.0.0/24", "10.0.0.0/8"]
}
}
}
]
}
4. VPC Endpoint에서만 접근 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VPCEndpointOnly",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::private-bucket",
"arn:aws:s3:::private-bucket/*"
],
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-1234567890abcdef0"
}
}
}
]
}
5. CloudFront OAC 연동
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOAC",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::cdn-bucket/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE"
}
}
}
]
}
6. HTTPS 강제 (HTTP 차단)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyHTTP",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::secure-bucket",
"arn:aws:s3:::secure-bucket/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
Condition 키 활용법
자주 사용하는 Condition 키
| Condition 키 | 용도 | 예시 값 |
|---|---|---|
aws:SourceIp | IP 주소 제한 | "192.168.1.0/24" |
aws:SourceVpce | VPC Endpoint 제한 | "vpce-xxx" |
aws:SourceVpc | VPC 제한 | "vpc-xxx" |
aws:SecureTransport | HTTPS 강제 | "true" / "false" |
s3:x-amz-acl | ACL 조건 | "bucket-owner-full-control" |
s3:prefix | 접두사 제한 | "folder/" |
Condition 연산자
| 연산자 | 설명 |
|---|---|
StringEquals | 문자열 정확히 일치 |
StringNotEquals | 문자열 불일치 |
StringLike | 와일드카드 일치 (*, ?) |
IpAddress | IP 범위 내 |
NotIpAddress | IP 범위 외 |
Bool | Boolean 조건 |
크로스 계정 액세스 설정
양쪽 모두 설정 필요
크로스 계정 액세스는 버킷 소유 계정과 접근 계정 양쪽에서 설정이 필요합니다.
크로스 계정 액세스 설정:
[계정 A: 버킷 소유자]
├── S3 버킷 정책
│ └── Principal: 계정 B 허용
│ └── Action: 필요한 작업 허용
│
[계정 B: 접근자]
├── IAM 정책 (사용자/역할에 연결)
│ └── Resource: 계정 A의 버킷 ARN
│ └── Action: 필요한 작업 허용
예시: 계정 B가 계정 A의 버킷 접근
계정 A (버킷 소유자) - 버킷 정책:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::222222222222:role/DataAccessRole"},
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::account-a-bucket",
"arn:aws:s3:::account-a-bucket/*"
]
}
]
}
계정 B (접근자) - IAM 정책:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::account-a-bucket",
"arn:aws:s3:::account-a-bucket/*"
]
}
]
}
시험 팁
시험 핵심: 크로스 계정 시 버킷 정책 Allow + IAM 정책 Allow = 접근 가능. 둘 중 하나라도 없으면 접근 불가! 단, 동일 계정 내에서는 버킷 정책만으로도 접근 가능합니다.
정책 평가 로직
Allow와 Deny 우선순위
정책 평가 순서:
1. 명시적 Deny가 있으면? → 무조건 거부 (최우선)
2. 명시적 Allow가 있으면? → 허용
3. 둘 다 없으면? → 암묵적 거부 (기본값)
핵심: Deny는 Allow보다 항상 우선!
여러 정책이 있을 때
동일 계정:
├── IAM 정책 Allow + 버킷 정책 없음 → 허용
├── IAM 정책 없음 + 버킷 정책 Allow → 허용
├── IAM 정책 Allow + 버킷 정책 Allow → 허용
└── 어디서든 Deny → 거부
크로스 계정:
├── 버킷 정책 Allow + IAM 정책 Allow → 허용
├── 버킷 정책 Allow + IAM 정책 없음 → 거부
├── 버킷 정책 없음 + IAM 정책 Allow → 거부
└── 둘 다 Allow 필수!
시험 출제 포인트
자주 나오는 문제 유형
| 유형 | 핵심 포인트 |
|---|---|
| Principal 차이 | 버킷 정책에만 Principal 있음, IAM 정책엔 없음 |
| 크로스 계정 | 양쪽 모두 Allow 필요 (버킷 정책 + IAM 정책) |
| Resource ARN | ListBucket은 버킷, GetObject는 객체(/*) |
| HTTPS 강제 | aws:SecureTransport 조건 사용 |
| VPC 제한 | aws:SourceVpce 또는 aws:SourceVpc 사용 |
| Deny 우선 | 명시적 Deny는 모든 Allow보다 우선 |
오답 함정
❌ IAM 정책에 Principal을 지정한다
→ IAM 정책에는 Principal 요소가 없음
❌ s3:GetObject는 버킷 ARN에 적용한다
→ 객체 ARN(버킷/*) 에 적용해야 함
❌ 크로스 계정은 버킷 정책만 있으면 된다
→ 상대방 계정의 IAM 정책도 필요
❌ Allow가 있으면 Deny를 무시한다
→ 명시적 Deny는 항상 최우선
❌ 퍼블릭 액세스는 IAM 정책으로 설정한다
→ 버킷 정책 + Block Public Access OFF 필요
FAQ
Q1: 버킷 정책과 IAM 정책 중 어떤 것을 먼저 확인해야 하나요?
둘 다 확인해야 합니다. 동일 계정에서는 둘 중 하나만 Allow해도 접근 가능하지만, 크로스 계정에서는 양쪽 모두 Allow가 필요합니다. Deny는 어디에 있든 최우선으로 적용됩니다.
Q2: 버킷 정책 크기 제한이 있나요?
20KB까지 작성 가능합니다. 복잡한 정책이 필요하면 IAM 정책과 분리하거나, S3 Access Points를 활용하세요.
Q3: 버킷 정책과 ACL 중 무엇을 써야 하나요?
버킷 정책을 사용하세요. ACL은 레거시이며, 2023년부터 새 버킷은 기본적으로 ACL이 비활성화됩니다. 버킷 정책이 더 세밀하고 관리하기 쉽습니다.
Q4: "Access Denied" 에러가 나는데 원인을 어떻게 찾나요?
- Block Public Access 설정 확인 (퍼블릭 접근 시)
- 버킷 정책의 Principal, Action, Resource 확인
- IAM 정책 확인 (크로스 계정 시 양쪽)
- 명시적 Deny 존재 여부 확인
- VPC Endpoint 정책 확인 (VPC 내부 접근 시)
Q5: 여러 Statement를 하나의 정책에 넣어도 되나요?
네, Statement는 배열이므로 여러 규칙을 하나의 정책에 포함할 수 있습니다. 하지만 가독성과 관리를 위해 Sid로 각 Statement를 명확히 구분하세요.
마무리
S3 버킷 정책은 리소스 기반 권한 관리의 핵심입니다. 핵심 포인트:
- 버킷 정책: "누가 이 버킷에 접근할 수 있는가" (Principal 필수)
- IAM 정책: "이 사용자가 어디에 접근할 수 있는가" (Principal 없음)
- 크로스 계정: 양쪽 모두 Allow 필요
- Deny 우선: 명시적 Deny는 모든 Allow를 무시
- Resource ARN: 버킷 vs 객체 구분 필수
다음으로 S3 암호화 옵션과 IAM 정책 평가 로직을 학습하면 S3 권한 관리를 완성할 수 있습니다.