Chapter 09 - 강사용 답안

S3 버킷 보안

"S3 버킷의 문은 필요한 만큼만 열어두자"

강사 전용 문서

이 문서는 강사용 답안입니다. 학생에게 공유하지 마세요. 각 단계의 정확한 답과 해설, 트러블슈팅 가이드가 포함되어 있습니다.

Step 1: S3 이미지 버킷 정책 확인

답안

웹 콘솔 방법

  1. AWS 콘솔에서 S3 서비스로 이동
  2. shopeasy-images-{ACCOUNT_ID} 버킷 클릭
  3. 권한 탭 클릭
  4. 버킷 정책 섹션에서 JSON을 확인
  5. 다음 항목을 점검:
    • Action"s3:GetObject"인지 (읽기만 허용)
    • Resource"arn:aws:s3:::shopeasy-images-{ACCOUNT_ID}/uploads/*"인지 (/uploads/*로 제한)
    • Principal"*"인지 (퍼블릭 읽기)

AWS CLI 방법

bash
# 계정 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

정상 출력 예시:

json
{
    "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/*인지 /*인지가 가장 중요합니다. /*로 되어 있으면 버킷에 저장된 모든 파일(설정 파일, 로그 등)이 퍼블릭으로 노출됩니다.

추가 확인: 퍼블릭 액세스 차단 상태
bash
# 퍼블릭 액세스 차단 설정 확인
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 이미지 버킷 버전 관리 활성화

답안

웹 콘솔 방법

  1. S3 → shopeasy-images-{ACCOUNT_ID} 버킷 클릭
  2. 속성 탭 클릭
  3. 버킷 버전 관리 섹션으로 스크롤
  4. 편집 클릭
  5. 활성화 라디오 버튼 선택
  6. 변경 사항 저장 클릭
  7. 버킷 버전 관리 섹션이 "활성화됨"으로 변경된 것을 확인

AWS CLI 방법

bash
# 계정 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}

정상 출력 예시:

json
{
    "Status": "Enabled"
}
해설: 버전 관리 상태 변화
상태 의미 되돌릴 수 있는가?
없음 (출력 없음) 버전 관리를 한 번도 활성화하지 않은 상태 -
Enabled 버전 관리 활성화. 모든 객체의 변경/삭제 이력이 보관됨 Suspended로만 변경 가능 (Disabled 불가)
Suspended 버전 관리 중지. 새 버전은 생성 안 됨, 기존 버전은 유지 Enabled로 다시 활성화 가능

주의: 버전 관리를 한 번 활성화하면 "한 번도 사용 안 한" 상태로는 되돌릴 수 없습니다. Suspended로 중지는 가능하지만, 기존에 생성된 버전들은 그대로 남아있으며 스토리지 비용이 발생합니다.

버전 관리 동작 테스트 (선택)
bash
# 테스트 파일 업로드
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 프론트엔드 버킷 보안 점검

답안

웹 콘솔 방법

  1. S3 → shopeasy-frontend-{ACCOUNT_ID} 버킷 클릭
  2. 권한 탭 클릭
  3. 버킷 정책 섹션에서 JSON을 확인
  4. 다음 항목을 점검:
    • Action"s3:GetObject"만 포함되어 있는지 확인
    • s3:PutObject, s3:DeleteObject, s3:* 등이 없는지 확인
    • Resource가 프론트엔드 버킷의 모든 객체를 대상으로 하는지 확인

AWS CLI 방법

bash
# 계정 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

정상 출력 예시 (읽기 전용 - 양호):

json
{
    "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:PutObjects3:*가 포함되어 있다면 즉시 수정해야 합니다. 다음 정책으로 교체하세요:

bash
# 올바른 프론트엔드 버킷 정책 적용 (읽기 전용)
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 액세스 로깅 확인

답안

웹 콘솔 방법 - 현재 상태 확인

  1. S3 → shopeasy-images-{ACCOUNT_ID} 버킷 클릭
  2. 속성 탭 클릭
  3. 서버 액세스 로깅 섹션으로 스크롤
  4. 현재 상태가 "비활성화됨"인 것을 확인

이번 실습에서는 액세스 로깅의 개념을 이해하는 것이 목표입니다. 활성화하려면 로그 저장용 버킷이 필요합니다.

AWS CLI 방법 - 현재 상태 확인

bash
# 계정 ID 확인
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# 이미지 버킷의 로깅 설정 확인
aws s3api get-bucket-logging \
  --bucket shopeasy-images-${ACCOUNT_ID}

# 출력이 비어있으면 로깅이 비활성화된 상태

출력이 비어있거나 {}이면 로깅이 비활성화된 상태입니다.

해설: S3 서버 액세스 로깅

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
(선택) 액세스 로깅 활성화 방법

이 단계는 선택 사항입니다. 액세스 로깅을 활성화하려면 아래 절차를 따릅니다.

bash
# 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}

참고: 로그가 실제로 기록되기까지 수 분에서 수 시간이 걸릴 수 있습니다. 또한 로그 저장으로 인한 추가 스토리지 비용이 발생합니다.

트러블슈팅 가이드

자주 발생하는 문제와 해결 방법

버킷 정책 조회 시 "The bucket policy does not exist" 에러

원인: 버킷에 정책이 설정되어 있지 않음 (Chapter 05에서 설정하지 않았을 가능성)

해결 방법:

  1. 이미지 버킷에 버킷 정책이 없다면 Chapter 05의 Step 3을 참고하여 정책을 추가합니다
  2. 아래 CLI로 정책을 적용합니다:
bash
# 이미지 버킷 정책 생성
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
버전 관리 활성화 시 "Access Denied" 에러

원인: 현재 IAM 사용자/역할에 s3:PutBucketVersioning 권한이 없음

해결 방법:

  1. IAM 콘솔에서 현재 사용자의 정책 확인
  2. AmazonS3FullAccess 정책이 연결되어 있는지 확인
  3. AdministratorAccess가 있다면 권한 문제가 아닐 수 있으므로 버킷 이름을 다시 확인
bash
# 현재 사용자 확인
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을 사용

해결 방법: 올바른 읽기 전용 정책으로 교체합니다

bash
# 올바른 프론트엔드 버킷 정책 적용
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 "프론트엔드 버킷 정책이 읽기 전용으로 교체되었습니다."
버킷을 찾을 수 없음: "NoSuchBucket" 에러

원인: 버킷 이름이 잘못되었거나, 버킷이 다른 리전에 있음

해결 방법:

bash
# 계정의 모든 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
버전 관리 활성화 후 삭제한 파일이 여전히 과금됨

원인: 버전 관리가 활성화된 후 삭제된 객체는 "삭제 마커"만 추가되고 이전 버전은 남아있음

해결 방법: 이전 버전을 완전히 삭제하거나 수명 주기 정책을 설정합니다

bash
# 특정 객체의 모든 버전 확인
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일 후 자동 삭제됨을 의미합니다. 프로덕션에서는 요구사항에 맞게 일수를 조정하세요.

액세스 로깅 활성화 시 "InvalidTargetBucketForLogging" 에러

원인: 로깅 대상 버킷이 소스 버킷과 다른 리전에 있거나, 로깅 버킷에 적절한 권한이 없음

해결 방법:

  1. 로깅 버킷과 소스 버킷이 같은 리전(ap-northeast-2)에 있는지 확인
  2. 로깅 버킷에 S3 로깅 서비스의 PutObject 권한이 있는지 확인
  3. 로깅 버킷 소유자가 소스 버킷 소유자와 동일한지 확인
bash
# 두 버킷의 리전 확인
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
강사 참고: 학생들이 자주 실수하는 포인트
  1. 이미지 버킷 정책의 Resource 경로 혼동 - /uploads/*/*의 차이를 정확히 이해하지 못하는 경우가 많습니다. 전체 공개(/*)가 얼마나 위험한지 강조하세요.
  2. 버전 관리를 되돌릴 수 있다고 착각 - 한 번 활성화하면 Disabled로 되돌릴 수 없고 Suspended만 가능하다는 점을 명확히 설명하세요.
  3. 프론트엔드 버킷의 /*가 보안 문제라고 오해 - 프론트엔드 버킷은 정적 호스팅용이므로 전체 공개(/*)가 정상입니다. 이미지 버킷과 다른 이유를 설명하세요.
  4. 액세스 로깅의 로깅 버킷 리전 불일치 - 소스 버킷과 로깅 버킷이 같은 리전에 있어야 합니다.
  5. 버전 관리와 비용의 관계 - 버전 관리 활성화 시 모든 버전이 보관되므로 스토리지 비용 증가 가능성을 인지시키세요. 수명 주기 정책과 함께 설명하면 좋습니다.