Naver Cloud Platform CDN+에서 AWS CloudFront로 마이그레이션하기

Karoid·2020년 2월 3일
0

클라우드 컴퓨팅

목록 보기
1/3
post-thumbnail

사건의 발단

AWS에 비해 클라우드 컴퓨팅 성능이 좋다는 이야기 때문에 서버 임대를 Naver Cloud Platfrom으로 하고 있다. 그 중에서 용량이 좀 되는 파일들은 Object Storage에 저장하고 있는데, 이게 AWS의 S3와 입출력이 같은 구조로 되어 있다.
여기까지 설정하는 것에는 크게 문제가 되는 부분은 없었다. 그러나 Naver Cloud Platform의 CDN을 여기에 붙이니 문제가 발생하기 시작했다. 브라우저에서 계속 CORS 에러나 나면서 파일을 제대로 불러오지 못하는 것이었다.

// 머리를 쥐어뜯게 만들었던 크롬 인스펙터의 에러메시지...
Access to XMLHttpRequest at '<Request URL>'
(redirected from '<Redircetion URL>') 
from origin 'http://localhost:3000' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

상황 분석

curl을 이용해서 CDN에 GET 요청을 넣어도 별다른 문제가 없었다. 그래서 CORS 관련한 구글링을 하다보니 도메인이 다른 곳에 요청을 하는 경우 Preflight과정을 거치는데 이 때 OPTION으로 해당 주소에 요청을 보내 타 도메인에서 접속해도 되는지 확인하는 절차가 있었다. 네이버 클라우드 플랫폼의 CDN+는 OPTION으로 오는 요청을 거절하고 있었다.. 하루가량을 뻘짓하고 문의까지 하고 내린 결론은, CDN을 교체해야 겠다고 생각했다. 코드로 어떻게 수정할 수 있는 부분이 없고, 내 문제도 아니라고 느꼈기 때문에 CDN만 AWS의 CloudFront로 옮기기로 했다.

CloudFront의 구조


CloudFront의 구조는 매우 심플하다. 일반적인 CDN과 같이 뷰어가 오청하면 오리진 서버로 요청 보내고, 다시 오리진 서버에서 요청을 받아 뷰어에게 보낸다. 지극히 심플하면서도 CORS 문제가 발생하지 않았다. 기본값으로 다른 도메인 요청을 허용하도록 설정되어 있는듯 하다. 만약 막혀 있더라도 당연히 수정 방법이 있었을 것이다. 괜히 비싸도 AWS 쓰는 것이 아니라는 생각이 들었다.

URL 만료 설정과 AWS Lambda

하지만 몇가지 문제가 더 있었다. 그건 바로 일정 시간이 되면 URL을 만료시키는 기능을 CloudFront에서 기본적으로 제공하지 않았다. Naver Cloud Platform CDN+에서는 akamai token을 이용해서 일정 시간이 지나면 URL이 만료되었었다. 이 기능을 그대로 사용하고 싶은 욕심이 들어서 열심히 구글링을 한 결과 AWS Lambda를 CloudFront에 붙여서 중간 과정의 정보를 조작할 수 있다는 사실을 알았다. 이 방식을 이용하면 akamai token을 검증하는 작업을 수행하도록 만들 수 있을 것 같았다.

AWS Lambda

개발을 하다보면 어쩌다 여기까지 왔는지 생각이 드는 순간이 가끔씩 있다. 고작 파일 하나 불러오겠다고 이제는 처음 보는 AWS의 서비스를 공부해야 했다. 다행히 AWS는 영문이긴 해도 예제를 몇개 만들어주어 제공하고 있었다. Node.js로 되어있길레 그냥 Node.js로 구현해야겠다고 마음먹었다. 이것까지 다른걸로 만들면 진짜 산으로 갈 것 같았다.
akamai token을 생성하는 Node.js 오픈소스를 일단 구글링했다. 오픈소스의 내용은 지극히 간단했다. 기간 정보를 받고 허용 URL정보를 받은후 SHA256으로 암호화하는 방식이었다. 서버에서 이 방식으로 만든 token을 받아서 검증하고 그에 따른 결과를 내보내도록 만드는 것이 주요 업무 내용이었다.

// Lambda 함수 디렉토리 구조
cloudfront-akamai-token-authentication
ㄴ edgeAuth.js
ㄴ index.js
ㄴ license.txt

우선 오픈소스의 edgeAuth.js 파일을 가져와서 그대로 넣고 index.js에서 해당 파일의 객체를 불러오기로 했다. license.txt 파일은 오픈소스의 라이선스이다.

// index.js
const EdgeAuth = require('./edgeAuth')
const querystring = require('querystring');

var ENCRYPTION_KEY = '<토큰 키>'


exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    
    const params = querystring.parse(request.querystring);
    
    if (typeof params.token == "string") {
      // 토큰이 존재하면
        const params_token_raw = params.token.split("~")
        var params_token = {}
        
        for(chunk of params_token_raw){
            params_token[chunk.split("=")[0]] = chunk.split("=")[1]
        }
        
        var ea = new EdgeAuth({
            key: ENCRYPTION_KEY,
            startTime: params_token["st"],
            endTime: params_token["exp"],
            tokenName: "token"
        })
        
        var token = ea.generateACLToken("/*")
        
        if (params.token == token) {
            // 토큰이 Validate 하면
            if(parseInt(params_token["st"]) < parseInt(Date.now() / 1000) &&
            parseInt(params_token["exp"]) > parseInt(Date.now() / 1000)){
                // 기한 내에 있으면
                console.log("\n__pass__")
                callback(null, request);
                return;
            }else{
                // 기한 내에 없으면
                console.log("\n__timeout__\n")
                request.uri = "/forbidden"
                callback(null, request);
                return;
            }
        }else{
          // 토큰이 invalidate 하면
            console.log("\n__fail_invalid_token__\n")
            console.log("params_token",params.token,"\n")
            console.log("       token",token,"\n")
            request.uri = "/forbidden"
            callback(null, request);
            return;
        }
        
    }else{
      // 토큰이 존재하지 않으면
        console.log("\n__fail_token_needed__\n")
        request.uri = "/forbidden"
        callback(null, request);
        return;
    }
}

진짜 이걸 짜면서 AWS는 정말 다 되는데 그만큼 더 공부를 해야한다는 느낌을 받았다. 마치 높은 자유도에는 높은 실력이 필요하다고 말하는 느낌이다. 이후 몇가지 권한 문제만 손보고 나서 CloudFront에 Lambda 함수를 붙일 수 있었다.

결론

이번 일을 겪으면서 시장의 후반에 들어온 Naver Cloud Platform의 부족한 부분이 많이 드러났다. 특히 CORS 문제를 겪었던 고객이 더 있었을텐데, 이 부분을 손보지 않아놓았다는 것이 의아했다. 그리고 대기업 위의 대기업 AWS는 이 모든 부분을 손볼 수 있게 만든 것도 좀 놀라운 부분이었다.
개인적으로 AWS Lambda에 akamai token을 검증하는 로직을 템플릿으로 제공하면 좀 편했을 것 같다는 생각이 들기도 했다.

코드 자체도 오픈소스로 되어 있어서 템플릿으로 제공했으면 정말 편리했을 것 같다.

profile
Backend. Rails, MongoDB 강좌를 운영하고 있습니다

1개의 댓글

comment-user-thumbnail
2020년 12월 2일

하루종일 NCP CDN+에 CORS 붙여보려고 삽질하다가 이 글을 보게 되었습니다. 응답에 커스텀 헤더 붙여도 안되고... 레퍼런스도 없고 해서 답답했는데... 근본적인 문제가 있었군요. 왜 CORS에 대한 대응이 없는지 저도 정말 궁금하네요. 좋은 글 감사드립니다!

답글 달기