본문 바로가기
클라우드

POST 방식으로 S3 버킷에 파일 업로드 #1

by ^..^v 2020. 11. 4.
728x90
반응형

등록되고 인증된 사용자만 S3 버킷에 파일을 직접 업로드하는 방법을 구현해 보겠습니다. 

(본 실습에서는 2번과 3번의 JWT 토큰 전달 및 검증은 포함하지 않습니다.)

 

 

#1 S3 버킷에 파일을 업로드 권한을 가진 사용자를 생성

upload-s3 사용자를 추가합니다.

 

upload-s3 사용자에게 버킷 조회 권한과 객체 생성 권한을 부여합니다. 

 

 

 

액세스 키와 비밀 액세스 키를 보관합니다. 

 

 

#2 정책을 생성하는 람다 함수를 생성합니다. 

작업 디렉터리를 생성합니다. 

 

모듈을 설치합니다.

 

package.json에 람다 함수 생성 스크립트 추가

 

C:\serverless\get-upload-policy\index.js 람다 함수 작성

'use strict';

var async = require('async');
var crypto = require("crypto-js");

const C_ACL = 'private';
const C_NOW = new Date().toISOString();
const C_DATE_STAMP = C_NOW.slice(0,10).replace(/-/g,'');
const C_REGION_NAME = 'us-east-1';
const C_SERVICE_NAME = 's3';
const C_X_AMZ_DATE = C_NOW.replace(/[-:\.]/g,'');
const C_X_AMZ_ALGORITHM = 'AWS4-HMAC-SHA256';
const C_X_AMZ_CREDENTIAL = `${process.env.ACCESS_KEY}/${C_DATE_STAMP}/${C_REGION_NAME}/${C_SERVICE_NAME}/aws4_request`;

//  오류 메시지를 반환
function createErrorResponse(errCode, errMessage) {
    var response = {
        'statusCode': errCode, 
        'headers': { 'Access-Control-Allow-Origin': '*' }, 
        'body': JSON.stringify({ 'error': errMessage })
    };
    return response;
}

//  성공
function createSuccessResponse(message) {
    var response = {
        'statusCode': 200, 
        'headers': { 'Access-Control-Allow-Origin': '*' }, 
        'body': JSON.stringify(message)
    };
    return response;
}

//  expiration(정책 유효 기간)을 계산해서 반환
function generateExpirationDate() {
    var currentDate = new Date();
    currentDate = currentDate.setDate(currentDate.getDate() + 1);
    return new Date(currentDate).toISOString();
}

//  보안정책을 생성
//  https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
function generatePolicyDocument(filename, next) {
    var expiration = generateExpirationDate();
    var dir = Math.floor(Math.random()*10**16).toString(16);
    var key = dir + '/' + filename;
    var policy = {
        'expiration': expiration,
        'conditions': [                                 
            { acl: `${C_ACL}` },                     
            { bucket: process.env.UPLOAD_BUCKET },
            [ 'starts-with', '$key', `${dir}/` ],       
            { 'x-amz-algorithm': `${C_X_AMZ_ALGORITHM}`},
            { 'x-amz-credential': `${C_X_AMZ_CREDENTIAL}` },
            { 'x-amz-date': `${C_X_AMZ_DATE}` }
        ]
    };
    next(null, key, policy);
}

//  보안정책을 BASE64로 인코딩해서 반환
function encode(key, policy, next) {
    var json = JSON.stringify(policy).replace('\n', '');
    var encodedPolicy = new Buffer(json).toString('base64');
    next(null, key, encodedPolicy);
}

//  서명 키(signing key) 생성
//  https://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html
function getSigningKey() {
    var dateKey = crypto.HmacSHA256(C_DATE_STAMP, "AWS4" + process.env.SECRET_ACCESS_KEY);
    var dateRegionKey = crypto.HmacSHA256(C_REGION_NAME, dateKey);
    var dateRegionServiceKey = crypto.HmacSHA256(C_SERVICE_NAME, dateRegionKey);
    var signingKey = crypto.HmacSHA256("aws4_request", dateRegionServiceKey);
    return signingKey;
}

//  서명 생성
//  https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
//  https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
function sign(key, encodedPolicy, next) {
    var signingKey = getSigningKey();
    var signature = crypto.HmacSHA256(encodedPolicy, signingKey);
    next(null, key, encodedPolicy, signature);
}

exports.handler = function(event, context, callback) {
    //  파라미터로 전달된 파일명을 추출
    var filename = null;
    if (event.queryStringParameters && event.queryStringParameters.filename) {
        filename = decodeURIComponent(event.queryStringParameters.filename);     
    } else {
        callback(null, createErrorResponse(500, '파일명이 누락되었습니다.'));
        return;
    }

    //  보안정책 생성 -> BASE64 인코딩 -> 서명 생성 => 키(임의폴더/파일명), BASE64로 인코딩된 보안정책, 서명값을 반환
    async.waterfall([ async.apply(generatePolicyDocument, filename), encode, sign ], function (err, key, encoded_policy, signature) {
        if (err) {
            callback(null, createErrorResponse(500, err));
        } else {
            //  POST 요청에서 사용할 값을 JSON 형식으로 반환
            var result = {
                upload_url: process.env.UPLOAD_URI,
                encoded_policy: encoded_policy, 
                key: key, 
                acl: `${C_ACL}`,
                x_amz_algorithm: `${C_X_AMZ_ALGORITHM}`, 
                x_amz_credential: `${C_X_AMZ_CREDENTIAL}`, 
                x_amz_date: `${C_X_AMZ_DATE}`,
                x_amz_signature: `${signature}`
            };
            callback(null, createSuccessResponse(result));
        }
    });
}

 

람다 함수 생성 및 배포

 

 

람다 함수 환경 변수 설정

 

람다 함수 테스트

 

 

728x90
반응형

댓글