AWS S3와 IAM을 함께 사용하는 방법 — 최소 권한 설계부터 업로드까지

조회수 31

S3를 “그냥 파일 저장소”처럼 쓰기 시작하면 권한 설정에서 사고가 난다.
S3는 기본적으로 퍼블릭 차단을 전제로 설계하고, 접근은 IAM 권한으로 통제하는 방식이 안전하다.

S3와 IAM을 함께 쓴다는 것은 결국 “누가(주체) 무엇을(리소스) 어떤 행위로(액션) 할 수 있는가”를 정의하는 일이다.
실무에서는 최소 권한(Least Privilege)을 기준으로 IAM Policy와 Bucket Policy를 조합한다.

1. 전체 흐름 먼저 보기

flowchart TD A["App / Server / User"] B["IAM User or Role"] C["IAM Policy
허용 액션 정의"] D["S3 Bucket"] E["Bucket Policy
버킷 단위 제어"] F["Object (Key)"] A --> B B --> C C --> D E --> D D --> F

IAM Policy는 “주체(IAM User/Role)가 할 수 있는 것”을 정의한다.
Bucket Policy는 “이 버킷은 누구에게 어떤 조건으로 허용할지”를 정의한다.

2. 어떤 IAM 방식을 선택할지

S3 접근 방식은 보통 아래 3가지 중 하나로 정리된다.

  • 로컬/개발 PC — IAM User + Access Key(개발용) 또는 SSO
  • EC2 서버 — IAM Role(Instance Profile)로 키 없이 접근
  • 브라우저 직접 업로드 — 서버가 Presigned URL 발급, 클라이언트가 S3에 PUT

운영 서버에서 Access Key를 파일로 박아두는 방식은 피하는 편이 안전하다.
가능하면 EC2 Role로 전환하는 것이 기본이다.

3. IAM 정책 설계 기본 (최소 권한)

예시는 “특정 버킷의 uploads/ 경로에만 업로드/조회 가능”한 정책이다.
ListBucket은 Prefix 조건으로 제한하고, Object 권한은 uploads/*로 제한한다.


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListOnlyUploadsPrefix",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": ["arn:aws:s3:::my-service-assets"],
      "Condition": {
        "StringLike": {
          "s3:prefix": ["uploads/*"]
        }
      }
    },
    {
      "Sid": "ObjectAccessUploadsOnly",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::my-service-assets/uploads/*"]
    }
  ]
}

버킷 전체 권한(s3:* / arn:aws:s3:::bucket/*)을 습관적으로 주면, 운영에서 실수했을 때 피해 범위가 커진다.
“경로 단위로 좁히는 것”이 가장 확실한 방어다.

4. 버킷 퍼블릭 차단은 기본값으로 유지

버킷은 기본적으로 퍼블릭 차단을 유지하는 것이 안전하다.
정적 공개가 필요하면 전체 공개 대신 CloudFront 또는 제한된 Bucket Policy로 풀어야 한다.


aws s3api put-public-access-block \
  --bucket my-service-assets \
  --public-access-block-configuration \
  BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true

5. (운영 권장) EC2 Role로 S3 접근하기

EC2에서 S3를 사용할 때는 IAM Role(Instance Profile)을 붙이는 방식이 일반적이다.
이 방식이면 서버에 Access Key를 저장하지 않아도 된다.

EC2에 Role을 붙인 뒤에는 AWS CLI가 자동으로 자격 증명을 가져온다.
다음처럼 바로 업로드가 가능하다.


aws s3 cp ./local-file.png s3://my-service-assets/uploads/local-file.png

이 구조는 키 유출 위험을 줄이고, 회수/교체도 Role 정책 변경으로 처리할 수 있다.

6. (개발 환경) IAM User + Access Key로 접근하기

개발 PC에서 CLI를 쓰는 경우는 Access Key를 설정하는 경우가 많다.
운영 계정 키를 쓰지 말고, 개발 전용 권한으로 별도 계정을 분리하는 편이 안전하다.


aws configure
# AWS Access Key ID: ...
# AWS Secret Access Key: ...
# Default region name: ap-northeast-2
# Default output format: json

설정 후 업로드 테스트는 아래처럼 한다.


aws s3 ls s3://my-service-assets/uploads/
aws s3 cp ./test.txt s3://my-service-assets/uploads/test.txt

7. (권장) Presigned URL로 브라우저 직접 업로드

사용자가 파일을 업로드할 때 서버가 파일을 받아서 다시 S3로 올리면 서버 부하가 커진다.
Presigned URL을 쓰면 서버는 “업로드 권한이 포함된 URL”만 발급하고, 실제 업로드는 클라이언트가 S3로 직접 수행한다.

서버(PHP)가 Presigned URL 발급


use Aws\S3\S3Client;

$s3 = new S3Client([
    'version' => 'latest',
    'region'  => 'ap-northeast-2',
]);

$bucket = 'my-service-assets';
$key = 'uploads/' . bin2hex(random_bytes(16)) . '.png';

$cmd = $s3->getCommand('PutObject', [
    'Bucket' => $bucket,
    'Key' => $key,
    'ContentType' => 'image/png',
]);

$request = $s3->createPresignedRequest($cmd, '+10 minutes');
$presignedUrl = (string) $request->getUri();

echo json_encode([
    'upload_url' => $presignedUrl,
    'object_key' => $key,
]);

ContentType을 명시하지 않으면 MIME 타입이 꼬이면서 브라우저 표시/다운로드 동작이 달라질 수 있다.
업로드 정책이 엄격한 환경에서는 ContentType 불일치로 403이 발생하기도 한다.

클라이언트가 S3로 PUT 업로드


async function uploadFile(uploadUrl, file) {
  const res = await fetch(uploadUrl, {
    method: "PUT",
    headers: {
      "Content-Type": file.type
    },
    body: file
  });

  if (!res.ok) {
    throw new Error("upload failed: " + res.status);
  }
}

서버는 object_key만 저장해 두고, 실제 파일 URL은 CloudFront 또는 S3 접근 정책에 맞춰 구성하면 된다.

8. 자주 발생하는 문제와 대처

AccessDenied(403)가 뜨는 경우

대부분 아래 항목 중 하나다.

  • IAM Policy에 Resource ARN이 잘못 지정됨 (버킷 ARN vs 오브젝트 ARN 혼동)
  • Bucket Policy에 명시적 Deny가 존재
  • PutObject 권한은 있는데 ListBucket이 없어 확인 과정에서 막힘
  • KMS 암호화를 쓰는데 kms:Encrypt 권한이 빠짐

먼저 “어떤 주체로 요청했는지”를 확정한 뒤, IAM Policy와 Bucket Policy를 같이 확인한다.
운영에서는 CloudTrail 이벤트를 보면 거절 이유를 좁히는 데 도움이 된다.

업로드는 되는데 다운로드가 안 되는 경우

버킷이 퍼블릭 차단 상태면 기본적으로 직접 접근은 막힌다.
이 경우 CloudFront를 붙이거나, 다운로드도 Presigned URL로 처리하는 방식이 일반적이다.

한 줄 요약

S3는 퍼블릭 차단을 기본으로 두고 IAM으로 접근을 통제한다. 운영 서버는 Access Key 대신 EC2 Role을 사용하고, 사용자 업로드는 Presigned URL로 직접 업로드 구조를 만들면 보안과 성능을 함께 확보할 수 있다.

댓글 0

  • 첫 번째 댓글을 남겨보세요.