ecsimsw
개인 파일 CDN URL 보호하는 방법, CloudFront signed url 본문
개인 파일 CDN URL 보호하는 방법, CloudFront signed url
JinHwan Kim 2024. 4. 10. 16:10개인 파일 CDN URL 보호
사용자 개인 사진, 동영상 파일을 S3에 저장하고 이를 CDN에 캐시하여 자원을 반환한다. 이 구조에서 악성 유저가 CDN URL의 resource key를 brute force로 요청하여 타인의 자원을 확인할 수 있는 문제가 있다. 유효 기간 동안만, 인증된 사용자만 자원을 허락할 수 있도록 개선하고자 한다.
이 글에선 CloudFront 에서 요청의 권한을 확인할 수 있는 두 가지 방법을 소개하고, PICUP에서 선택한 방법과 처리 흐름을 설명하려 한다.
CloudFront function 을 사용한 토큰 인증
CDN 을 처음 공부할 때 정적 자원과 함께 Lambda function 도 올릴 수 있다고 봤었다. 이걸 이용하면 요청을 선처리 할 수 있지 않을까라는 방향으로 공부했고, CloudFront function 을 사용해서 JWT 토큰을 확인하는 방법을 찾을 수 있었다. 공식 문서 에서도 잘 설명되어 쉽게 따라 할 수 있었다.
JWT 토큰을 읽어 토큰 유효성과 payload 를 가져올 수 있다. 토큰 페이로드에서 유저 정보를 확인하고, 요청 자원 url에 포함된 사용자 정보와 일치하는지 확인하여 1. 인증된 사용자가, 2. 본인의 자원에 접근하려 하는지를 검증할 수 있었다.
Function 을 정의하기만 하면 요청을 쉽고 빠르게 선처리할 수 있지만, JWT 토큰을 쿠키에 저장하는 경우 Domain 정보나 HttpOnly 옵션에 따라 쿠키가 제대로 넘어가는지, Function 에서 그 값을 가져올 수 있는지 같은 관리 포인트가 생긴다. 무엇보다, 이런식의 권한 확인이 가능했던 이유는 요청하는 URL 안에 자원의 사용자 정보가 명확한 상황뿐이다.
예를 들어 요청하는 자원의 경로가 "https://d1mx51dsfeok14i.cloudfront.net/users/243/my-image.jpg" 이런 식이고 User 정보 243 이 JWT 페이로드에 들어있다면 이 방식이 가능하다. 그렇지 않고 URL 에 권한을 확인할 수 있는 사용자 정보가 없거나 여러 사용자가 동시에 권한을 갖고 있는 자원은 권한 확인이 불가능할 것이다.
쉽고 빠르지만, 한계가 명확하다.
CloudFront signed url 를 사용한 CDN URL 암호화
Signed url 을 사용하면 자원에 접근할 수 있는 권한을 확인할 수 있다. CDN 에 Public key 를 등록해 두고, 인증된 사용자가 요청하는 URL을 Private 키로 암호화해 두는 방식이다. 암호화된 Url 으로 자원을 요청했을 때 CloudFront 는 Public 키로 요청 URL이 올바르게 인증된 자원인지 확인한다. URL 암호화에 권한이 포함된 자원 정보, IP 범위, 권한 유효 시작 시간, 권한 유효 종료 시간를 지정할 수 있다.
PICUP 을 예시로, FE에서 사진 파일 정보를 BE 에서 요청하면, BE는 파일 주소를 암호화하여 Signed Url 을 반환한다. FE 에서 이 Signed Url 에 요청하게 되면 CDN 을 통해 원하는 파일을 얻을 수 있게 된다. 암호화 과정에서 요청 IP를 제한하면 해당 URL 은 외부인이 사용할 수 없고, 유효 기간을 제한하면 유효 시간 전후의 요청에선 자원을 정상 응답하지 않는다.
Signed url 생성 - AWS SDK Java V2
CloudFront 에서 public key 등록 방법은 공식 문서 또는 간단한 캡처본 를 참고한다.
Java 에서 Signed URL 을 만들기 위해선 aws-sdk-java-v2 가 필요하다. 이때 공식 문서에서 소개하는 " 'software.amazon.awssdk:aws-sdk-java:2.X.X'"가 아니라 아래처럼 필요한 모듈만 가져오길 추천한다. 앞선 aws-sdk-java:2.x.x 은 모든 AWS 리소스를 다루기 위한 의존성을 가져온다. 이 글에서 다루는 S3 와 Cloudfront signed url 을 위해선 아래 두 의존성이면 충분하다.
dependencies {
implementation 'software.amazon.awssdk:s3:2.25.27'
implementation 'software.amazon.awssdk:cloudfront:2.25.27'
}
아래 코드로 Signed url 을 만들 수 있다. 사전 준비로 필요한 요소는 CloudFrontKeyPairId 와 CloudFront 도메인 주소, public 키와 함께 생성된 private key의 로컬 경로이다. 예시에서 생성된 Url 은 7일 동안, /my-image.jpg 자원에 한하여 유효하다.
var cloudFrontKeyPairId = 0;
var cloudFrontDomainName = "";
var privateKeyPath = "";
var resourcePath = "/my-image.jpg";
var sign = CannedSignerRequest.builder()
.privateKey(Path.of(privateKeyPath))
.resourceUrl(new URL("https", cloudFrontDomainName, resourcePath).toString())
.keyPairId(cloudFrontKeyPairId)
.expirationDate(Instant.now().plus(7, ChronoUnit.DAYS))
.build();
var signedUrl = cloudFrontUtilities.getSignedUrlWithCannedPolicy(sign);
return signedUrl.url();
CloudFrontKeyPairId 는 CloudFront -> 퍼블릭 키에서 생성 또는 확인할 수 있다.
CustomSignerRequest 를 사용하면 접근 가능한 자원에 와일드카드를 사용하거나, IP 범위, 유효 시작 시간을 지정할 수 있다. ( 공식 문서 )
var sign = CustomSignerRequest.builder()
.privateKey(Path.of(PRIVATE_KEY_PATH))
.ipRange("0.0.0.0")
.resourceUrl(new URL(CDN_PROTOCOL, CLOUD_FRONT_DOMAIN_NAME, "/users/1/*").toString())
.keyPairId(publicKeyId)
.activeDate(Instant.now())
.expirationDate(Instant.now().plus(EXPIRATION_AFTER_DAYS, ChronoUnit.DAYS))
.build();
var signedUrl = cloudFrontUtilities.getSignedUrlWithCustomPolicy(sign);
return signedUrl.url();
캐싱
매번 자원의 url 을 새로 암호화하면 암호화 시간도 문제겠지만, 매번 바뀌는 Url 에 Content-Cache 정책이 적용되지 않는다. Picup 에서는 url 암호화에 사용자 remote ip 를 사용하여 외부인이 접근하지 못하도록 막는데, {ip:url}으로 암호화된 url 을 캐싱하여 암호화 시간을 제거하고 Content-Cache 를 사용할 수 있도록 하였다.
주의해야 할 것은 CDN URL에 만료 기간이 있다는 것이다. 만료 기간이 지난 CDN Url은 캐시를 사용하지 않고 재발급, 캐시를 업데이트 할 수 있는 API 정의가 필요하다.
아래는 암호화된 CDN URL 캐시 사용으로 브라우저 Content cache가 적용된 사진 로딩의 모습이다.
암호화
- 암호화에는 RSA 를 사용한다.
- 암호화에 필요한 시간은 생각보다 크지 않다.
- 애플리케이션에서 처음 Signed url 을 만드는데만 시간이 걸리고 (16ms), 그 이후부터는 1ms, 0ms 가 소요된다.
signed url duration : 16ms
signed url duration : 5ms
signed url duration : 6ms
signed url duration : 3ms
signed url duration : 2ms
signed url duration : 2ms
signed url duration : 2ms
signed url duration : 1ms
signed url duration : 2ms
signed url duration : 2ms
signed url duration : 1ms
signed url duration : 1ms
signed url duration : 0ms
signed url duration : 0ms
signed url duration : 0ms
signed url duration : 0ms
signed url duration : 0ms
signed url duration : 0ms
signed url duration : 1ms
signed url duration : 1ms
signed url duration : 1ms
'Architecture > Infrastructure' 카테고리의 다른 글
개발자는 Vault를 알지 못하도록, Vault kubernetes injector (0) | 2024.05.31 |
---|---|
Nginx 액세스 로그로 응답 시간 모니터링 / Http 2.0 설정 (0) | 2024.03.20 |
AWS S3 의 비용이 부담된다면? Vultr Object storage 소개하기 (1) | 2023.12.31 |
Nginx 요청 호출 수 제한과 접근 가능 IP 제한 (0) | 2023.12.17 |
Mysql Multi source replication 로 백업 데이터 중앙화 (0) | 2023.11.18 |