[AWS]フォーム付き静的コンテンツを独自SSL/独自ドメインで公開するまで②-lambda/Cognito編

お問い合わせフォーム付きWeサイトを

- S3で
- Slack通知ありで
- 独自ドメインで(お名前.comよりCNAMEを利用)
- 独自SSLで（https/SNIベース）

公開するまでの備忘録
どこにでもやり方やリンクは転がってますが個人的にまとめます。

前回でS3での静的コンテンツ配信は可能となりましたので、
次は**lambda/Cognito**を使って問い合わせフォームより送信があった場合は**slack通知**をしましょう。

流れは、
Cognitoで一時的なIAM Userを作成して、
そのアカウントを使ってフォームの入力内容をS3でテキストでアップロード、
S3のアップロードをLambdaでフックして内容をSlack通知です。

各リンクは以下  

* [[AWS]フォーム付き静的コンテンツを独自SSL/独自ドメインで公開するまで①-S3編](http://qiita.com/tutida/items/bfc3865f2035483d2c43)
* [[AWS]フォーム付き静的コンテンツを独自SSL/独自ドメインで公開するまで③-CloudFront編](http://qiita.com/tutida/items/7a0396008d19d9051511)

## 技術要件

- AWS
 - S3
 - aws-cli
 - **Cognito** <-今回
 - **Lambda** <-今回
 - **aws-sdk** <-今回
 - CloudFront
- dnsまわり

## 参考リンク
[S3 + Lambda + Cognitoを使って、簡単お問い合わせシステム構築](http://qiita.com/takuma_yoshida/items/3bd3af8b09307d25981a)

## Slack
### Incoming Webhook取得
### 1.Slackログイン状態で、https://api.slack.com/incoming-webhooks にアクセス

以下をクリック
![2015-12-22_21h46_59.png](https://qiita-image-store.s3.amazonaws.com/0/74129/398a065b-51ab-fcd5-626d-3182a3e1c203.png)

### 2.通知をしたいチャンネルまたは個人を選択
![2015-12-22_21h49_31.png](https://qiita-image-store.s3.amazonaws.com/0/74129/02b56605-aedc-0218-3e7a-e32468b28208.png)

### 3.Incoming Webhook用のURLを取得
以下のURLをちゃんとメモしておいてください
![2015-12-22_21h52_01.png](https://qiita-image-store.s3.amazonaws.com/0/74129/3ba53ea8-d32a-a42d-7b41-12952f7e4561.png)

必要があればアイコンや名前など上記ページの下「Integration Settings」でいじれます。

## Cognito
S3にアップロードするための一時的なIAM Userを作成するためにCognitoを使用します。
### 1.Identity Poolの作成
Amazon Management Consoleより「Cognito」を選択。
「Create New Identity Pool」
「Name」は適用に分かりやすいやつを
「Unauthenticated identities」にチェックを入れましょう
特定の認証（ID/PASSだったり、Facebook認証だったり）をしないでもIAM Userを作成できるようにするため
![2015-12-22_22h09_18.png](https://qiita-image-store.s3.amazonaws.com/0/74129/06cd71b1-ab70-6f7c-90fb-87834b60cdb1.png)

### 2.新しいIAM Roleの確認
上が「authenticated role」
認証をした場合のIAM Role
下が「unauthenticated role」
認証をしなかった場合のIAM Role

それぞれ新しく作成されます。
![2015-12-22_22h10_51.png](https://qiita-image-store.s3.amazonaws.com/0/74129/40dc7bcc-ec86-606f-baf9-c8717c90d8a0.png)

### 3.Identity Pool Id の取得
ここにあるIDを後ほど使います。
![2015-12-22_22h11_21.png](https://qiita-image-store.s3.amazonaws.com/0/74129/73971f36-fe94-c6aa-e1df-039e932a6095.png)

### 4.作成したunauthenticated Roleに権限を付与
Amazon Management Consoleより「Identity and Access Management」を選択。
左メニュー「Role」より先ほど作成した「UnAuth」のロールをクリック
![2015-12-22_22h12_53.png](https://qiita-image-store.s3.amazonaws.com/0/74129/7ebb5578-9169-57b6-27f9-c507084c9b5a.png)

インラインポリシー　「ロールポリシーの作成」
![2015-12-22_22h13_29.png](https://qiita-image-store.s3.amazonaws.com/0/74129/c5d5ac63-3375-2e6d-f177-1681899a29c0.png)

今回は以下のようなポリシーを追加 : 特定のBucketへのアップロード権限

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::example.co.jp/*" <- アクセス許可するBucket
        }
    ]
}
```
## aws-sdk-js
### フォームのための処理
アップロードする箱と権限は設定したの実際にアップロードしてみましょう。
**aws-sdkを使うのでHTMLのヘッダで読み込んでおきましょう。**
今回はCDNで読み込みます。

```
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
```

以下のようなHTMLとJSをS3に置きます。
こんな感じのフォームがあったとします（あくまで例です）

```
<div>
    <input type="text" id="ContactCompanyName"/>
    <input type="text" id="ContactName"/>
    <input type="text" id="ContactEmail"/>
    <input type="text" id="ContactTel"/>
    <textarea cols="40" rows="4" id="ContactDetailBody"></textarea>
    <a href="#" id="put_contact"><img src="/send_btn.gif" alt="送信"/></a>
</div>
```

aws-sdkを使って送信ボタンがクリックしたときにフォームの内容をuploadします

```
$('#put_contact').on('click', function() {
    AWS.config.region = "ap-northeast-1";
    AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: "取得した Identity Pool Id"});
    AWS.config.credentials.get();

	//作成したBucketを指定しましょう
    var bucket = 'example.co.jp';
    var s3 = new AWS.S3({
        params: {
            Bucket: bucket
        }
    });

    var now = new Date();
    var obj = {
        "問い合わせ時間" : now.toLocaleString(),
        "御社名" : $("#ConfirmContactCompanyName").text(),
        "お名前" : $("#ConfirmContactName").text(),
        "メールアドレス" : $("#ConfirmContactEmail").text(),
        "電話番号" : $("#ConfirmContactTel").text(),
        "お問い合わせ内容" : $("#ConfirmContactDetailBody").text(),
    };

    var blob = new Blob([JSON.stringify(obj, null, 2)], {type:'text/plain'});

	//Bucketの「contacts」フォルダの下にアップロード
    s3.putObject({Key: "contacts/" + now.getTime()+".txt", ContentType: "text/plain", Body: blob},
    function(err, data){
        if(data !== null){
             window.location.href = '/contact_end.html';
        }
        else{
            alert("お問い合わせ送信に失敗しました。お手数ですが再度ご入力をお願いします。");
            window.location.href = '/contact.html';
        }
    });
});
```

### S3にCORSの設定
アップロードまでもう少し。
JavaScriptでS3にアクセスするにはCORSの設定が必要となります。
CORS…http://dev.classmethod.jp/etc/about-cors/
Amazon Management ConsoleからS3で作成したBucketに行きましょう
![2015-12-22_23h06_37.png](https://qiita-image-store.s3.amazonaws.com/0/74129/085a4fd1-4688-4776-9c17-27307fe397f8.png)
ここにこんな感じに記述

```
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>アクセス元（今回はBucketの静的コンテンツ配信のためのエンドポイントのURL）</AllowedOrigin>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

```

さて、疲れましたね。
もうアップロードは出来るはず。

## lambda
アップロードは出来たので最後にSlack通知のためのlambda functionを設置します。
### 1.lambda function作成のための準備
npmをインストール（CentOS6の場合）

```
yum install epel-release
yum install nodejs npm --enablerepo=epel
```

npmより「reqest」「aws-sdk」をインストール

* request…HTTPリクエストを簡単にするため
* aws-sdk…S3からアップロードされたファイルの内容取得のため

```
npm install aws-sdk
npm install request
```

### 2.コードサンプル
index.jsを「node_modules」と同じ階層に作成

```
var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var request = require('request');

exports.handler = function(event, context) {
    var bucket = event.Records[0].s3.bucket.name;
    var key = event.Records[0].s3.object.key;
    s3.getObject({Bucket:bucket, Key:key}, function(err, data) {

        //アップロードされたファイルの内容を取得
        var contentType = data.ContentType;
        var body = data.Body.toString("utf-8");

        var options = {
            uri: '取得したSlackのIncoming Webhook URL',
            json: true
        };

        if (key.match(/^contacts/)) {
            options['form'] = {
                payload: JSON.stringify({
                    'username': 'webhookのusername',
                    'channel': '通知するchannel',
                    'text': 'お問い合わせがありました。'
                })
            };
        }

        request.post(options, function(err, responce, body) {
            if(!err && responce.statusCode == 200) {
                console.log('success');
            } else {
                console.log('error: ' + responce.statusCode);
            }
            context.done(null, '');
        });
    });
};
```

### 3.zip化
「index.html」と「node_modules」をzip化しましょう

```
zip -r contact.zip index.js node_modules
```

### 4.lambda用のIAM Roleを作成
デプロイ前にひと手間
今回、lambda function内でS3の中身を取得しているので、lambda用のRoleを作成しましょう。

Amazon Management Consoleより「Identity and Access Management」を選択。
左メニュー「Role」より新しいRoleを作成しましょう
Role名は適当に分かりやす名前で

![2015-12-22_23h37_26.png](https://qiita-image-store.s3.amazonaws.com/0/74129/6e250fbb-af1a-a036-1c2e-15d0a5764e35.png)

Role type でLambdaを選択

![2015-12-22_23h40_07.png](https://qiita-image-store.s3.amazonaws.com/0/74129/b28c0c8b-6638-3c03-d72a-da86c1fa6a8c.png)

S3のReadOnlyのポリシーを付与

![2015-12-22_23h47_46.png](https://qiita-image-store.s3.amazonaws.com/0/74129/ba6d23cb-7e23-7036-48ca-49bbaa14a60a.png)

これで、lambda用のRoleが完成

### 5.lambdaにデプロイ

さて、いよいよデプロイです。

Amazon Management Consoleより「lambda」を選択
「Create a Lambda Function」
色々雛形が出てきますが、今回はスルー

![2015-12-22_23h49_34.png](https://qiita-image-store.s3.amazonaws.com/0/74129/2f0fcfcc-fbb3-1894-801f-432288682fbc.png)

名前は適当に分かりやすいものを
Upload a ZIP fileで先ほど作成したlambda functionをまとめたzipをアップロード
Roleに先ほど作成したlambda用のRoleを選択します。

![説明](/system/media/files/000/000/938/original/2015-12-22_23h50_42.png?1450796605)![2015-12-22_23h50_42.png](https://qiita-image-store.s3.amazonaws.com/0/74129/fc6fdbd4-9f8e-4e8e-d812-a9f8b4f4f424.png)

最終確認画面でEnableを選択すればlambda functionが有効となります。
ただ最後に最後にあと一仕事
lambdaが何のEventにhookされるのか設定しましょう。

作成されたlambda functionの「Event source」を選択
「Add event source」でhookするポイントを追加します

![2015-12-23_00h10_35.png](https://qiita-image-store.s3.amazonaws.com/0/74129/7e950b40-de51-d913-0110-7a67caaecfc0.png)

BucketはアップロードするBucket
Event typeはS3へオブジェクトがクリエイトされた時
prefixは今回は「contacts/」にアップロードされるので記入しているだけです

![2015-12-23_00h11_25.png](https://qiita-image-store.s3.amazonaws.com/0/74129/e0414ee5-9cc8-cf53-e57e-d6c41726ad95.png)

## ここまででサイトとしては機能している

さて、長くなりましたが今回はここで終了です。
きっと静的コンテンツ越しにアップロードされたファイルがslackで通知されている（ﾊｽﾞ

次はamazonから与えられるエンドポイント用の長いURLではなく
独自のドメインでサイトにアクセスするのと、通信のSSL化です。

* [[AWS]フォーム付き静的コンテンツを独自SSL/独自ドメインで公開するまで③-CloudFront編](http://qiita.com/tutida/items/7a0396008d19d9051511)