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"
{
"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. 静的ウェブサイトホスティングの有効化
{
"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 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