S3 + Cognito + DynamoDBでサーバレスな簡易コメント投稿システム

こんにちは。独りAdventCalendar風ブログ更新の12月5日分の記事です。4日分の投稿が遅かったので間が開かずの更新です。

今回参考にさせていただいたのはQiitaに投稿されているこちらの記事。qiita.com

はてブもたくさんついていて、良い記事だとういことがうかがえます。それにびんj(ryというわけではなく、最近はサーバレスなシステムをどうやって作っていったらいいかと考えていたりとか、触っていないAWSのサービスが多いのでなんとかしていろいろ触ってみたいなと思っていたりすることが重なってこのネタに至ったわけです。

S3バケットの準備

コメントシステムのHTMLを配置するためにStatic Website Hostingを利用します。Bucket名は「matetsu.example.com」とし、PropertiesのStatic Website Hositing(静的ウェブサイトホスティング)メニューから「Enable website hosting」を選択し、必要な項目を埋めて「Save」でひとまず完了。

Static Website Hostingの設定方法については、こちらが参考になるかと思います。

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Coginitoの設定

Cognitoはモバイルデバイスの認証やデータ同期などに利用されるAWSのサービスの一つです。Cognitoを使ってSTSの一時的セキュリティ認証機能*1を使ってHTMLやJavascriptに固定のIAMの認証情報を埋め込まずとも時限的な認証情報を得ることができるようになります。

Identity Poolを作成します。認証なしで時限的な認証をするために、「Enable access to unauthenticated identities」にはチェックを入れるようにしましょう。

f:id:matetsu:20151205215152p:plain

Cognito用のauthenticated RoleとUnauthenticated Roleが作成されるので、とりあえずはそのまま「Allow」で進む。両者のPolicyはそれぞれ以下の様な感じ。

Authenticated

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*",
        "cognito-identity:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Unauthenticated

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Identity Poolの作成が完了するとSample Codeの画面になるので、各言語のSample Codeを眺めてみるのもよし。ここで必要なのはIdentity Pool IDの部分。これを控えておきます。

IAM RoleのPolicyを追加

先ほどCognitoのIdentity Poolを作成したところで作ったIAM RoleのPolicyではCognito関連の操作しかできません。S3に配置したHTML+Javascriptかたの操作でDynamoDBに出たを入れられるようにしたいので、DynamoDBの操作権限も追加したいと思います。今回使うのはUnauthenticatedなほうだけなので、そちらにだけ。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:ACCOUNT_NO:table/TABLE_NAME"
            ]
        }
    ]
}

HTML+JSを配置してみる

とりあえず今日はformに投稿された内容をDynamoDBにPutして、Scanで取ってきて表示するというもの。JSはあまり得意ではないので、いろいろなところから拝借した感じになってしまいました。特に表示データを加工もしていないし見た目もあれなので、どうにかしたい。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>コメント投稿</title>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.2.21.min.js"></script>
    <script>
        var $id = function(id) { return document.getElementById(id); };
        AWS.config.region = "ap-northeast-1";
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: "POOL_ID"});
        AWS.config.credentials.get(function(err) {
            if (!err) {
                console.log("Cognito Identify Id: " + AWS.config.credentials.identityId);
            }
        });

        var table = "blog-neta2";
        var dynamodb = new AWS.DynamoDB({params: {TableName: table}});

        function postComment() {
            var now = Math.floor(new Date().getTime() / 1000);
            var itemParams = {
              Item: {
                article: {S: location.pathname},
                timestamp: {N: "" + now},
                data: {S: $id("comment").value}
              }
            };

            dynamodb.putItem(itemParams, function(err){
                if(err){
                    alert("Error: " + err);
                } else {
                    load();
                }
            });
        }

        function load() {
            $id("comment").value = "";
            dynamodb.scan({
                ScanFilter: {
                    article: {
                        ComparisonOperator: 'EQ',
                        AttributeValueList: [
                            { S: location.pathname }
                        ]
                    }
                }
            }, function(err, data) {
                if (err) {
                    alert(err);
                } else {
                    var c = document.getElementById('comment_list');
                    c.innerHTML = '';

                    data.Items.map( function(item) {
                        var p = document.createElement('p');
                        p.textContent = new Date(item.timestamp.N * 1000) + ": " + item.data.S;
                        return p;
                    }).forEach( function(p) {
                        c.appendChild(p);
                    });
                }
            });
        }
    </script>
</head>
<body onload="load();">
    <div class="wrapper">
        <div id="postform">
            <form>
                <table>
                    <tr>
                        <th>Comment</th>
                        <td>
                            <textarea id="comment" cols="40" rows="5" name="comment"></textarea>
                        </td>
                    </tr>
                </table>
                <div id="button_area">
                    <input onClick="postComment();" type="button" value="投稿" id="post_button" />
                </div>
            </form>
        </div>
    </div>
    <div id="comment_list">
    <div>
</body>
</html>

これでテキストエリアに入力して「投稿」ボタンを押していくと、こんな感じになる。

f:id:matetsu:20151206015529p:plain

とりあえず動いてますといった感じ。

おわりに

今日のところはとりあえず簡単なコメントシステムっぽいものができた。これをうまいことちゃんと作れば、WPやMTをStatic Website HostingにHTMLを吐き出してつかった場合にもコメントが出来るようになる(ということをイメージしてみたんだがどうだろうか)。もちろんSEO云々は全く考慮していませんが、WPとかMTを使ってる人の中にはサーバ管理がそんなに得意ではない人もいるのでそのあたりのコストを考えたらコメントの部分くらいは気にしなくてもいいのかなと(といいつつ、そのへんの分野も明るくないのでよくわからないというのが本音)。

とりあえず試してみた感が強いので、もう少し面白みのあることに繋げられるといいなと思う。とくにDynamoDB StreamをつかってさらにLambda Functionを呼び出して云々とかできたらいいなと。

DynamoDBをまともに使ったことがなく、突貫で使ったので正しく使えているのかが不安なので、あとでちゃんとドキュメントを読みます(読んでないのかと)。まずい所があったらコメンとなりSNSなりで教えていただければと思いますm(_ _)m

おわりにのおわりに

5日目もやっぱり日付またいでの更新。。。当日中に更新できる日は来るのだろうか。

JAWS-UG京王線 攻めと守りのセキュリティ&監視を開催しました

前回の単独開催からは結構期間が空いてしまいましたが、12月6日にJAWS-UG京王線の勉強会を開催しました。今回も会場は電通大のリサージュ。ちょっとしたトラブルもありましたが、みんなで協力してなんとかなりました。(写真を見るとどことなく違和感があることに気づけるはず)

f:id:matetsu:20151206143931j:plain

今回のテーマはセキュリティ(と監視)。東京リージョンではまだ提供されていないInspectorの紹介をはじめ、さまざまな観点でのセキュリティ関連の話題がてんこ盛りでした。なんだかJAWS-UG支部の中でもマイナーな方の京王線にこんなに豪華なメンツが揃ってしまっていいのかというくらい豪華でした。

話し下手な私は、今回も表立って話すことはせずにつぶやき担当として裏方を支えていました(自画自賛)。

AWSJ松本さんによるInspectorの紹介

まだ東京リージョンでは使えないですが、オレゴンリージョンでPreviewとして提供されているAmazon Inspectorについての紹介を、AWSの中の人である松本さんにしていただきました。

AWSのサービスとしては珍しい、利用者の環境の中にアクションを起こすサービスであるInspector。これまで専門の業者を選定して様々なコストをかけて実施されてきた脆弱性診断をAWSの中の機能として実施することができるのはかなり革新的なサービスではないかと思います。現状ではAmazon LinuxUbuntuだけがサポートされていますが、そのうち様々なディストロやOSに対応してくれると思いますので、気長に待ちましょう。気長でなくても大丈夫な気がしますし、東京リージョンに来るときにはよく使われているディストリやOSには対応してくれていると信じています。

それにしても、もともとがセキュリティコンサルとして様々な立場の人に説明していたということもあってか、すごくわかりやすくて聴きやすい発表でした。内部的にもあまり情報が出回っていない中、ありがとうございました。

サーバーワークス柳瀬さんによる「モンハンで学ぶIAM」

本質のネタ枠、、、ではないです。久々に柳瀬さんの発表を聞きましたね。「モンハンで学ぶ」という感じでサーバワークスらしいネタ感も出していますが、内容としてはすごく丁寧にIAMの説明がされている発表でした。

IAM User、IAM Group、IAM Roleとある中で、やっぱりIAM Roleの説明の難しさは痛いほどよくわかりました(質問された時に回答に困りやすい)。また、githubなどの公開リポジトリACCESS_KEYID/SECRET_ACCESS_KEYを上げてしまうことの怖さについても説明していただいたので、どこを自分たちで守らないといけないのかといった共有責任モデルの部分に関しても勉強になる発表がったと思います。

自分のブログでもちょっとだけ出てきた(その時はCognitoを使いましたが)STSの時限式アクセス権についても説明がありました。自分としても勉強になったのは、一時的なアクセス権の付与にはIAM UserにConditionで期間設定するよりも、時限式アクセス権を使ったほうが安全で簡潔に済ませらるということですね。次回同じようなことが必要になる機会があったら絶対にSTSでやりますね。

モンハン経験者が少ないアウェー感のある中お疲れ様でした。

休憩

なんと、予定よりも20分早くすすんでしまい、ちょっとばかり時間の活用方法にこまる事態に。。。

坪さんによる「AWS + セキュリティ」

同時間帯に開催されていたSECCONのオンライン予選に参加しつつ、この勉強会でも発表してくださるというバイタリティあふれる方。内容もセキュリティ界隈のトレンドからAWS WAFまで幅広い内容でした。(自分がそういうリクエストをしたというのもありますがw)

最近のWebセキュリティではどんなことを気をつけて対策をするかということで、対策をどこかのポイントで1つ行うだけでは不十分で多段防御を行う事が重要であることや、いろいろ起きている昨今ではクライアント側の対策がかなり重要になっているということについても説明いただき、今すぐにでも実践しないといけないという気持ちが強くなりました。これまではセキュリティは門外漢だからという感じでいましたが、全員が当事者としてあたっていくということが大事なんだなと思える発表でした。

また、次の河野さんの発表でも出てきますが、ロギングの重要性もしていただき、とっていることに安心せず常にチェックをしていくことが非常に大切であることを改めて感じさせられました。

無茶振りに近い感じでの発表依頼となってしまいましたが受けていただけて、さらに素晴らしい発表も聞けたので大満足です。

河野さんによる「クラウドで実現する理想のID管理とトータルセキュリティ」

セキュリティ界隈では知らない人がいないくらい有名な河野さん、これまたギリギリに発表依頼をしたにもかかわらず快諾いただきました。内容は、ID管理とログ管理の統合がどれだけ重要であるか、ログ管理はリアルタイムで見なければ意味がなくなってきているということなど、真に迫る内容とわかりやす話し方で非常に勉強になる発表でした。

ID管理とログ管理を統合してリアルタイムで監視をし、普段とは違った挙動をしていることを検出するといったことを自前で持つことは大変なコストがかかるだけでなく、専門の人材もひつようとなるので、すべての企業が実施することは現実的では無い。そこで必要とされるのが Security as a Services。自分たちでやるべきことはすべてを自前で持つことではなく、共有IDなどはせず(識別)、確認機構を正しく設定し(認証)、権限管理を正しく行う(認可)といったところ。その下にあるログ管理を正しく行う(説明責任)部分はそれができるサービス(たとえばAzure AD)を使うことが求められるようになっている。当たり前のようでなかなかできていない部分ではあるので、気を引き締めなければいけないなと思いました。

河野さんの意図していないことを書いてしまいそうで怖いので、詳しくはこちらの資料を見ていただいたほうが良いと思います。

自分の初速している会社でもID管理の統合や監査のためのログ管理などすごくタイムリーな話題だっただけに、響くものが多かったです。すごく濃い内容を40分くらいで話していただきましたが、60分くらいの枠で話していただいても良かったなと思いました。次回は是非もっと長い枠でお願いしたいなと淡い期待を込めて。

LT

高度論文試験の対策本の紹介LT、攻めのITに向けてのお話を書かれた本の紹介LT、HPCに関する紹介LT、筋肉についてAWS WAFの紹介LT、社内のセキュリティ対策はちゃんとできてますかと問われるLT、KMSに関するLTと、セキュリティに関する内容でバラエティに富んだ発表がたくさんでした。

感想と反省

専門外だからと敬遠しがちだったセキュリティ周り。もともと重要だという認識はあったがなかなか自分から手を出そうとは思わないようにしていたのが少し恥ずかしくなるくらいにためになる発表ばかりでした。何をするにも当事者意識を持って臨まないと物事は進まないという気持ちを強く持たされるすごく気持ちの引き締まる勉強会となりました。

ただ、時間配分など運営側の至らない点が多かったことは、今回は反省すべき点だと思っていますので、次回以降はこの反省を活かしてより良い勉強会にしていきたいと思います。

発表者の皆様、参加してくださった皆様、スタッフの皆様、本当にありがとうございました。そして休日開催の勉強会への参加、お疲れ様でした。次回開催は未定ですが、楽しみに待っていていただければと思います。