728x90

S3 부가 기능 (P226)

버전 관리

  • 객체를 덮어 쓴 경우, 이전 버전으로 돌아갈 수 있음
  • 버전 관리 기능은 활성화해서 사용
  • 버전 관리를 해제할 수는 없음 ⇒ 일시 중지는 가능

 

정적 웹 사이트 호스팅

  • 서버 측 코드 실행은 지원하지 않음
  • HTML, CSS, Image, JavaScript와 같은 정적인 컨텐츠를 제공(서비스)

 

스토리지 클래스

중복성, 액세스 특징 및 가격 정책에 따라 네 가지 스토리지 클래스를 지원

요금은 저장 비용(스토리지 클래스, 리전, 저장된 데이터의 양)과 요청 비용(요청과 데이터 전송)으로 구분

  • Standard
    • 데이터에 자주 액세스할 때 적합
    • 99.999999999%의 내구성을 지원
    • 저장 비용 → 10,000건의 요청당 $0.004
    • 사용 비용 → 처음 1TB의 데이터/월 사용 시 GB 당 $0.0300
  • Standard_IA(Infrequent Access)
    • 자주 액세스하지 않는 데이터에 적합
  • Glacier
    • 간헐적으로 접근
    • 데이터를 가져오는데 3~5시간 소요
    • 처음부터 Glacier 클래스로 객체를 생성할 수 없음 ⇐ 생명주기 관리 규칙을 사용해서 Glacier로 전환
  • Reduced Redundancy
    • 99.99% 내구성 지원



웹 페이지를 통해 인증된 사용자만 S3 버킷에 파일을 업로드할 수 있도록 제한 (P238)

Lambda 함수 ⇒ 사용자를 확인하고 S3에 파일을 업로드하는데 필요한 정책과 서명을 생성



#1 upload-s3 사용자 생성 및 업로드 정책 생성

 

 

{

    "Version": "2012-10-17",

    "Statement": [

        {

            "Resource": "arn:aws:s3:::serverless-video-upload-myanjini", ⇐ 업로드 버밋의 ARN

            "Action": "s3:ListBucket",

            "Effect": "Allow"

        }, 

        {

            "Resource": "arn:aws:s3:::serverless-video-upload-myanjini/*", 

            "Action": "s3:PutObject",

            "Effect": "Allow"

        }

    ]

}

 

 




#2 람다 함수 생성 및 배포

#2-1 작업 디렉터리 생성

PS C:\Users\i> cd C:\serverless\

PS C:\serverless> mkdir get-upload-policy

PS C:\serverless> cd .\get-upload-policy\

PS C:\serverless\get-upload-policy> npm init -y

 

#2-2 모듈 설치

PS C:\serverless\get-upload-policy> npm install -y async

PS C:\serverless\get-upload-policy> npm install -y crypto-js



#2-3 package.json 파일에 create, precreate 스크립트를 작성

{

  "name": "get-upload-policy",

  "version": "1.0.0",

  "description": "",

  "main": "index.js",

  "scripts": {

    "create": "aws lambda create-function --function-name get-upload-policy --handler index.handler --memory-size 128 --runtime nodejs12.x --role arn:aws:iam::199503606661:role/lambda-s3-execution-role --timeout 3 --publish --zip-file fileb://Lambda-Deployment.zip",

    "precreate": "zip -r Lambda-Deployment.zip * -x *.zip *.log node_modules/aws-sdk/*"

  },

  "keywords": [],

  "author": "",

  "license": "ISC",

  "dependencies": {

    "async": "^3.2.0",

    "crypto-js": "^4.0.0"

  }

}



#2-4 람다 함수 작성

c:\serverless\get-upload-policy\index.js

// P242

'use strict';



var async = require('async');

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



// 상수를 정의 

const C_ACL = 'private';    

const C_NOW = new Date().toISOString();                     // 현재 시간 2021-03-19T02:04:38.030Z

const C_DATE_STAMP = C_NOW.slice(0,10).replace(/-/g,'');    // 현재 시간을 YYYYMMDD 형식으로 변환 2021-03-19 => 20210319

const C_REGION_NAME = 'us-east-1';  

const C_SERVICE_NAME = 's3';

const C_X_AMZ_DATE = C_NOW.replace(/[-:\.]/g,'');           // 20210319T020438030Z

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(정책 유효 기간)을 계산해서 반환하는 함수

// 다음 날을 ISO 형식으로 반환

// https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate

function generateExpirationDate() {

    var currentDate = new Date();

    currentDate = currentDate.setDate(currentDate.getDate() + 1);

    //                                ~~~~~~~~~~~~~~~~~~~~~~~~~

    //                                현재 일자 + 1 => 다음 날

    return new Date(currentDate).toISOString();

}



// 보안정책문서를 생성

function generatePolicyDocument(filename, next) {

    var expiration = generateExpirationDate();              // 정책 유효기간

    var dir = Math.floor(Math.random()*10**16).toString(16);// 디렉터리 명으로 사용할 난수를 생성

    var key = dir + '/' + filename;                         // 키 이름을 설정

    // https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html

    var policy = {

        'expiration': expiration,

        'conditions': [                                 

            { acl: `${C_ACL}` },                     

            { bucket: process.env.UPLOAD_BUCKET },

            [ 'starts-with', '$key', `${dir}/` ],           // 키 이름이 ${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);                                // waterfall 함수에 따라 encode 함수가 호출

}



// 보안정책문서의 포맷을 변경 : 문자열 -> JSON -> 개행문자를 제거 -> BASE64로 인코딩

function encode(key, policy, next) {

    var json = JSON.stringify(policy).replace('\n', '');

    //         ~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~

    //         JSON 형식으로 변환      개행문자를 제거

    var encodedPolicy = new Buffer(json).toString('base64');

    //                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    //                  BASE64로 인코딩 

    next(null, key, encodedPolicy);                         // waterfall 함수에 따라 sign 함수를 호출

}



// 서명 키를 생성

// https://docs.aws.amazon.com/ko_kr/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

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;

    }



    async.waterfall([ async.apply(generatePolicyDocument, filename), encode, sign ], function (err, key, encoded_policy, signature) {

        if (err) {

            callback(null, createErrorResponse(500, err));

        } else {

            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));

        }

    });

}

 



#2-5 람다 함수 생성 및 배포

PS C:\serverless\get-upload-policy> npm run create

 

#2-6 람다 함수 실행에 필요한 환경변수를 설정

 

ACCESS_KEY, SECRET_ACCESS_KEY ⇒ upload-s3 사용자의 액세스 키 ID, 비밀 액세스 키

 

UPLOAD_URI ⇒ https://업로드버킷이름.s3.amazonaws.com

UPLOAD_BUCKET ⇒ 업로드 버킷 이름

 

#2-7 람다 함수 테스트

{

  "queryStringParameters": {

      "filename": "myvideo.mp4"

  }

}

 



https://myanjini.tistory.com/entry/POST-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-S3-%EB%B2%84%ED%82%B7%EC%97%90-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-1

https://myanjini.tistory.com/entry/POST-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-S3-%EB%B2%84%ED%82%B7%EC%97%90-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-2

 

#3 API Gateway 설정

#1 24-hour-video API 생성



#2 s3-policy-document 리소스 생성



#3 GET 메소드 생성

 



#4 API 배포

 




#4 웹 사이트에 업로드 기능을 추가

c:\serverless\24-hour-video\index.html

      <div class="container" id="video-list-container">

        <div id="video-template" class="col-md-6 col">

          <div class="video-card">

            <!-- 동영상을 재생해 주는 요소(태그) -->

            <video width="100%" height="100%" controls autoplay>

              <!-- video 태그에 사용될 동영상의 소스 -->

              <!-- src 속성에 동영상의 주소를 추가하면 재생이 가능 -->

              <source type="video/mp4">

              지원하지 않는 타입

            </video>

          </div>

        </div>

        <div calss="row">

          <!-- S3 버킷에 저장된 동영상의 목록을 제시하고 

               동영상 이름을 클릭하면 video 태그를 통해서 동영상 재생 -->

          <ul id="video-list">

            

          </ul>

        </div>

      </div>

      <!-- 시작: P249 참고 업로드 버튼 및 진행률 표시 (대략 94라인) -->

      <span id="upload-video-button" class="btn btn-info btn-file">

        <span class="glyphicon glyphicon-plus"></span>

        <input id="upload" type="file" name="file"><!-- 파일 선택 창 -->

      </span>

      <div class="progress" id="upload-progress">

        <div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar" aria-valuemin="0" aria-valuemax="100"></div><!-- 진행률 표시 -->

      </div>

      <!-- 끝 -->




c:\serverless\24-hour-video\js\upload-controller.js

var uploadController = {

    data: {

        config: null

    }, 

    uiElements: {

        uploadButton: null

    }, 

    init: function(configConstants) {

        this.data.config = configConstants;

        this.uiElements.uploadButton = $('#upload');

        this.uiElements.uploadButtonContainer = $('#upload-video-button');

        this.uiElements.uploadProgressBar = $('#upload-progress');

        this.wireEvents();

    }, 

    wireEvents: function() {

        var that = this;



        // 파일 선택창의 내용이 변경된 경우 수행할 기능을 정의

        this.uiElements.uploadButton.on('change', function(result) {

            // 선택한 파일 정보를 가져와서 변수에 할당

            // file = {name: "수행평가 - 1조.pdf", lastModified: 1616109336996, lastModifiedDate: Fri Mar 19 2021 08:15:36 GMT+0900 (대한민국 표준시), webkitRelativePath: "", size: 5272297, …}

            var file = $('#upload').get(0).files[0];

            // 24-hour-video API의 s3-policy-document 리소스에 filename 파라미터의 값으로 선택한 파일의 이름을 전달

            var requstDocumentUrl = that.data.config.getUploadPolicyApiUrl + '/s3-policy-document?filename=' + encodeURI(file.name);

            $.get(requstDocumentUrl, function(data, status) {

                // file: 선택한 파일 정보

                // data: get-upload-policy 람다 함수의 실행 결과 = S3 버킷 업로드 정책 문서 

                that.upload(file, data, that);

            });

        });

    }, 

    // 파일 선택창에서 선택한 파일을 S3 버킷으로 업로드 

    upload: function(file, data, that) {

        // 파일 선택창을 숨기고, 진행률 창을 보이게 처리

        this.uiElements.uploadButtonContainer.hide();

        this.uiElements.uploadProgressBar.show();

        this.uiElements.uploadProgressBar.find('.progress-bar').css('width', '0');



        // 요청 본문을 생성

        var fd = new FormData();

        fd.append('key', data.key);

        fd.append('policy', data.encoded_policy);

        fd.append('acl', data.acl);

        fd.append('x-amz-algorithm', data.x_amz_algorithm);

        fd.append('x-amz-credential', data.x_amz_credential);

        fd.append('x-amz-date', data.x_amz_date);

        fd.append('x-amz-signature', data.x_amz_signature);

        fd.append('file', file, file.name);        

        $.ajax({

            url: data.upload_url,

            type: 'POST', 

            data: fd, 

            processData: false,

            contentType: false, 

            xhr: this.progress,

            // ajax 요청을 전달하기 전에 수행해야 할 작업을 명시

            // Authorization 요청 헤더의 값을 제거

            beforeSend: function(req) {

                req.setRequestHeader('Authorization', '');

            }

        })

        // 업로드에 성공한 경우 수행할 내용

        .done(function(response) {

            that.uiElements.uploadButtonContainer.show();

            that.uiElements.uploadProgressBar.hide();

            alert('업로드 성공');

        })

        // 업로드에 실패한 경우 수행할 내용

        .fail(function(response) {

            that.uiElements.uploadButtonContainer.show();

            that.uiElements.uploadProgressBar.hide();

            console.error(response);

            alert('업로드 실패');

        })

    }, 

    // 진행율을 표시

    progress: function() {

        var xhr = $.ajaxSettings.xhr();

        xhr.upload.onprogress = function(evt) {

            var percentage = evt.loaded / evt.total * 100;

            $('#upload-progress').find('.progress-bar').css('width', percentage + '%');

        };

        return xhr;

    }

}

 



c:\serverless\24-hour-video\js\config.js

var configConstants = {

    auth0: {

        domain: 'naanjini.us.auth0.com',

        clientId: 'BAdEeZTkFlAdyC1a7nWENB3sRydpByXR'

    },

    // user-profile API 호출 URL

    apiBaseUrl: 'https://vfhp67njsk.execute-api.us-east-1.amazonaws.com/dev', 



    // get-file-list API 호출 URL

    getFileListApiUrl: 'https://6ln109dh77.execute-api.us-east-1.amazonaws.com/dev', 



    // 추가 get-upload-policy API 호출 URL

    getUploadPolicyApiUrl: 'https://ehw3hrbl7a.execute-api.us-east-1.amazonaws.com/dev'

};



c:\serverless\24-hour-video\js\main.js

// 즉시 실행 함수

(function() {

    // 해당 웹 페이지 문서가 로딩되면 설정 정보를 가져와서 설정

    $(document).ready(function() {

        // user-controller.js에 선언되어 있는 userController 객체의 init 메소드를 호출

        // coonfig.js에 선언되어 있는 configConstants 객체를 인자로 전달

        userController.init(configConstants);

        videoController.init(configConstants);  

        uploadController.init(configConstants); /* 추가 */

    });

})();



c:\serverless\24-hour-video\index.html

        <!-- (생략) -->

        <script src="https://cdn.auth0.com/js/lock/11.27/lock.min.js"></script>

        <script src="js/config.js"></script>

        <script src="js/user-controller.js"></script>

        <script src="js/video-controller.js"></script>  

        <script src="js/upload-controller.js"></script><!-- 추가 -->

        <script src="js/main.js"></script>

        <!-- (생략) -->



c:\serverless\24-hour-video\css\main.css

 

/* 업로드 버튼 및 진행률 표시를 위한 스타일을 추가 */



#upload-video-button {

  display: none;

  margin-bottom: 30px;

}



.btn-file {

  position: relative;

  overflow: hidden;

}



.btn-file input[type=file] {

  position: absolute;

  top: 0;

  right: 0;

  min-width: 100%;

  min-height: 100%;

  font-size: 100px;

  text-align: right;

  filter: alpha(opacity=0);

  opacity: 0;

  outline: none;

  background: white;

  cursor: inherit;

  display: block;

}



#upload-progress {

  display: none;

}



#video-list-container {

  text-align: center;

  padding: 30px 0 30px;

}



.progress {

  background: #1a1a1a;

  margin-top: 6px;

  margin-bottom: 36px;

}

 




#6 파일 업로드 테스트 

Access to XMLHttpRequest at 'https://ehw3hrbl7a.execute-api.us-east-1.amazonaws.com/dev/s3-policy-document?filename=%EC%88%98%ED%96%89%ED%8F%89%EA%B0%80%20-%205%EC%A1%B0.pptx' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

⇒ 24-hour-video API의 CORS 활성화 후 재배포

 




⇒ AccessToken 헤더를 허용하도록 CORS 설정하지 않아서 CORS 오류가 발생

 



"업로드 실패" ⇒ 파일을 S3 버킷에 업로드했을 때 나오는 메시지 ⇒ s3-upload-policy 람다 함수 호출(실행)은 완료되었음을 의미

 

Access to XMLHttpRequest at 'https://s3.amazonaws.com/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

⇒ S3 버킷에 CORS 설정이 필요



#5 업로드 S3 버킷에 CORS 설정

참고 ⇒ https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/cors.html

 

 

[

    {

        "AllowedHeaders": [

            "*"

        ],

        "AllowedMethods": [

            "POST"

        ],

        "AllowedOrigins": [

            "*"

        ],

        "ExposeHeaders": [], 

        "MaxAgeSeconds": 3000

    }

]



#7 파일 업로드 테스트



실습 리소스 정리

EC2

 

 

 

볼륨 ⇒ 볼륨 분리 → 볼륨 삭제

 




종료된 인스턴스 1개와 기본 보안 그룹 1개만 보여야 함




Elastic Transcoder 파이프라인 삭제 확인

 

S3 버킷 삭제

버킷을 비운 후 삭제

 

API Gateway

API 삭제

 

Lambda



CloudWatch



IAM

 





DevStack 설치

https://myanjini.tistory.com/entry/DevStack-%EC%84%A4%EC%B9%98

 

728x90

'CLOUD > AWS' 카테고리의 다른 글

3/19 - AWS 마무리 정리  (0) 2021.03.19
3/17 - AWS 14차시  (1) 2021.03.17
3/16 - AWS 13차시  (0) 2021.03.16
3/15 - AWS 12차시  (0) 2021.03.15
3/12 - AWS 11차시  (0) 2021.03.12

+ Recent posts