LabHub

오브젝트 스토리지와 S3 · 접근 제어(정책·프리사인드 URL) · 이론

프리사인드 URL 이 푸는 문제

LabHub 에서 이어서 보기

한 줄 요약

프리사인드 URL 은 "이 사람이 이 객체를 이 시간 동안 이 동작만 할 수 있다"를 서명된 URL 하나로 표현한 것이다. 바이트가 앱 서버를 통과할 필요가 없어진다.

왜 이게 필요했나

사용자가 올린 파일을 다운로드하게 해 주는 가장 소박한 구현은 이렇습니다. 애플리케이션이 권한을 확인하고, 스토리지에서 객체를 읽어, 응답 본문으로 흘려보냅니다.

이 방식의 문제는 규모에서 나타납니다. 500MB 동영상 100개가 동시에 다운로드되면 앱 서버가 50GB 를 중계합니다. 워커가 그동안 묶이고, 대역폭이 앱 서버 스펙에 갇히고, 메모리 버퍼링을 잘못하면 OOM 이 납니다. 앱 서버는 로직을 처리해야지 바이트를 나르는 파이프가 되면 안 됩니다.

프리사인드 URL 은 역할을 나눕니다. 앱은 권한을 판단하고 URL 을 서명해서 돌려줍니다. 실제 바이트 전송은 클라이언트와 스토리지 사이에서 직접 일어납니다. 앱은 몇 바이트짜리 URL 만 만들면 됩니다.

어떻게 동작하나

URL 에는 서명과 함께 제약이 담깁니다. 어떤 동작(GET/PUT), 어떤 객체, 언제까지(X-Amz-Expires), 어떤 자격 증명으로(X-Amz-Credential), 어떤 헤더를 서명에 포함했는지(X-Amz-SignedHeaders). 스토리지는 이 서명을 검증해 요청을 허용합니다. 서명이 자격 증명으로 만들어졌지만 시크릿 키 자체는 URL 에 없다는 점이 핵심입니다.

업로드에도 씁니다. 프리사인드 PUT URL 을 주면 클라이언트가 스토리지에 직접 올립니다. 여기서 주의할 점은 클라이언트가 무엇을 얼마나 올릴지 제한해야 한다는 것입니다. 프리사인드 POST 정책을 쓰면 Content-Length 범위와 Content-Type 을 서명에 묶을 수 있습니다.

만료 시간은 짧을수록 좋습니다. 다운로드용은 몇 분, 업로드용은 몇십 분이면 대개 충분합니다. URL 이 로그나 리퍼러 헤더로 새어 나갈 수 있기 때문입니다. 반대로 너무 짧으면 느린 회선의 사용자가 업로드를 끝내지 못합니다.

정책은 다른 층위입니다. 프리사인드가 임시 위임이라면 정책은 상시 권한입니다. IAM 스타일 JSON 으로 어떤 주체가 어떤 리소스에 어떤 동작을 할 수 있는지 적습니다. 여기서 자주 하는 실수가 Resource 를 버킷 전체로 잡는 것입니다. 접두어 단위로 좁히면 서비스 하나가 털려도 피해가 그 접두어 안에 갇힙니다.

현장에서 만나는 모습

버킷을 익명 읽기로 열어 두는 것은 대부분의 데이터 유출 사고의 시작점입니다. "이미지만 있으니까 괜찮다"고 열었다가 그 아래 백업이나 사용자 업로드가 섞여 들어가는 일이 흔합니다. 익명 공개가 정말 필요하면 버킷 전체가 아니라 public/ 같은 특정 접두어만 열고, 그 접두어에는 공개해도 되는 것만 넣는 규칙을 코드로 강제합니다.

그리고 MinIO 의 CVE-2025-62506 처럼 세션 정책 우회로 권한이 상승하는 취약점이 실제로 나옵니다. 정책을 좁게 쓰는 것은 구현 버그에 대한 방어이기도 합니다.

다음 실습에서 할 것

익명 접근이 거부되는 것을 확인한 뒤 프리사인드 GET 과 PUT 을 만들어 쓰고 만료를 검증합니다. 그다음 별도 실습에서 사용자를 만들고 읽기 전용 정책과 접두어 한정 정책을 작성해 부착합니다.