{APIGateway}REST API リソースの CORS を有効にする


https://dev.classmethod.jp/articles/serverless-mailform-s3-website-hosting/
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-cors.html


S3(webhosting) --> API gateway(CORS) --> Lambda --> SES

 

-- 1. コマンド等のインストール

-- 1.1 aws cli version 2 インストール

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws --version

 

-- 1.2 jqインストール
sudo yum -y install jq

 

-- 2. Identityの作成

aws ses verify-email-identity \
--email-address hoge@example.net


aws ses list-identities

aws ses get-identity-verification-attributes \
--identities hoge@example.net

 

-- 3. 動作確認

aws ses send-email \
--from hoge@example.net \
--to hoge@example.net \
--subject "subject01" \
--text "text01"

 

-- 4. IAMロール作成
vim role01.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}


aws iam create-role \
--role-name role01 \
--assume-role-policy-document file://role01.json

-- 5. ポリシーをロールにアタッチ
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole \
--role-name role01

aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AmazonSESFullAccess \
--role-name role01


-- 6. Lambda関数作成

vim test.js

'use strict'
const SDK = require('aws-sdk');

exports.handler = (event, context, callback) => {
    const ses = new SDK.SES({ region: 'ap-northeast-1' });
    const adminaddress = 'hoge@example.net'
    const email = {
        Source: 'hoge@example.net',
        Destination: { 
            ToAddresses: [ adminaddress ]            
        },
        Message: {
            Subject: { Data: "フォームからのお問い合わせ" },
            Body: {
                Text: { Data: [
                    '[お問い合わせ表題] : ' + event.form['subject'],
                    '[メールアドレス] : ' + event.form['email'],
                    '[お問い合わせ本文] : ' + "\n" + event.form['body']
                ].join("\n")}
            },
        },
    };
    ses.sendEmail(email, callback);
};


chmod 755 test.js
zip test.zip test.js


aws lambda create-function \
--function-name func01 \
--zip-file fileb://test.zip \
--handler test.handler \
--runtime nodejs14.x \
--role arn:aws:iam::999999999999:role/role01

 

aws lambda list-functions | grep func01
aws lambda get-function --function-name func01


-- 7. API を作成する

aws apigateway create-rest-api \
--name api01 \
--description "api01" \
--endpoint-configuration '{"types": ["REGIONAL"]}'

aws apigateway get-rest-apis

aws apigateway get-rest-api \
--rest-api-id 1111111111

 

-- 8. API でリソースを作成する
aws apigateway get-resources \
--rest-api-id 1111111111

aws apigateway create-resource \
--rest-api-id 1111111111 \
--parent-id 2222222222 \
--path-part "send"

 

 

-- 9. リソースにPOSTメソッドを作成する

 

aws apigateway put-method \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method POST \
--authorization-type NONE \
--no-api-key-required \
--request-parameters {}


aws apigateway put-method-response \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method POST \
--status-code 200 \
--response-parameters '{
                "method.response.header.Access-Control-Allow-Origin": false
            }' \
--response-models '{"application/json": "Empty"}'


vim request_templates.json
{
  "application/json": "{\r\n    \"form\": {\r\n        \"subject\":  \"$util.escapeJavaScript($input.path('$.subject'))\",\r\n        \"email\":  \"$util.escapeJavaScript($input.path('$.email'))\",\r\n        \"body\":  \"$util.escapeJavaScript($input.path('$.body'))\"\r\n    }\r\n}"
}

 

aws apigateway put-integration \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method POST \
--type AWS \
--integration-http-method POST \
--content-handling CONVERT_TO_TEXT \
--uri "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:999999999999:function:func01/invocations" \
--passthrough-behavior "WHEN_NO_MATCH" \
--request-templates file://request_templates.json

 

aws apigateway put-integration-response \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method POST \
--status-code 200 \
--response-parameters "{
                \"method.response.header.Access-Control-Allow-Origin\": \"'*'\"
            }" \
--selection-pattern "" 

 

aws apigateway get-method \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method POST


-- 10. リソースにOPTIONSメソッドを作成する

※CORSのためにOPTIONSメソッド必要


aws apigateway put-method \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method OPTIONS \
--authorization-type NONE \
--no-api-key-required \
--request-parameters {}


aws apigateway put-method-response \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method OPTIONS \
--status-code 200 \
--response-parameters '{
                "method.response.header.Access-Control-Allow-Headers": false,
                "method.response.header.Access-Control-Allow-Methods": false,
                "method.response.header.Access-Control-Allow-Origin": false
            }' \
--response-models '{"application/json": "Empty"}'


aws apigateway put-integration \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method OPTIONS \
--type MOCK \
--passthrough-behavior "WHEN_NO_MATCH" \
--request-templates '{
            "application/json": "{\"statusCode\": 200}"
        }'


aws apigateway put-integration-response \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method OPTIONS \
--status-code 200 \
--response-parameters "{
                    \"method.response.header.Access-Control-Allow-Headers\": \"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'\",
                    \"method.response.header.Access-Control-Allow-Methods\": \"'OPTIONS,POST'\",
                    \"method.response.header.Access-Control-Allow-Origin\": \"'*'\"
                }" 
            


aws apigateway get-method \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method OPTIONS

 

 

-- 11. Lambda関数に権限を追加する

aws lambda add-permission \
--function-name func01 \
--statement-id apigw \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn arn:aws:execute-api:ap-northeast-1:999999999999:1111111111/*/POST/send


aws lambda get-policy \
--function-name func01 | jq -r .Policy  | jq .


-- 12. デプロイ前にAPI をテストする

aws apigateway test-invoke-method \
--rest-api-id 1111111111 \
--resource-id 333333 \
--http-method POST \
--body '{
    "subject":  "subject01",
    "email":  "email01",
    "body":  "body01"
}'

 

-- 13. API をデプロイする


aws apigateway get-deployments \
--rest-api-id 1111111111

aws apigateway get-stages \
--rest-api-id 1111111111


aws apigateway create-deployment \
--rest-api-id 1111111111

aws apigateway create-stage \
--rest-api-id 1111111111 \
--stage-name stage01 \
--deployment-id 444444

 

-- 14. S3 バケットを作成する

aws s3 ls

aws s3 mb s3://bucket123

-- 15. 静的ウェブサイトホスティングの有効化

vim a.json

{
    "IndexDocument": {
        "Suffix": "index.html"
    }
}

aws s3api put-bucket-website \
--bucket bucket123 \
--website-configuration file://a.json

aws s3api get-bucket-website \
--bucket bucket123

 


-- 16. パブリックアクセスブロック設定の編集

-- 16.1 アカウントレベル
aws s3control put-public-access-block \
--account-id 999999999999 \
--public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"

aws s3control get-public-access-block \
--account-id 999999999999

-- 16.2 バケットレベル
aws s3api put-public-access-block \
--bucket bucket123 \
--public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"

aws s3api get-public-access-block \
--bucket bucket123


-- 17. バケットポリシーの設定

vim b.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::bucket123/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": "192.0.2.1/32"
                }
            }
        }
    ]
}

IPアドレス制限追加


aws s3api put-bucket-policy \
--bucket bucket123 \
--policy file://b.json


aws s3api get-bucket-policy \
--bucket bucket123


-- 18. インデックスドキュメントの設定

vim index.html

<!DOCTYPE html>
<html>
<head>
    <title>サーバーレスお問い合わせフォーム テスト</title>
    <meta charset=utf-8>
    <meta name=viewport content="width=device-width, initial-scale=1">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" rel=stylesheet />
</head> 
<body>
    <div class="container" style="max-width:960px; margin:60px;">
        <h2 class="title is-4">お問い合わせフォーム</h2>
        <form class=field method=post id=mailForm> 
            <input class=input name=subject placeholder="お問い合わせの表題"> 
            <input class=input name=email placeholder="メールアドレス"> 
            <textarea class=textarea name=body placeholder="お問い合わせ本文"></textarea> 
            <div><button class="button is-midium is-link center" id=button type=submit>送信する</button></div> 
        </form>
    </div>

    <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery-validation@1.19.1/dist/jquery.validate.min.js"></script> 
    <script src="function.js"></script> 
</body> 
</html>


aws s3api put-object --bucket bucket123 --key index.html --body index.html --content-type text/html

 


-- 19. JavaScriptの設定
vim function.js

$(function(){
  ///// Eメールの送信処理
  $.validator.setDefaults({
    submitHandler: function() {
      $('form').on("submit", function(){
        $('#button').text('送信中です');
        var data = $('form').serializeArray();
        data = parseJson(data);

        $.ajax({
          url:           'https://1111111111.execute-api.ap-northeast-1.amazonaws.com/stage01/send',
          type:          'post',
          dataType:      'json',
          contentType:   'application/json',
          scriptCharset: 'utf-8',
          data:          JSON.stringify(data)
        })
        .then(
          function (data) {
            ///// 送信成功時の処理
            alert('送信に成功しました');
            location.reload();
          },
          function (data) {
            ///// 送信失敗時の処理
            alert('送信に失敗しました');
            location.reload();
        });    
      })
      var parseJson = function(data) {
        var returnJson = {};
        for (idx = 0; idx < data.length; idx++) {
          returnJson[data[idx].name] = data[idx].value
        }
        return returnJson;
      }

    }
  });
  ///// フォームの入力チェック
  $("#mailForm").validate({
    errorElement: "span",
    errorClass: "alert",
    rules: {
      'subject': {
          required: true,
          maxlength: 50
      },
      'email': {
          required: true,
          email: true
      },
      'body': {
          required: true
      }
    },
    messages: {
      'subject': {
          required: "表題を入力してください",
          maxlength: "表題は50文字以内にしてください"
      },
      'email': {
          required: "メールアドレスを入力してください",
          email: "有効なメールアドレスを入力してください"
      },
      'body': {
        required: "本文を入力してください"
      }
    }
  });
});


aws s3api put-object --bucket bucket123 --key function.js --body function.js --content-type text/javascript

aws s3 ls bucket123 --recursive

 

-- 20. 動作確認

フォーム登録してメールが届くか確認

http://bucket123.s3-website-ap-northeast-1.amazonaws.com/

 

 

-- 21. クリーンアップ

-- バケットの削除
aws s3 ls

aws s3 rb s3://bucket123 --force

-- アカウントレベルのパブリックアクセスブロックの有効化

aws s3control put-public-access-block \
--account-id 999999999999 \
--public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

aws s3control get-public-access-block \
--account-id 999999999999


-- API削除

aws apigateway get-rest-apis

aws apigateway delete-rest-api \
--rest-api-id 1111111111

 

-- Lambda関数の削除

aws lambda get-function --function-name func01
aws lambda delete-function --function-name func01


-- IAMロールの削除
aws iam list-roles | grep role01

aws iam detach-role-policy \
--role-name role01 \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

aws iam detach-role-policy \
--role-name role01 \
--policy-arn arn:aws:iam::aws:policy/AmazonSESFullAccess


aws iam delete-role --role-name role01

 

-- identityの削除

aws ses list-identities

aws ses delete-identity \
--identity hoge@example.net