개요
운영 환경에서 S3 버킷을 여러 서비스(ECS, EC2, 온프레미스 서버, 사내 PC)에서 접근해야 하는 상황에서, 보안과 운영 편의성을 모두 만족하는 접근 제어 체계를 설계하고 구축했습니다.
핵심 원칙:
- 퍼블릭 액세스 완전 차단
- IAM Role/User 기반 최소 권한 부여
- TLS(HTTPS) 강제
- Presigned URL을 통한 외부 공유
퍼블릭 액세스 차단 + IAM Role 접근
S3 퍼블릭 액세스 차단을 활성화하면서도 내부 서비스 접근은 정상 동작하도록 설계했습니다.
퍼블릭 액세스 차단 → "Principal": "*" 에만 영향
IAM Role 기반 접근 → "Principal": "arn:aws:iam::..." 은 차단되지 않음
| 항목 | 동작 여부 |
|---|---|
| ECS Task Role에서 S3 접근 | 허용됨 (HTTPS 사용 시) |
| Task Role로 서명한 Presigned URL | 허용됨 (HTTPS 사용 시) |
| 익명(퍼블릭) 접근 | 차단됨 |
환경별 접근 권한 설계
1. ECS 서비스 → S3
컨테이너 내부 애플리케이션이 S3에 접근하려면 Task Role에 권한을 부여해야 합니다.
ECS Task Definition
└── Task Role (컨테이너 내 코드가 AWS API 호출 시 사용)
└── S3 접근 정책 연결
Task Execution Role이 아닌 Task Role에 부여해야 합니다. Execution Role은 이미지 Pull, 로그 전송 등 ECS 인프라 작업용입니다.
2. EC2 서버 → S3
EC2 인스턴스에 연결된 IAM Role에 정책을 추가합니다.
3. 온프레미스 서버 → S3
AWS 외부 서버이므로 EC2 Instance Role을 사용할 수 없습니다. IAM User를 생성하고 Access Key를 발급받아 사용합니다.
4. 사내 PC → S3
테스트/로그 확인/수동 파일 관리 용도로 IAM User Access Key + AWS CLI를 사용합니다.
IAM 정책 설계 (최소 권한)
특정 Prefix 하위만 접근 가능하도록 제한합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowListSpecificPrefix",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::[bucket-name]",
"Condition": {
"StringLike": {
"s3:prefix": ["outbound/*"]
}
}
},
{
"Sid": "AllowReadWriteSpecificPrefix",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::[bucket-name]/outbound/*"
}
]
}ListBucket은 버킷 레벨 + prefix 조건으로 제한- Object 권한은 특정 prefix 하위만 허용
- 불필요한 권한(DeleteObject 등)은 필요 시에만 추가
버킷 정책 설계
운영 환경 (Presigned URL 전용)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPresignedFromAppRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[account-id]:role/[task-role-name]"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[bucket-name]/outbound/*",
"Condition": {
"Bool": { "aws:SecureTransport": "true" }
}
},
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::[bucket-name]",
"arn:aws:s3:::[bucket-name]/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}
]
}개발 환경 (IP 제한 + Role 허용)
개발 환경에서는 사내 IP 대역 허용 + 특정 Role 허용 + 그 외 전부 차단하는 구조입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowInternalIPsReadList",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::[bucket-name]",
"arn:aws:s3:::[bucket-name]/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "true" },
"IpAddress": {
"aws:SourceIp": [
"[office-ip-1]/32",
"[office-ip-2]/32"
]
}
}
},
{
"Sid": "AllowPresignedFromAppRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::[account-id]:role/[task-role-name]"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::[bucket-name]/outbound/*",
"Condition": {
"Bool": { "aws:SecureTransport": "true" }
}
},
{
"Sid": "DenyOtherIPsExceptAppRole",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::[bucket-name]",
"arn:aws:s3:::[bucket-name]/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "true" },
"NotIpAddress": {
"aws:SourceIp": ["[office-ip-1]/32", "[office-ip-2]/32"]
},
"StringNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::[account-id]:role/[task-role-name]",
"arn:aws:sts::[account-id]:assumed-role/[task-role-name]/*"
]
}
}
},
{
"Sid": "DenyInsecureTransport",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::[bucket-name]",
"arn:aws:s3:::[bucket-name]/*"
],
"Condition": {
"Bool": { "aws:SecureTransport": "false" }
}
}
]
}핵심 구조:
- 사내 IP → 읽기/목록 허용
- 특정 Role → Presigned URL용 GetObject 허용
- 그 외 IP + 그 외 Principal → 전부 Deny
- HTTP(비SSL) → 전부 Deny
Presigned URL: STS 세션 만료 문제와 장기키 전환
문제 발생
ECS Task Role(STS 기반)로 Presigned URL을 생성했을 때, URL 유효기간을 7일로 설정해도 STS 세션 토큰이 먼저 만료되어 ExpiredToken(400) 에러가 발생했습니다.
[기존 구조 - 문제]
ECS Task Role (STS 기반)
↓ 임시 자격증명 (세션 만료 있음)
↓ Presigned URL 생성
↓ STS 세션 만료 시 URL 조기 사망
→ 주말/월요일 걸치는 다운로드 실패
해결: IAM User 장기키로 전환
[현재 구조 - 해결]
IAM User 장기키
↓ 세션 만료 없음
↓ Presigned URL 생성
↓ URL 만료시간 = 실제 유효시간 (최대 7일)
IAM User 생성 (prod/dev 분리)
IAM Console → Users → Create user
User name: [service]-presign-signer-prod / dev
Access type: Programmatic access (Access Key)
태그: Service=[service], Purpose=PresignSigner, Env=prod/dev
Presigned 전용 최소 권한 정책
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowGetPutForPresignPrefixesOnly",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": [
"arn:aws:s3:::[bucket-name]/[prefix-1]/*",
"arn:aws:s3:::[bucket-name]/[prefix-2]/*"
]
}
]
}검증 방법
생성된 Presigned URL에서 다음을 확인합니다:
| 항목 | 장기키 서명 (정상) | STS 서명 (문제) |
|---|---|---|
X-Amz-Expires | 604800 (7일) | 604800 |
X-Amz-Security-Token | 없음 | 있음 |
X-Amz-Security-Token이 있으면 아직 STS로 서명되고 있는 것입니다.
코드에서 장기키 명시
// Presigned URL 전용 클라이언트 (장기키 명시)
S3Presigner presigner = S3Presigner.builder()
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKeyId, secretAccessKey)
))
.region(Region.AP_NORTHEAST_2)
.build();
DefaultCredentialsProvider를 사용하면 ECS 환경에서 Task Role(STS)로 서명될 수 있으므로, Presigned URL 생성 시에는 반드시StaticCredentialsProvider로 장기키를 명시해야 합니다.
온프레미스 서버 접근 설정
AWS 외부 서버에서 S3에 접근하는 경우:
# AWS CLI 프로파일 설정
aws configure --profile [profile-name]
# 업로드 테스트
aws s3 cp file.txt s3://[bucket-name]/outbound/ --profile [profile-name]
# 다운로드 테스트
aws s3 cp s3://[bucket-name]/outbound/file.txt ./downloaded.txt --profile [profile-name]
# Presigned URL 생성
aws s3 presign s3://[bucket-name]/outbound/file.txt \
--expires-in 3600 --profile [profile-name]Python에서 프로파일 지정:
session = boto3.Session(profile_name='[profile-name]')
s3 = session.client('s3')보안 운영 통제
장기키는 편리하지만 유출 시 리스크가 있으므로 다음 통제를 적용합니다:
- prod/dev 키 완전 분리
- Prefix 단위 최소 권한 (버킷 전체 접근 불가)
- TLS 강제 (
aws:SecureTransport) - Access Key 정기 로테이션
- CloudTrail로 키 사용 이력 추적
- 장기키를 컨테이너 환경변수로 전역 설정하지 않음 (다른 AWS 호출까지 장기키로 갈 수 있음)
정리
- 퍼블릭 액세스 차단 + IAM Role 기반 접근으로 보안 확보
- ECS Task Role / EC2 Instance Role / IAM User(온프레미스)별 최소 권한 설계
- 버킷 정책: IP 제한 + Role 허용 + TLS 강제 + 나머지 Deny
- STS 세션 만료로 인한 Presigned URL 조기 사망 문제를 장기키 전환으로 해결
- prod/dev 분리, Prefix 제한, StaticCredentialsProvider 명시로 보안 통제