TT Lab
시작하기
배우기 러닝패스 코스

오브젝트 스토리지와 S3

사용자와 정책으로 접근 제어하기

TT Lab 에서 이어서 보기

목표

서비스 사용자를 만들고 최소 권한 정책을 직접 작성해 부착하면서, 권한 범위를 접두어 단위로 좁히는 습관을 만든다.

왜 중요한가

스토리지 유출 사고의 대부분은 복잡한 공격이 아니라 넓게 열어 둔 권한에서 옵니다. "이미지만 있으니까"라며 버킷을 익명 읽기로 열었는데, 몇 달 뒤 그 버킷 아래에 백업이나 사용자 업로드가 섞여 들어가는 식입니다. 정책의 Resource 를 버킷 전체(arn:aws:s3:::bucket/*)로 잡는 것도 같은 문제입니다 — 서비스 하나의 자격 증명이 새면 버킷 전체가 노출됩니다. 접두어까지 좁혀 두면 피해가 그 접두어 안에 갇힙니다. 그리고 이것은 구현 버그에 대한 방어이기도 합니다. MinIO 의 CVE-2025-62506 은 세션 정책 우회로 권한이 상승하는 취약점이었습니다. 정책을 좁게 쓰는 습관은 그런 순간에 남는 마지막 방어선입니다.

이 실습은 AWS 와 같은 IAM API(aws iam ...)로 합니다. 서버는 SeaweedFS 의 S3 게이트웨이이고, 사용자·액세스 키·관리형 정책·부착을 AWS 와 같은 호출로 받습니다. 그래서 여기서 쓴 명령과 정책 문서는 AWS 계정에 그대로 가져갈 수 있습니다.

단계

  1. 관리자 프로필 local 로 aws iam create-user --user-name appuser 를 실행하고, aws iam create-access-key --user-name appuser 로 받은 키를 프로필 app 에 넣는다(endpoint_url 과 region 도 함께).
  2. aws --profile app s3 ls s3://lab-media 를 시도하고 결과를 /root/pol/denied.txt 에 저장한다. 파일에 AccessDenied 가 있어야 한다.
  3. /root/pol/readonly.json 에 정책을 쓴다. s3:GetObject 와 s3:ListBucket 을 허용하고 Resource 는 arn:aws:s3:::lab-media 와 arn:aws:s3:::lab-media/* 두 개다.
  4. aws iam create-policy --policy-name labreadonly --policy-document file:///root/pol/readonly.json 으로 관리형 정책을 만들고, aws iam attach-user-policy 로 appuser 에 부착한다. aws --profile app s3 ls s3://lab-media 가 성공해야 한다.
  5. aws --profile app s3 cp /opt/fixtures/s3/readme.txt s3://lab-media/doc/nope.txt 가 실패해야 한다. /root/pol/write.txt 에 write_denied=true 를 적는다.
  6. /root/pol/imgonly.json 에 Resource 를 arn:aws:s3:::lab-media/img/* 로 좁힌 정책을 쓰고 labimgonly 로 만들어 부착한다(넓은 labreadonly 는 뗀다). appuser 로 img/logo.png 조회는 되고 doc/sales.csv 조회는 실패해야 한다. /root/pol/prefix.txt 에 img=ok doc=denied 를 적는다.
  7. 버킷 정책으로 lab-media 의 public/ 접두어만 익명 다운로드로 연다. 익명 GET 이 public/ 아래 객체에는 200, private/ 아래 객체에는 403 이어야 한다. /root/pol/anon.txt 에 public=200 private=403 을 적는다.

참고

서비스 사용자 만들기

관리자 프로필 local 로 aws iam create-user --user-name appuser 를 실행하고, aws iam create-access-key --user-name appuser 로 받은 키를 프로필 app 에 넣는다(endpoint_url 과 region 도 함께).

관리자 프로필로 사용자를 만들고 액세스 키를 발급합니다. 시크릿은 발급 응답에서 한 번만 보이니 바로 프로필에 넣으세요.

정책 없는 사용자가 거부되는지 확인하기

aws --profile app s3 ls s3://lab-media 를 시도하고 결과를 /root/pol/denied.txt 에 저장한다. 파일에 AccessDenied 가 있어야 한다.

새 프로필로 목록을 시도하면 거부됩니다. 기본은 아무 권한도 없는 것입니다.

읽기 전용 정책 작성하기

/root/pol/readonly.json 에 정책을 쓴다. s3:GetObject 와 s3:ListBucket 을 허용하고 Resource 는 arn:aws:s3:::lab-media 와 arn:aws:s3:::lab-media/* 두 개다.

JSON 으로 Effect, Action, Resource 를 적습니다. 목록 조회와 객체 읽기는 서로 다른 Action 입니다.

관리형 정책 만들고 부착해 읽기 성공시키기

aws iam create-policy --policy-name labreadonly --policy-document file:///root/pol/readonly.json 으로 관리형 정책을 만들고, aws iam attach-user-policy 로 appuser 에 부착한다. aws --profile app s3 ls s3://lab-media 가 성공해야 한다.

정책을 만들고 사용자에게 붙이는 것은 별도 단계입니다. 부착 직후에는 반영에 몇 초가 걸릴 수 있습니다.

쓰기는 여전히 거부되는지 확인하기

aws --profile app s3 cp /opt/fixtures/s3/readme.txt s3://lab-media/doc/nope.txt 가 실패해야 한다. /root/pol/write.txt 에 write_denied=true 를 적는다.

읽기 전용 정책이라면 업로드가 실패해야 합니다. 실패하는 것이 정상입니다.

접두어 한정 정책 만들기

/root/pol/imgonly.json 에 Resource 를 arn:aws:s3:::lab-media/img/* 로 좁힌 정책을 쓰고 labimgonly 로 만들어 부착한다(넓은 labreadonly 는 뗀다). appuser 로 img/logo.png 조회는 되고 doc/sales.csv 조회는 실패해야 한다. /root/pol/prefix.txt 에 img=ok doc=denied 를 적는다.

Resource 에 와일드카드를 접두어까지 붙입니다. img/ 는 되고 doc/ 는 안 되게 하세요.

특정 접두어만 익명 공개하기

버킷 정책으로 lab-media 의 public/ 접두어만 익명 다운로드로 연다. 익명 GET 이 public/ 아래 객체에는 200, private/ 아래 객체에는 403 이어야 한다. /root/pol/anon.txt 에 public=200 private=403 을 적는다.

버킷 전체를 여는 것과 접두어 하나만 여는 것은 사고 규모가 다릅니다.

Resource 끝의 슬래시 한 글자를 조심하세요. 이 실습의 요지가 거기에 다 들어 있습니다.

"Resource": "arn:aws:s3:::lab-media/public*"    → public 으로 시작하는 모든 키
"Resource": "arn:aws:s3:::lab-media/public/*"   → public/ 아래 키만

S3 에 폴더는 없습니다. 정책이 보는 것은 문자열 접두어뿐이라, 슬래시를 빠뜨리면 public-backup.tar 처럼 public 으로 시작하기만 하는 루트 객체까지 함께 열립니다. aws s3api get-bucket-policy --bucket lab-media 로 서버에 실제로 걸린 Resource 를 확인하세요.

버킷 정책은 버킷에 하나이고 put-bucket-policy 를 할 때마다 통째로 바뀝니다. 범위를 바꿀 때는 문서 전체를 다시 넣으세요.