CloudFront 서명된 URL로 콘텐츠 보호하기

이상민·2022년 10월 4일
1

1. CloudFront

AWS의 Content Delivery Network(CDN)이다. CDN이란 정적 콘텐츠를 보다 효율적으로 제공하기 위한 특별한 시스템이다. 만약 특정 서버에서 모든 콘텐츠를 제공한다면 클라이언트와 해당 서버 사이의 물리적 거리로 인해 제공하는데 수요되는 지연시간이 증가할 것이다. 하지만 CloudFront와 같은 CDN은 전세계 곳곳에 캐시 역할을 하는 엣지 로케이션을 통해 더욱 빠르게 콘텐츠를 제공할 수 있다.


2. CDN 콘텐츠 접근 제어

캔바와 같은 서비스를 생각해보자. 캔바에서는 왼쪽의 패널에 있는 다양한 요소를 사용하여 에디터를 꾸밀 수 있다. 하지만 이때 조심할 것이 있다! 캔바는 웹 서비스이기 때문에 개발자 모드를 켜면 요소의 URL을 찾을 수 있고, 이를 공유하여 마음대로 사용하는 악성 사용자가 있을 수 있다.

캔바도 결국 요소를 외부 업체와 계약을 맺고 제공하는 것이기 때문에 이는 곤란할 수 있다. 불특정 다수에게 콘텐츠가 제공되어야하는 이와 같은 상황에서 어떻게 접근 제어를 해야할까? CloudFront는 이런 사용성을 위해 서명된 URL 기능을 제공한다


3. 서명된 URL

서명된 URL이란 만료 시간과 같은 추가 정보를 통해 콘텐츠에 대한 접근을 세부적으로 제어할 수 있는 기능이다. 일단 만료 시간을 통해 URL을 서명한 아래 예시를 통해 바로 알아보자.

https://content.test.com/some/static/file/to/serve.jpg?Expires=1664974447&Signature=askEm~nFo4x736ky7plV6a1HMV6Ngugstmr8cKtM0cB3how8RiYt0y9PnYCW7~Mm4H9AamEBQz4gB34ZmQm1M8Gs6WOw1UNj3ArakE6hj8iYIXTLHAf4S3FZnb-CT0bUVnyFD4zgIdgkesm1d0IwrZcG9DeNaJyiBdyuHphtPIH5AF9l5HEN24VrB2Ge1TZHuyqMxrOCZitWb0qi9AqNN6ksugycd1whn50DjO7W0QWqIw5VLFRna0ISPep0osKdOzZ7-j5mhlScvNet1QaivYUiWjqx~DsPYD1saxl077dM1LmT3OiOZxOrs914uUo8tNIRhp895-8PKdkxgwx~Eg__&Key-Pair-Id=K31DYHTOA6ENGO

뭔가 엄청 길어보이지만 사실 https://content.test.com/some/static/file/to/serve.jpg 이부분만 실제 URL이고 이후 긴 쿼리 스트링은 접근 제어를 위해 서명한 내용이다. 각 쿼리 스트링에 대한 설명은 다음과 같다.

파라미터포멧설명
ExpiresInteger초 단위로 된 유닉스 타임스탬프. URL의 만료 시간을 지정한다. 만료 시간 이후에는 URL에 접근할 수 없다
SignatureStringURL과 쿼리 스트링을 비공개 키를 통해 서명한 내용. 덕분에 URL 변조를 확인할 수 있다
Key-Pair-IdString비공개 키로 서명된 Signature을 검증할 수 있는 등록된 공개 키

이처럼 서명된 URL 기능을 통해 URL은 여전히 사람이 식별 가능하면서 쉽게 접근 제어를 할 수 있다! 한가지 주의할 점은 서명 이후 URL을 조작할 시 변조가 되기 때문에 쿼리 파라미터를 포함시켜야한다면 서명 이전에 추가해야한다.


4. 사용자 지정 정책

위에서는 만료시간으로만 접근을 제어했지만, 이보다 더 복잡한 조건으로 제어하고 싶다면 어떻게 할까? 이를 위해 CloudFront는 Canned 정책과 사용자 지정 정책을 제공한다. Canned 정책을 만료 시간을 통해서만 접근 제어를 할 수 있다. 위 예시가 Canned 정책을 사용한 경우였다. 사용자 지정 정책은 이와 다르게 더 많은 옵션을 제공한다. 접근 만료 시간을 지정할 뿐만 아니라 접근 가능한 시작 시간을 지정할 수도 있고, IP 주소를 통해 접근 제어를 할 수도 있다. 이 글은 간단하게 만료 시간만 다룬다.


5. 서명된 쿠키

이 글에서는 서명된 URL을 위주로 설명하지만 서명된 쿠키라는 옵션도 있기 때문에 잠깐 설명하고 지나간다. 서명된 URL은 말 그대로 URL, 즉 특정 리소스에 대해 접근을 제어하는 기능이다. 하지만 리소스가 아닌 사용자를 기준으로 접근을 제어하고 싶을 수도 있다. 이때 사용하면 좋은 것이 서명된 쿠키이다. 서버에서 서명된 쿠키를 전달하면 클라이언트는 해당 쿠키가 유효한 동안 사용하여 리소스에 접근할 수 있다. 리소스마다 각각 서명하는 서명된 URL과 달리 사용자마다 서명하기 때문에 서명 횟수가 줄어든다는 것도 부가적인 장점이다. CloudFront에서는 서명을 위해 비대칭키 암호화 방식을 사용하는데, 이는 비용이 큰 연산이다. 따라서 적게 서명하므로 성능을 높일 수 있다. 더 자세한 내용은 서명된 쿠키에 대한 레퍼런스를 참고하자.


6. 접근 제어 설정하기

이제 실제로 서명된 URL을 설정하고 사용해보자. 과정이 매우 직관적이기 때문에 대략적인 설명만 작성하겠다.

6-1. 비대칭키 쌍 생성

  • SSH-2 RSA 키 페어이고 base64 인코딩된 pem 포멧이어야하며 2048 키 사이즈이어야 한다.
    -> 공식 문서
openssl genrsa -out private_key.pem
 
openssl rsa -in private_key.pem -out public_key.pem -pubout 

6-2. CloudFront > Key management > Public keys에 공개키 등록

6-3. CloudFront 디스트리뷰션 설정에서 동작 추가

  • Path pattern : 동작을 적용할 경로 매턴
  • Origin andorigin groups : 콘텐츠 오리진 서버
  • 2단계에서 등록한 공개키로 키 그룹을 생성하고 설정

6-4. 자바에서 AWS SDK로 URL 서명

  • aws sdk 의존성을 추가한다
  • 작성일 기준 pem 파일 지원은 sdk 1.x만 해당된다
implementation ("com.amazonaws:aws-java-sdk-cloudfront:1.12.239")
  • URL을 서명한다
File privateKeyFile = new File("/.../private_key.pem");
String keyPairId = "3단계에서 생성한 키 그룹 ID";
Date dateLessThan = DateUtils.parseISO8601Date("2022-11-14T22:20:00.000Z");
 
try {
    PrivateKey privateKey = SignerUtils.loadPrivateKey(privateKeyFile);
    return CloudFrontUrlSigner.getSignedURLWithCannedPolicy(
        resourceUrl,
        keyPairId,
        privateKey,
        dateLessThan
    );
} catch (InvalidKeySpecException e) {
    log.error("", e);
    throw new RuntimeException(e);
} catch (IOException e) {
    log.error("", e);
    throw new RuntimeException(e);
}

7. 서명된 URL의 암호화

마지막으로 서명된 URL은 무엇을 어떻게 서명하는지 간단하게 알아보자. 앞서 설명했듯 서명된 URL은 다음과 같은 비대칭 키로 서명 및 검증을 한다.

  • SSH-2 RSA 키 페어
  • base64 인코딩된 pem 포멧
  • 2048 키 사이즈

7-1. RSA

가장 대표적인 비대칭키 암호화 알고리즘으로 동일한 키로 암호화와 복호화를 하는 대칭키 암호화와 대비되는 개념이다. 비대칭키 암호화에서 공개키와 비공개키는 한 쌍을 이룬다. 공개키로 암호화한 내용은 비공개키로만, 비공개키로 암호화한 내용은 공개키로만 복호화할 수 있다. 예를 들어 서버에서 평문을 비공개키로 암호화한다면, 클라이언트는 공개키를 통해 복호화할 수 있다.

이 덕분에 암호화와 복호화가 단방향으로만 될 수 있어서 클라이언트는 공개키만 가지고는 서버로 위장하여 암호화 할 수 없다. 이러한 특성 때문에 인증된 사용자(서버)가 발급한 것이 맞는지 검증해야하는 서명에서 많이 사용된다. 서명된 URL도 이러한 방식으로 RSA를 사용한다.

7-2. RSA 키 생성

RSA 키 페어는 아래와 같은 과정을 거쳐 생성된다.

  1. 두개의 큰 소수인 p와 q를 선택한다
  2. n = pq를 계산한다 (이때 n의 비트 수가 사이즈이다)
  3. φ(n) = (p-1) * (q-1)일 때 φ(n)보다 작으면서 φ(n)와 서로소인 e를 선택한다
  4. (e * d) mod φ(n) = 1이 되도록 하는 d를 선택한다

이렇게 구한 n, e, d에서 (n, e)를 공개키(n, d)를 비공개키에 사용한다. 예시로 다시 한번 복기해보자

  1. 두 소수 p=17, q=11을 선택한다
  2. n = pq = 17*11 = 187
  3. φ(n) = (p-1) (q-1) = 16 10 = 160, 160보다 작으면서 160과 서로수인 e=7을 선택
  4. (e d) mod φ(n) = (7 d) mod 160 = 1, d=23을 선택

n = 187, e = 7, d = 23

7-3. RSA 키 암호화와 복호화

키를 생성했다면 암호화는 매우 쉽다! 어떤 키로 암호화하고 어떤 키로 복호화 하는지에 따라 평문을 e 또는 d로 제곱 후 n으로 모듈러 연산을 한다.

M = 평문, C = 암호문

// 공개키로 암호화
M^e mod n = C

// 비공개키로 암호화
M^d mod n = C

복호화는 반대로 암호문을 암호화한 키와 다른 키로 제곱한후 n으로 모듈러 연산을 한다

M = 평문, C = 암호문

// 비공개키로 복호화
C^d mod n = M

// 공개키로 복호화
C^e mod n = M

마찬가지로 위에서 예시로 생성한 비공개키로 암호화하고, 공개키로 복호화 해보자

// 평문 150을 비공개키로 암호화
150^23 mod 187 = 57

// 암호문 57을 공개키로 복호화
57^7 mod 187 = 150
profile
편하게 읽기 좋은 단위의 포스트를 추구하는 개발자입니다

1개의 댓글

comment-user-thumbnail
2023년 1월 13일

오, 정리를 잘 해주셨네요.
이름만 알고있던 클라우드 프론트가 뭔지 드디어 깨달았습니다.
서명 알고리즘도 예시까지 잘 설명해주셔서 이해가 잘되네요 감사합니다

답글 달기