S3 버킷 보안
"S3 버킷의 문은 필요한 만큼만 열어두자"
이 문서는 강사용 답안입니다. 학생에게 공유하지 마세요. 각 단계의 정확한 답과 해설, 트러블슈팅 가이드가 포함되어 있습니다.
Step 1: S3 이미지 버킷 정책 확인
답안
웹 콘솔 방법
- AWS 콘솔에서 S3 서비스로 이동
shopeasy-images-{ACCOUNT_ID}버킷 클릭- 권한 탭 클릭
- 버킷 정책 섹션에서 JSON을 확인
- 다음 항목을 점검:
Action이"s3:GetObject"인지 (읽기만 허용)Resource가"arn:aws:s3:::shopeasy-images-{ACCOUNT_ID}/uploads/*"인지 (/uploads/*로 제한)Principal이"*"인지 (퍼블릭 읽기)
AWS CLI 방법
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
echo "Account ID: $ACCOUNT_ID"
# 이미지 버킷 정책 조회
aws s3api get-bucket-policy \
--bucket shopeasy-images-${ACCOUNT_ID} \
--output text | python3 -m json.tool
정상 출력 예시:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadUploads",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-images-123456789012/uploads/*"
}
]
}
| 항목 | 올바른 값 | 잘못된 값 (보안 위험) |
|---|---|---|
Action |
"s3:GetObject" |
"s3:*" 또는 ["s3:GetObject", "s3:PutObject"] |
Resource |
".../uploads/*" |
".../*" (버킷 전체 공개) |
Effect |
"Allow" |
- |
핵심 점검: Resource 끝부분이 /uploads/*인지 /*인지가 가장 중요합니다.
/*로 되어 있으면 버킷에 저장된 모든 파일(설정 파일, 로그 등)이 퍼블릭으로 노출됩니다.
# 퍼블릭 액세스 차단 설정 확인
aws s3api get-public-access-block \
--bucket shopeasy-images-${ACCOUNT_ID}
# 예상 결과 (4개 모두 false):
# {
# "PublicAccessBlockConfiguration": {
# "BlockPublicAcls": false,
# "IgnorePublicAcls": false,
# "BlockPublicPolicy": false,
# "RestrictPublicBuckets": false
# }
# }
4개 항목이 모두 false인 것이 정상입니다. 이 설정이 있어야 버킷 정책의 퍼블릭 읽기 허용이 작동합니다.
Step 2: S3 이미지 버킷 버전 관리 활성화
답안
웹 콘솔 방법
- S3 →
shopeasy-images-{ACCOUNT_ID}버킷 클릭 - 속성 탭 클릭
- 버킷 버전 관리 섹션으로 스크롤
- 편집 클릭
- 활성화 라디오 버튼 선택
- 변경 사항 저장 클릭
- 버킷 버전 관리 섹션이 "활성화됨"으로 변경된 것을 확인
AWS CLI 방법
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 현재 버전 관리 상태 확인 (비활성화 상태이면 출력 없음)
aws s3api get-bucket-versioning \
--bucket shopeasy-images-${ACCOUNT_ID}
# 버전 관리 활성화
aws s3api put-bucket-versioning \
--bucket shopeasy-images-${ACCOUNT_ID} \
--versioning-configuration Status=Enabled
# 활성화 확인
aws s3api get-bucket-versioning \
--bucket shopeasy-images-${ACCOUNT_ID}
정상 출력 예시:
{
"Status": "Enabled"
}
| 상태 | 의미 | 되돌릴 수 있는가? |
|---|---|---|
| 없음 (출력 없음) | 버전 관리를 한 번도 활성화하지 않은 상태 | - |
| Enabled | 버전 관리 활성화. 모든 객체의 변경/삭제 이력이 보관됨 | Suspended로만 변경 가능 (Disabled 불가) |
| Suspended | 버전 관리 중지. 새 버전은 생성 안 됨, 기존 버전은 유지 | Enabled로 다시 활성화 가능 |
주의: 버전 관리를 한 번 활성화하면 "한 번도 사용 안 한" 상태로는 되돌릴 수 없습니다. Suspended로 중지는 가능하지만, 기존에 생성된 버전들은 그대로 남아있으며 스토리지 비용이 발생합니다.
# 테스트 파일 업로드
echo "version 1" > /tmp/test-version.txt
aws s3 cp /tmp/test-version.txt \
s3://shopeasy-images-${ACCOUNT_ID}/uploads/test-version.txt
# 같은 이름으로 다른 내용 업로드 (덮어쓰기)
echo "version 2" > /tmp/test-version.txt
aws s3 cp /tmp/test-version.txt \
s3://shopeasy-images-${ACCOUNT_ID}/uploads/test-version.txt
# 모든 버전 확인 (Version 1과 Version 2 모두 보임)
aws s3api list-object-versions \
--bucket shopeasy-images-${ACCOUNT_ID} \
--prefix uploads/test-version.txt
# 테스트 파일 정리
aws s3 rm s3://shopeasy-images-${ACCOUNT_ID}/uploads/test-version.txt
list-object-versions 출력에 VersionId가 다른 두 개의 버전이 보이면 성공입니다.
Step 3: S3 프론트엔드 버킷 보안 점검
답안
웹 콘솔 방법
- S3 →
shopeasy-frontend-{ACCOUNT_ID}버킷 클릭 - 권한 탭 클릭
- 버킷 정책 섹션에서 JSON을 확인
- 다음 항목을 점검:
Action이"s3:GetObject"만 포함되어 있는지 확인s3:PutObject,s3:DeleteObject,s3:*등이 없는지 확인Resource가 프론트엔드 버킷의 모든 객체를 대상으로 하는지 확인
AWS CLI 방법
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 프론트엔드 버킷 정책 조회
aws s3api get-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--output text | python3 -m json.tool
정상 출력 예시 (읽기 전용 - 양호):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-frontend-123456789012/*"
}
]
}
| 점검 항목 | 양호 | 위험 |
|---|---|---|
Action |
"s3:GetObject" (읽기만) |
"s3:*" 또는 PutObject/DeleteObject 포함 |
Principal |
"*" (정적 호스팅이므로 정상) |
- |
Resource |
프론트엔드 버킷의 /* |
- |
| Statement 개수 | 1개 (GetObject만) | 여러 개 (불필요한 권한 추가) |
프론트엔드 버킷은 이미지 버킷과 다르게 /*(전체 경로)가 정상입니다.
정적 웹사이트 호스팅 버킷은 모든 HTML/CSS/JS 파일을 브라우저에서 접근해야 하기 때문입니다.
단, Action이 반드시 s3:GetObject(읽기)만이어야 합니다.
프론트엔드 버킷 정책에 s3:PutObject나 s3:*가 포함되어 있다면
즉시 수정해야 합니다. 다음 정책으로 교체하세요:
# 올바른 프론트엔드 버킷 정책 적용 (읽기 전용)
cat > /tmp/frontend-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-frontend-${ACCOUNT_ID}/*"
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--policy file:///tmp/frontend-policy.json
# 변경 확인
aws s3api get-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--output text | python3 -m json.tool
Step 4: S3 액세스 로깅 확인
답안
웹 콘솔 방법 - 현재 상태 확인
- S3 →
shopeasy-images-{ACCOUNT_ID}버킷 클릭 - 속성 탭 클릭
- 서버 액세스 로깅 섹션으로 스크롤
- 현재 상태가 "비활성화됨"인 것을 확인
이번 실습에서는 액세스 로깅의 개념을 이해하는 것이 목표입니다. 활성화하려면 로그 저장용 버킷이 필요합니다.
AWS CLI 방법 - 현재 상태 확인
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
# 이미지 버킷의 로깅 설정 확인
aws s3api get-bucket-logging \
--bucket shopeasy-images-${ACCOUNT_ID}
# 출력이 비어있으면 로깅이 비활성화된 상태
출력이 비어있거나 {}이면 로깅이 비활성화된 상태입니다.
S3 서버 액세스 로깅은 다음과 같은 정보를 기록합니다:
| 로그 필드 | 설명 | 예시 |
|---|---|---|
| Bucket Owner | 버킷 소유자 ID | 79a59df900b949e55d... |
| Bucket | 요청 대상 버킷 | shopeasy-images-123... |
| Time | 요청 시간 | [06/Feb/2025:00:00:38 +0000] |
| Remote IP | 요청자 IP 주소 | 192.0.2.3 |
| Operation | 수행된 작업 | REST.GET.OBJECT |
| Key | 대상 객체 키 | uploads/review-001.jpg |
| HTTP Status | 응답 코드 | 200, 403, 404 |
이 단계는 선택 사항입니다. 액세스 로깅을 활성화하려면 아래 절차를 따릅니다.
# 1. 로깅 전용 버킷 생성
aws s3api create-bucket \
--bucket shopeasy-logs-${ACCOUNT_ID} \
--region ap-northeast-2 \
--create-bucket-configuration LocationConstraint=ap-northeast-2
# 2. 로깅 버킷에 S3 로그 전달 권한 부여
cat > /tmp/log-bucket-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3ServerAccessLogsPolicy",
"Effect": "Allow",
"Principal": {
"Service": "logging.s3.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::shopeasy-logs-${ACCOUNT_ID}/s3-access-logs/*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "${ACCOUNT_ID}"
}
}
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket shopeasy-logs-${ACCOUNT_ID} \
--policy file:///tmp/log-bucket-policy.json
# 3. 이미지 버킷에 액세스 로깅 활성화
aws s3api put-bucket-logging \
--bucket shopeasy-images-${ACCOUNT_ID} \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "shopeasy-logs-'${ACCOUNT_ID}'",
"TargetPrefix": "s3-access-logs/"
}
}'
# 4. 로깅 설정 확인
aws s3api get-bucket-logging \
--bucket shopeasy-images-${ACCOUNT_ID}
참고: 로그가 실제로 기록되기까지 수 분에서 수 시간이 걸릴 수 있습니다. 또한 로그 저장으로 인한 추가 스토리지 비용이 발생합니다.
트러블슈팅 가이드
자주 발생하는 문제와 해결 방법
원인: 버킷에 정책이 설정되어 있지 않음 (Chapter 05에서 설정하지 않았을 가능성)
해결 방법:
- 이미지 버킷에 버킷 정책이 없다면 Chapter 05의 Step 3을 참고하여 정책을 추가합니다
- 아래 CLI로 정책을 적용합니다:
# 이미지 버킷 정책 생성
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
cat > /tmp/images-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadUploads",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-images-${ACCOUNT_ID}/uploads/*"
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket shopeasy-images-${ACCOUNT_ID} \
--policy file:///tmp/images-policy.json
원인: 현재 IAM 사용자/역할에 s3:PutBucketVersioning 권한이 없음
해결 방법:
- IAM 콘솔에서 현재 사용자의 정책 확인
AmazonS3FullAccess정책이 연결되어 있는지 확인- AdministratorAccess가 있다면 권한 문제가 아닐 수 있으므로 버킷 이름을 다시 확인
# 현재 사용자 확인
aws sts get-caller-identity
# 버킷이 실제 존재하는지 확인
aws s3api head-bucket --bucket shopeasy-images-${ACCOUNT_ID}
# 권한 테스트
aws s3api get-bucket-versioning --bucket shopeasy-images-${ACCOUNT_ID}
원인: Chapter 06에서 버킷 정책 설정 시 잘못된 Action을 사용
해결 방법: 올바른 읽기 전용 정책으로 교체합니다
# 올바른 프론트엔드 버킷 정책 적용
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
cat > /tmp/frontend-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shopeasy-frontend-${ACCOUNT_ID}/*"
}
]
}
EOF
aws s3api put-bucket-policy \
--bucket shopeasy-frontend-${ACCOUNT_ID} \
--policy file:///tmp/frontend-policy.json
echo "프론트엔드 버킷 정책이 읽기 전용으로 교체되었습니다."
원인: 버킷 이름이 잘못되었거나, 버킷이 다른 리전에 있음
해결 방법:
# 계정의 모든 S3 버킷 목록 확인
aws s3 ls | grep shopeasy
# 특정 버킷의 리전 확인
aws s3api get-bucket-location \
--bucket shopeasy-images-${ACCOUNT_ID}
# ACCOUNT_ID가 올바른지 확인
echo "ACCOUNT_ID: ${ACCOUNT_ID}"
aws sts get-caller-identity --query Account --output text
원인: 버전 관리가 활성화된 후 삭제된 객체는 "삭제 마커"만 추가되고 이전 버전은 남아있음
해결 방법: 이전 버전을 완전히 삭제하거나 수명 주기 정책을 설정합니다
# 특정 객체의 모든 버전 확인
aws s3api list-object-versions \
--bucket shopeasy-images-${ACCOUNT_ID} \
--prefix uploads/파일명.jpg
# 특정 버전 영구 삭제 (VersionId 필요)
aws s3api delete-object \
--bucket shopeasy-images-${ACCOUNT_ID} \
--key uploads/파일명.jpg \
--version-id "특정_버전_ID"
# (선택) 수명 주기 정책으로 오래된 버전 자동 삭제 설정
cat > /tmp/lifecycle.json << 'EOF'
{
"Rules": [
{
"ID": "DeleteOldVersions",
"Status": "Enabled",
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
},
"Filter": {
"Prefix": ""
}
}
]
}
EOF
aws s3api put-bucket-lifecycle-configuration \
--bucket shopeasy-images-${ACCOUNT_ID} \
--lifecycle-configuration file:///tmp/lifecycle.json
팁: 수명 주기 정책에서 NoncurrentDays: 30은 이전 버전이 30일 후 자동 삭제됨을 의미합니다.
프로덕션에서는 요구사항에 맞게 일수를 조정하세요.
원인: 로깅 대상 버킷이 소스 버킷과 다른 리전에 있거나, 로깅 버킷에 적절한 권한이 없음
해결 방법:
- 로깅 버킷과 소스 버킷이 같은 리전(ap-northeast-2)에 있는지 확인
- 로깅 버킷에 S3 로깅 서비스의
PutObject권한이 있는지 확인 - 로깅 버킷 소유자가 소스 버킷 소유자와 동일한지 확인
# 두 버킷의 리전 확인
aws s3api get-bucket-location --bucket shopeasy-images-${ACCOUNT_ID}
aws s3api get-bucket-location --bucket shopeasy-logs-${ACCOUNT_ID}
# 로깅 버킷 정책 확인
aws s3api get-bucket-policy \
--bucket shopeasy-logs-${ACCOUNT_ID} \
--output text | python3 -m json.tool
- 이미지 버킷 정책의 Resource 경로 혼동 -
/uploads/*와/*의 차이를 정확히 이해하지 못하는 경우가 많습니다. 전체 공개(/*)가 얼마나 위험한지 강조하세요. - 버전 관리를 되돌릴 수 있다고 착각 - 한 번 활성화하면 Disabled로 되돌릴 수 없고 Suspended만 가능하다는 점을 명확히 설명하세요.
- 프론트엔드 버킷의
/*가 보안 문제라고 오해 - 프론트엔드 버킷은 정적 호스팅용이므로 전체 공개(/*)가 정상입니다. 이미지 버킷과 다른 이유를 설명하세요. - 액세스 로깅의 로깅 버킷 리전 불일치 - 소스 버킷과 로깅 버킷이 같은 리전에 있어야 합니다.
- 버전 관리와 비용의 관계 - 버전 관리 활성화 시 모든 버전이 보관되므로 스토리지 비용 증가 가능성을 인지시키세요. 수명 주기 정책과 함께 설명하면 좋습니다.