📕 목차
1. 개요
2. Sub Account API Key 발급
3. Cloud Function & API Gateway
4. 실행
1. 개요
📌 presigned url
- Object Storage(= AWS S3)는 일반적으로 누구나 접근해서 수정 가능하면 안 된다.
- 그렇다면 권한 인증을 받아야 하는데, Client에 Object Storage 접근 권한 정보를 입력해두는 것은 좋지 않은 생각이다.
- 즉, Server 측에서 Object Storage 접근 권한을 얻고 Client에게 해당 경로를 반환해주면 되는데, 그것이 presigned url
- 다만 presigned url은 말 그대로 접근이 허용된 경로이기 때문에, 무한정 열어주면 Client가 언제든지 접근이 가능해지는 보안 구멍이 생긴다.
- 따라서 presigned url을 발급받을 때는 적절한 권한 제어와 만료 시간을 설정해주어야 한다.
📌 serverless
- Server가 없는 것이 아니라, 개발자가 Server를 직접 관리할 필요가 없는 Architecture를 말한다.
- 개발자가 개발한 API는 instance를 대여해서 24시간 동안 비용이 지출된다.
- Serverless에 등록한 API는 호출 횟수로 비용이 책정된다. (동적 자원 할당)
- 호출 횟수가 많지 않거나, WAS에서 모두 처리하는 것이 부담되는 경우 Serverless로 분리하면 비용과 트래픽을 효율적으로 관리할 수 있다.
📌 Architecture
1️⃣ Senario1. Client가 object storage에 바로 업로드
- Client 측 Application에 API key를 넣어두고 Object Storage에 직접 접근
Access Key야 그렇다쳐도, Secret Key는 노출되면 보안 취약점으로 보안 취약점으로 이어지므로 패스
2️⃣ Senario2. API에서 presigned url 발급받은 후 업로드
- WAS에 presigned url 요청
- API에서 Object Storage 측에 presigned url 요청 (PUT 권한)
- Object Storage에서 Server에 presigned url과 메타 데이터 전달
- Server에서 Client 측으로 presigned url 반환
- 유효 시간 내에 Client가 Object Storage로 사진 업로드
지인이 알려준 방법이 이거였으나, 과정을 유심히 보던 중 '굳이 WAS에 요청을 보내야 할까?'라는 의문이 들었다.
API 호출만 하고 끝나는 단순한 작업인데다, serverless로 처리한다면 server로 가는 트래픽을 감소시킬 수 있을 것 같았다.
검색해보니 나만 그렇게 생각한 게 아니라, 대부분이 그렇게 구현해뒀길래 좀 더 찾아보게 되었다.
3️⃣ Senario3. Serverless로 presigned url 발급받은 후 업로드
- Action을 실행시키기 위한 API Gateway에 POST 요청
- API Gateway에서 Cloud Function 실행
- Cloud Function에서 Object Storage에 presigned url 발급 요청 (PUT 권한)
- Object Storage에서 Cloud Function측에 presigned url과 메타 데이터 전달
- Client측으로 presigned url 반환
- 유효 시간 내에 Client가 Object Storage로 사진 업로드
이론 상 완벽했다.
문제는 내가 람다를 써본 적이 없다는 것. (comento 4주차 강의에서 사용해보긴 했는데, 멘토님이 코드를 다 짜주셔서 도움이 안 됐다.)
그래도 Serverless, Lambda라는 개념 자체는 이전에 관심이 있어 몇 번 찾아봤기에 사용법만 찾으면 될 거라 생각해서 일단 시작했다.
근데 NCP 관련 자료 너무 빈약하더라 ㅋㅋ...덕분에 밤 새서 작업했다.
AWS S3 만져봤다면 쉬웠을 것.
2. Sub Account API Key 발급
📌 Sub Account
- 루트 계정은 너무 많은 권한을 가지고 있기 때문에, 루트 계정의 API Key는 사용하지 않는 것을 권고한다.
- 따라서 제한된 권한을 가진 Sub Accounts를 생성하자.
- API Key를 발급받기 위해 API Gateway 접근 권한을 허용해주어야 한다.
- Object Storage 접근 권한을 허용해주기 위해서 정책도 적절히 선택한다.
- 모든 권한을 주면 편하지만, 그만큼 보안 문제가 생긴다는 점에 유의하자.
📌 API Key 발급
- Sub Account의 access/secret key를 발급받고 저장하자
- access key야 url 발급하면 노출되긴 하지만, secret key는 절대 노출시켜선 안 된다.
3. Cloud Function & API Gateway
📌 Action 생성
- Cloud Function 이용 요청을 하고, Package를 생성한다. (안 해도 상관은 없지만, 비슷한 Action을 하나의 package 내에서 관리하는 것을 권장한다.)
- 나는 굳이 trigger는 필요 없을 것 같아 정의하지 않았다. actions는 node.js 환경에서 실행
function main(params) {
const AWS = require("aws-sdk");
const { v4: uuidv4 } = require('uuid');
const endpoint = new AWS.Endpoint("https://kr.object.ncloudstorage.com");
const region = "kr-standard";
const access_key = params.access;
const secret_key = params.secret;
const S3 = new AWS.S3({
endpoint: endpoint,
region: region,
credentials: {
accessKeyId: access_key,
secretAccessKey: secret_key,
},
signatureVersion: 'v4'
});
const bucket_name = "pkcy";
const object_name = params.dirname + "/" + uuidv4().substring(0, 13).replace("-", "") + "." + params.extension;
const signedUrlExpireSeconds = 300;
// const content_type = "image/*"
const url = S3.getSignedUrl("putObject", {
Bucket: bucket_name,
Key: object_name,
Expires: signedUrlExpireSeconds
});
console.log(url);
// presigned url
return {payload: url};
- 실행 코드를 붙여넣고, access/secret key를 파라미터로 전달한다.
- dirname과 extension은 클라이언트로 부터 전달받을 것이므로 생략해도 무방하다.
- 나는 파일 이름을 uuid로 정했는데, 이건 본인 서비스 정책에 맞게 선택하면 된다. 파일명까지 클라이언트에게 받을 거면 파라미터를 추가하면 된다.
✒️ 시행 착오
처음에 signatureVersion: "v4"를 인자로 넣지 않았더니 계속 SignatureDoesNotMatch 에러가 발생했다.
한참을 머리싸매다가 aws cli로 Object Storage의 get 권한을 받는 명령어를 실행해봤더니, 다음과 같았다.
aws --endpoint-url=https://kr.object.ncloudstorage.com s3 presign s3://{bucket}/{object} --expires-in 120
v4 버전으로 S3 객체를 생성하지 않으면 헤더 정보가 많이 다르게 나온다.
여기저기 찾아보다가 로컬 node.js 애플리케이션으로 테스트해보니 정상적으로 처리됐다.
📌 API Gateway 연결
- API GW를 대충 만들어주고 APIs를 눌러서 경로를 잡아줘야 한다.
// AWS에서 지정한 API GW url 패턴
https://{product_id}.apigw.ntruss.com/{api_name}/{stage_name}/{resource_name}/{type+}
- api를 만들어봤다면 여기서도 비슷하다. 경로 잘 잡아주고 POST 요청을 허용하자.
- 위에서 만들었던 Action으로 이동해서 API Gate와 연결해주면 된다.
4. 실행
📌 presigned url 발급
- 사전에 명시했던 dirname, extension을 파라미터로 넘긴다.
- payload로 넘어온 URL 정보가 곧 presigned url. 여기에 PUT 요청을 보내면 된다.
- expire time을 300s로 지정했기에 만료 시간이 지나면 권한이 사라진다. (만료 시간 내엔 권한이 유지된다.)
이 부분이 다소 문제가 있다고 여겨진다.
presigned url을 발급받기 위한 Sub Account의 권한을 최소화해두긴 했지만, 악용하려고 했을 때 어떤 시나리오가 있을지 생각해보면 개선할 방향을 정할 수 있을 것이다.
예를 들어, 하나의 Trigger를 더 두어서 발급한 presigned url에 대한 요청을 단 한 번만 허용하도록 할 수도 있을 것 같긴 한데 아직까진 꼭 필요한지 모르겠다.
✒️ block과 result
{path}?result=true&blocking=true
- result: 응답에 대한 결과값만을 보여준다.
- blocking: 비동기 방식이 아닌 동기 방식으로 처리하는데, 이걸 안 해주면 action의 결과값이 아니라 실행된 action의 번호만 주구장창 알려준다.
📌 Object Storage에 사진 업로드
- binary로 사진을 선택하고 presigned url에 PUT 요청을 Object Storage에 저장되는 것을 확인할 수 있다.
- PUT 요청에 대한 presigned url을 얻은 것이기 때문에 반드시 PUT 요청만 성공한다.
개념적으로만 알고 있던 Serverless와 Object Storage를 직접 사용하고 성공해서 기분이 좋다.
AWS로 시작했다면 보다 수월하게 했을 것 같긴 한데, 방식이 똑같아서 다행히 하루 정도 밤새서 해결 가능했던 것 같다.
NCP에서 이걸 한 사람이 별로 없던 거 같아서, 처음 하시는 분들께서 도움이 되셨으면 좋겠다.