사용자와 정책으로 접근 제어하기
목표
서비스 사용자를 만들고 최소 권한 정책을 직접 작성해 부착하면서, 권한 범위를 접두어 단위로 좁히는 습관을 만든다.
왜 중요한가
스토리지 유출 사고의 대부분은 복잡한 공격이 아니라 넓게 열어 둔 권한에서 옵니다. "이미지만 있으니까"라며 버킷을 익명 읽기로 열었는데, 몇 달 뒤 그 버킷 아래에 백업이나 사용자 업로드가 섞여 들어가는 식입니다. 정책의 Resource 를 버킷 전체(arn:aws:s3:::bucket/*)로 잡는 것도 같은 문제입니다 — 서비스 하나의 자격 증명이 새면 버킷 전체가 노출됩니다. 접두어까지 좁혀 두면 피해가 그 접두어 안에 갇힙니다. 그리고 이것은 구현 버그에 대한 방어이기도 합니다. MinIO 의 CVE-2025-62506 은 세션 정책 우회로 권한이 상승하는 취약점이었습니다. 정책을 좁게 쓰는 습관은 그런 순간에 남는 마지막 방어선입니다.
이 실습은 AWS 와 같은 IAM API(aws iam ...)로 합니다. 서버는 SeaweedFS 의 S3 게이트웨이이고, 사용자·액세스 키·관리형 정책·부착을 AWS 와 같은 호출로 받습니다. 그래서 여기서 쓴 명령과 정책 문서는 AWS 계정에 그대로 가져갈 수 있습니다.
단계
- 관리자 프로필
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/*두 개다.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를 적는다.- 버킷 정책으로
lab-media의public/접두어만 익명 다운로드로 연다. 익명 GET 이public/아래 객체에는 200,private/아래 객체에는 403 이어야 한다./root/pol/anon.txt에public=200 private=403을 적는다.
참고
- 사용자·키:
aws iam list-users·aws iam list-access-keys --user-name appuser - 정책 목록과 부착 확인:
aws iam list-policies·aws iam list-attached-user-policies --user-name appuser - 정책 ARN 은 손으로 적지 말고
list-policies로 찾습니다. AWS 는 계정 번호가 들어가고(arn:aws:iam::123456789012:policy/…) 이 서버는 비어 있어(arn:aws:iam:::policy/…) 형식이 다릅니다. - 같은 이름으로
create-policy를 다시 하면EntityAlreadyExists입니다. 문서를 고칠 때는aws iam create-policy-version --set-as-default를 씁니다. - 버킷 정책:
aws s3api put-bucket-policy --bucket lab-media --policy file://…· 확인은get-bucket-policy - IAM 변경은 즉시 반영되지 않을 수 있습니다. AWS 도 몇 초가 걸리므로, 부착 직후의 확인은 몇 번 재시도하는 편이 안전합니다.
- 이 서버의 한계: 사용자에 직접 넣는 인라인 정책(
put-user-policy)은Allow만 받습니다(Deny는MalformedPolicyDocument). 관리형 정책과 버킷 정책은Deny도 받으므로, 이 실습은 AWS 가 권하는 관리형 정책으로 합니다. - 흔한 실수 1:
ListBucket을 빼먹는 것 — 객체는 읽히는데 목록이 안 나옵니다. 둘은 다른 Action 이고 Resource 형식도 다릅니다. - 흔한 실수 2:
Resource를 버킷 전체로 잡는 것 — 자격 증명 하나가 새면 전부 노출됩니다.
서비스 사용자 만들기
관리자 프로필 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 를 할 때마다 통째로 바뀝니다. 범위를 바꿀 때는 문서 전체를 다시 넣으세요.