S3 사전 서명 URL, 서명이 보증하지 않는 것
사전 서명 URL은 만료 전까지 몇 번이든 쓸 수 있고, 만든 쪽의 자격 증명보다 오래 살지 못한다.
누가 보내는지, 무엇이 올라오는지는 서명과 상관없이 서비스가 따로 확인해야 한다.
이 글의 목차서버를 거치지 않고 올리는 이유7
프로필 사진 업로드 같은 기능을 처음 만들 때 파일을 S3에 올리는 방법을 찾다 보면 ‘사전 서명 URL’이라는 이름을 자주 만난다.
서버가 이 URL을 만들어 브라우저에 건네면, 브라우저가 그 주소로 S3에 파일을 바로 올리는 방식이다.
만들어 보면 생각보다 간단하다.
그런데 이 주소가 무엇을 허락하고 무엇은 확인하지 않는지 모르고 쓰면 업로드 기능에 빈틈이 생긴다.
서버를 거치지 않고 올리는 이유
가장 단순한 업로드는 브라우저가 파일을 서버로 보내고, 서버가 다시 S3로 보내는 방식이다.
이러면 파일이 클수록 서버의 네트워크 대역폭과 메모리가 그만큼 쓰인다.
AWS의 사전 서명 URL 지침은 클라이언트가 S3와 직접 주고받게 해서 S3의 확장성을 그대로 쓰는 것을 이 방식의 핵심 이유로 든다.
서버는 바이트를 나르지 않고 주소만 만들어 주면 된다.
문제는 권한이다.
S3는 원래 액세스 키 같은 AWS 자격 증명으로 요청한 사람을 확인한다.
내 서비스의 사용자에게는 그런 자격 증명이 없고, 그렇다고 버킷을 누구나 쓰게 열어 둘 수도 없다.
사전 서명 URL은 버킷 정책을 바꾸지 않고도 특정 객체에 정해진 시간 동안만 접근을 허락한다.
객체는 S3에 저장된 파일 하나이고, 객체 키는 버킷 안에서 그 파일을 가리키는 이름이다.
서명에 담기는 것
이 URL을 만들 때는 서명할 자격 증명과 함께 버킷, 객체 키, HTTP 메서드, 만료 시간을 정한다.
HTTP 메서드는 요청의 종류이고, 업로드라면 PUT이다.
서명은 이 요청 값들을 모두 묶어 계산한 값이고, 요청에서 비밀인 것은 그 서명 하나뿐이다.
값을 하나라도 바꾸면 서명이 맞지 않아 S3가 거절한다.
이 목록에는 빠진 것이 있다.
누가 보내는지, 몇 번 쓰이는지, 무엇이 담기는지는 서명에 들어가지 않는다.
이 세 가지는 서비스가 따로 챙겨야 한다.
서명은 만든 쪽의 권한을 빌려 준다
사전 서명 URL이 쓰는 자격 증명은 그 URL을 만든 주체의 것이다.
받는 쪽은 AWS 자격 증명이 없어도 되고, 만든 쪽이 가진 권한 안에서만 요청할 수 있다.
그리고 서명은 받는 쪽이 누구인지 따지지 않는다.
사전 서명 URL은 가진 사람이면 누구나 쓸 수 있는 토큰(bearer token)이다.
주소를 손에 넣은 사람은 누구든 같은 요청을 보낼 수 있다.
그래서 로그인 확인은 URL을 내주기 전에 서비스가 끝내야 한다.
이 사용자가 정말 이 파일을 올려도 되는지 확인한 다음에만 주소를 만들어 준다.
적어 둔 만료 시간보다 일찍 끝나기도 한다
만료 시간은 만들 때 정한다.
콘솔에서 고르는 범위는 1분에서 12시간이고, 명령줄 도구(CLI)나 언어별 개발 도구(SDK)로는 최대 7일이다.
그런데 서명에 쓴 자격 증명이 임시 자격 증명이면 이야기가 달라진다.
URL은 서명한 자격 증명보다 오래 유효할 수 없어서, 정한 만료 시간과 자격 증명의 만료 중 먼저 오는 쪽에서 끝난다.
서버에 붙이는 IAM 역할이 바로 이런 임시 자격 증명을 쓴다.
IAM 역할은 키를 따로 보관하지 않고 AWS 권한을 빌려 쓰게 해 주는 신원이다.
컨테이너를 실행해 주는 ECS에서는 태스크의 역할 자격 증명이 보통 1시간에서 6시간마다 바뀐다.
태스크 역할이 무엇인지는 태스크 역할과 실행 역할 글에서 다뤘다.
개발할 때와 배포한 뒤의 결과가 달라지는 이유가 여기 있다.
오래 가는 액세스 키로 서명하면 잘 되다가, 역할로 서명하면 적어 둔 시간보다 일찍 끊긴다.
URL은 6시간, 발급 자격 증명은 2시간 뒤 만료하는 예시다.
URL의 기한
6시간 뒤자격 증명의 기한
2시간 뒤URL의 기한
아직 남음6시간 중 3시간 경과
자격 증명의 기한
만료됨새 요청 거부
임시 자격 증명으로 만든 사전 서명 URL은 URL 기한과 자격 증명의 기한 중 먼저 끝나는 쪽을 넘길 수 없다.
발급 1시간 뒤에는 두 기한 모두 지나지 않았다. 새 요청을 보낼 수 있다.
가상의 기한이며 다른 정책 거부·자격 증명 취소는 없는 예시다. 만료 전에 시작한 전송과 새 요청은 구별한다.
만료는 요청이 들어온 시점에 확인한다.
그래서 만료 직전에 시작한 큰 다운로드는 도중에 만료 시각이 지나도 계속된다.
다만 연결이 끊긴 뒤 다시 시작하면 그때는 거절된다.
만료 전까지는 몇 번이든 쓸 수 있다
한 번 쓰면 사라지는 주소로 생각하기 쉽지만 사전 서명 URL은 만료 시각까지 여러 번 쓸 수 있다.
업로드용 주소라면 같은 키로 다시 올리는 것도 막히지 않는다.
같은 키의 객체가 이미 있으면 S3는 기존 객체를 새로 올린 것으로 바꾼다.
누군가 같은 주소로 다시 올리면 검사를 마친 파일도 다른 내용으로 덮인다.
주소가 퍼지는 길도 있다.
서명은 URL의 쿼리 문자열에 실려 다니므로, 클라이언트나 중간의 프록시가 URL을 로그에 남기면 서명도 함께 남는다.
그래서 AWS는 사전 서명 URL을 잠깐 쓰는 접근 자격 증명처럼 다루라고 권한다.
만료 시간을 짧게 잡으면 새어 나간 주소가 쓰일 수 있는 시간도 짧아진다.
AWS는 조건이 맞으면 60초짜리 서명도 현실적으로 쓸 수 있다고 본다.
무엇이 올라오는지는 서명이 정하지 않는다
서명이 기본으로 정하는 것은 어느 키에 어떤 방식으로 요청할 수 있느냐까지다.
파일의 체크섬 값을 요청에 함께 실어 무결성을 확인할 수도 있지만, 그것은 파일이 전송 중에 바뀌지 않았는지를 볼 뿐 내용이 안전한지는 알려 주지 않는다.
브라우저 화면에서 확장자를 검사하고 사진 크기를 줄여도, 누군가는 그 화면을 거치지 않고 같은 주소로 다른 파일을 보낼 수 있다.
화면의 검사는 사용자를 돕는 장치일 뿐이다.
조건을 더 걸 수는 있다.
S3는 POST 방식의 사전 서명도 지원하는데, 이때는 미리 서명한 정책에 맞는 업로드만 받는다.
그래도 파일 내용이 안전한지는 올라온 뒤에 확인해야 한다.
| 서명이 정하는 것 | 서명이 정하지 않는 것 | 서비스가 따로 할 일 |
|---|---|---|
| 버킷, 키, 요청 방식 | 누가 보내는지 | 로그인 확인 뒤에만 발급 |
| URL의 만료 시각 | 실제로 끝나는 시각 | 서명한 자격 증명의 수명 확인 |
| 서명에 넣은 요청 값 | 파일 내용의 안전성 | 올라온 뒤 내용 검사 |
올라온 뒤의 확인에는 S3 이벤트 알림을 많이 쓴다.
객체가 새로 만들어지면 S3가 메시지 큐인 SQS나 코드를 실행해 주는 Lambda 같은 곳으로 알림을 보낸다.
알림은 보통 몇 초 안에 오지만 1분 넘게 걸릴 때도 있다.
한 번 이상 전달되도록 설계돼서 같은 알림이 두 번 오기도 하므로, 검사 처리는 같은 파일을 두 번 받아도 문제없게 만든다.
서버에 남겨 둘 것
업로드를 S3로 직접 보내면 서버는 가벼워지지만, 서버가 모르는 일이 늘어난다.
파일이 실제로 올라왔는지, 언제 올라왔는지, 무엇이 담겼는지를 서버가 저절로 알지 못한다.
그래서 실무에서는 주소를 발급한 기록을 남긴다.
누구에게 어느 키로 언제까지 유효한 주소를 주었는지 남겨 두면, 이상한 파일이 올라왔을 때 거꾸로 따라갈 수 있다.
키는 업로드마다 새로 만든다.
사용자가 고른 파일 이름을 그대로 키로 쓰면 다른 사람의 파일과 겹치거나 덮어쓸 수 있기 때문이다.
올라온 파일은 먼저 검사용 위치에 두고, 검사를 통과한 것만 서비스가 쓰는 위치로 옮긴다.
같은 버킷에 다시 쓰는 처리라면 그 쓰기가 또 알림을 일으켜, 처리가 자기 자신을 다시 부를 수 있다.
그래서 버킷을 나누거나, 알림을 들어오는 파일이 놓이는 경로에만 걸어 둔다.