読者です 読者をやめる 読者になる 読者になる

そう、今日は肉の日。だから振り返ってみる。

早いもので、某中井夫妻(@nakansuke & @Nagafichik)と3人で開催している肉会も2年を超えました。この肉会のおかげで、毎月29日頃にうまい肉を喰らい、幸せな気持ちになっております。

(つい30時間前くらいまで完全に忘れていましたが)そろそろ3人で肉会のまとめをしようということになり、各々のブログで肉の日である2月9日に発表しようということになりましたので、唐突ではありますが肉ブログです。

いろいろあってなかなか選抜には苦労しましたが、独断と偏見のTOP3を発表してみたいと思います。

(ドラムロール)

第3位 黒牛 / 2014年10月29日来店

tabelog.com

この月は前の月に開催できず、2ヶ月分まとめての開催ということで普段財布の紐が固めの私も奮発したなあ。
高級なシャトーブリアンが初お目見えしたり、結構金銭感覚がおかしかったとは思うが、うまい肉のためなら多少は奮発する肉会のベースになったような気がする思い出の回でもある。いい肉は高いからうまいとは限らないこともありますが、ここは高いけどうまかったので満足。東京は金を払えばうまいもんが食える。田舎者の学びでした。
(ビールとマッコリのちゃんぽんで結構気持ち悪くなっていたのは気づかれていなかったはず。)

(ドラムロール)

第2位 生粋 / 2015年11月29日

tabelog.com

よろにく系の店のひとつ。お上品な感じのコースで、いい肉を適度にいただける店。私もいい年になってきたので、サシの多い肉をがっつり大量に食べることはできませんが、生粋さんは適量で満足度も高い。〆の冷麺もちょうどよい。デザートのかき氷にはちょっとびっくりしつつ、でもコースを締めくくるのに調度よい感じ。おいしくてちょうどよい。よい。高級な感じ。

(ドラムロールと照明くるくる)

第1位! 房家 本郷三丁目店 / 2014年11月29日

tabelog.com

栄えある第1位は、本郷三丁目にある房家さん。ここは(他と比較して)お手頃な感じで高級肉がいただけるので非常に満足度が高い。いろんな部位をいろんなタレなどでいただけるのもよいです。変わり種としては限定品(?)のサクラユッケがウマウマです(食ったよね)。肉は数秒焼くだけでこれほどにもうまくなるのかと感動を呼ぶこと間違い無し。そろそろ再訪したい。

お店の感じも気取った感じではないので気軽に(予約すれば)行ける感じもよいです。


別枠 和飲和ん / 何度も

tabelog.com

中井(夫)さん行きつけの店。二人の結婚祝いもここでやったし、ここではないけど姉妹店の和ん通桶T氏どんまいこ氏と5人で行ったことが肉会の始まりだったりもするので、思い入れのある店。肉も酒もうまいし、常連な中井さんがいるおかげで普通よりは良いサービスを受けられていると思う。肉もうまいけど、卵かけご飯もうまいから、食うべし。


その他捨てがたいとこ

NO MEAT, NO LIFEやジャンボ、格之進、(ちょっと系統が違うけど)ひつじやあたりも捨てがたい。いやあ、ホントいい店に行ってるな。

その他の店

その他の店については、夫妻が書いてくれているはず。

nakansuke.hatenablog.com
gyori.hatenablog.com

まとめ

うまい肉を食うと幸せになれます。年齢が上がってきてたくさんは食えないけど、いい肉を少しずつ、かつ大胆に食える肉会。皆様にもおすすめです。

気づいたらTOP3ではない感じになってしまいましたが、気にしない。

Amazon Kinesis Streamsを使ってみた

11日目、というより11記事目といったほうがいいですかね。ネタを思いつかないので、基本に立ち返って「使ってみた」系にしてみました。

そこそこの期間AWSを利用しているのですが、業務に関係無かったりするとなかなか触れないサービスもあったりします。そういうものを触っていこうというのも今回のAdventCalendar的更新のモチベーションの1つだったはずなので、わざわざ簡単なシステムに落とさなくてもいいのではと思った次第です。

というわけで、Kinesisを使ったことがなかったのでとりあえずはじめの一歩的な内容をば。(間違っていることもあるかも)

Kinesis Streamの作成

今回扱うのはKinesis Streamの方です。今回はFirehose扱いませんが、バージニアオレゴンアイルランドの3リージョンでのみ利用可能となっています。

f:id:matetsu:20151213195311p:plain

それでは始めましょう。Let's 「Create Stream」!

f:id:matetsu:20151213213618p:plain

名前はとりあえず「stream-test」とでもしておきます。

はじめてなので、「Help me decide how many shards I need」にチェックを入れて助けを請いましょう。とりあえずは平均レコードサイズは1KBとして最大書き込み/秒は適当に100としておきましょうか。Consumerアプリケーション数は1とします。

すると、Shard数は「1」で良いでしょうと言った感じで出てきます。

f:id:matetsu:20151213211401p:plain

では、お言葉に甘えて「1」を利用させていただきましょう。(ドキュメントのシャードの項を読めばたいんいつシャードあたりの性能はわかりますし、Shard数を入れると下にキャパシティなどが表示されるのでそれを見ながら調整してもいいんでしょうねw)

f:id:matetsu:20151213213211p:plain

「Create」ボタンを押したら、Statusが「Active」になるまで待ちましょう。

f:id:matetsu:20151213213358p:plain

ACTIVEになるとこんな感じ。

f:id:matetsu:20151213213420p:plain

streamができあがりましたので、データ(レコード)を入れてみましょう。

Streamにレコードを送信する

KinesisではStreamにデータを送信する役割をもったノードやアプリケーションをProducerと呼びます。

まずは簡易的にレコードを送信してみたいので、kinesis-catを利用してみます。

$ gem install kinesis_cat
$ echo '{"key1": "val1", "key2": "val2"}' | kinesis-cat --stream-name stream-test

では送信したレコードを確認してみましょう。確認するためには、CLIを使います。

まずshardの情報を取得して、シャードの位置を確認する必要があります。kinesis get-shard-iteratorコマンドで取得したShardIteratorは300000msec以内に利用しないとExpireされますので、初めてでもたついていると苦労します。

$ aws kinesis describe-stream --stream-name stream-test
{
    "StreamDescription": {
        "StreamStatus": "ACTIVE",
        "StreamName": "stream-test",
        "StreamARN": "arn:aws:kinesis:ap-northeast-1:ACCOUNT_NO:stream/stream-test",
        "Shards": [
            {
                "ShardId": "shardId-000000000000",
                "HashKeyRange": {
                    "EndingHashKey": "340282366920938463463374607431768211455",
                    "StartingHashKey": "0"
                },
                "SequenceNumberRange": {
                    "StartingSequenceNumber": "49557238107343677981012040936118820808140350529483046914"
                }
            }
        ]
    }
}
$ aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type TRIM_HORIZON --stream-name stream-test
{
    "ShardIterator": "AAAAAAAAAAFPuPSOFFd7SOl/TA2jlaJQHCJjOdBOtaqG/R/vgaY9LpL+u7QofNeFxXHlR54VPmcrEcTOGQocib2Gcd6xGwXpknZSPEVHw0mcDUPnFYaEuf1O61Yacn4bPJNj86SWT033nlXkycojKM0+0td08FCfQ/Behdp2SloPr7hIehAUMZeYh3yU8t0n2NvIw+tbvM4WT32X2fn7/kya87tVhLLM"
}
$ aws kinesis get-records --shard-iterator AAAAAAAAAAHTBivnV5Rk0dGwvKd0PnA2ntjdqYX6xsO3wtkTP/37T63C5AdZTbwfONrXge1PT43OKkkrbx1Cga6lbE+VMNzBwHoxJW9XZ35vUpkhkaTq1c62HSALUp3B+AM8cJaRtu3gpNWi2emSta+lGxLeO2CgT8fdq9yy5uZFAlkHYgilgDAbxnHl6Ps3+6g38jdXP4IsXjG4HbrYHf6P/bSUeSru
{
    "Records": [
        {
            "PartitionKey": "1450012611",
            "Data": "eyJrZXkxIjoidmFsMSIsImtleTIiOiJ2YWwyIn0=",
            "SequenceNumber": "49557238107343677981012040936505677070417222287059058690"
        }
    ],
    "NextShardIterator": "AAAAAAAAAAGlPk98twtF2KpCnwlOadDjIgSDr60meZq9qjV88WLzDWPpuplichv5qALBFXZ2igTDMxRBy15Tchl6mlFuNGohx6Nu0MAQ07BB4Mv+NWmmLsxXHeGFd19tFrcABk7k+nKEVnTZsV1qDiQf+5Y32bCi4Wm5xvsGXPie9QUBfi6zrLwViHNVM0FNfNWGdOkliOxXxALtl731nVv6neWhVvJg",
    "MillisBehindLatest": 685000
}

ShardIteratorの取得とRecordsの取得は、合わせてこんな感じにしたほうがいいですかね。(ドキュメントにも近いことが書いてある)

$ aws kinesis get-records --shard-iterator $(aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type TRIM_HORIZON --stream-name stream-test | jq -r .ShardIterator)

また、表示されたDataはBase64エンコードされているので、

$ aws kinesis get-records --shard-iterator $(aws kinesis get-shard-iterator --shard-id shardId-000000000000 --shard-iterator-type TRIM_HORIZON --stream-name stream-test | jq -r .ShardIterator) | jq -r ".Records[0].Data" | base64 -D
{"key1":"val1","key2":"val2"}

とするとデコードされた状態で確認することができます。

再度レコードを登録して、重複なく取得するためには、NextShardIteratorを利用します。

$ aws kinesis get-records --shard-iterator AAAAAAAAAAGlPk98twtF2KpCnwlOadDjIgSDr60meZq9qjV88WLzDWPpuplichv5qALBFXZ2igTDMxRBy15Tchl6mlFuNGohx6Nu0MAQ07BB4Mv+NWmmLsxXHeGFd19tFrcABk7k+nKEVnTZsV1qDiQf+5Y32bCi4Wm5xvsGXPie9QUBfi6zrLwViHNVM0FNfNWGdOkliOxXxALtl731nVv6neWhVvJg --stream-name stream-test
{
    "Records": [],
    "NextShardIterator": "AAAAAAAAAAGeum3ukha5gAZXqSkErxc+CUW0LhCIWv1iJ65F6vEvKyIC//McDjma4EKRfGNZqOFOhGCn4EHMTFyjwmbxzl35Pv6vY6a43OBjaQlZ7/zUfk435y8ULCtc0M6w4J9U9s5ttYdI9DIOZ+JlKL0KKgaze13nLmo2PpmCtKglyQ4PB4nE761Etyv3j8ZgzvorRfIW9/pmBPkn6/Z7E5j5NdSl",
    "MillisBehindLatest": 0
}

と言った感じで、今は何も次に登録していないので、空のRecordsが返ってきます。

急いで新たなレコードを登録して、先ほど表示されたNextShardIteratorを指定してみましょう。

$ echo '{"key1": "val1", "key2": "val2"}' | kinesis-cat --stream-name stream-test
$ aws kinesis get-records --shard-iterator AAAAAAAAAAGeum3ukha5gAZXqSkErxc+CUW0LhCIWv1iJ65F6vEvKyIC//McDjma4EKRfGNZqOFOhGCn4EHMTFyjwmbxzl35Pv6vY6a43OBjaQlZ7/zUfk435y8ULCtc0M6w4J9U9s5ttYdI9DIOZ+JlKL0KKgaze13nLmo2PpmCtKglyQ4PB4nE761Etyv3j8ZgzvorRfIW9/pmBPkn6/Z7E5j5NdSl
{
    "Records": [
        {
            "PartitionKey": "1450014952",
            "Data": "eyJrZXkxIjoidmFsMSIsImtleTIiOiJ2YWwyIn0=",
            "SequenceNumber": "49557238107343677981012040936509303847876227046878216194"
        }
    ],
    "NextShardIterator": "AAAAAAAAAAF+SbH2cLWMq0ytQLuaMc3ZSH7pNMBCU2hGycDaMlZZBIh5E8p9fVHeOsBidhoYPJ1DzoFUdDFCbLG+H2WHcqD9tcGIQN1+TR2cVrnTOZwijnlccd1XoVQDrDcQ5dIMwZIHMQggvG5i+eEVobCpmqWbicoE0H1XcrMfbSJ1KTFAsYVLJ3VBpsdC0UR9ofRudf2ZF/lk2G2W6BotkjFrvLEn",
    "MillisBehindLatest": 37000
}

はい、うまくいきました。とりあえず、KinesisのStreamにレコードを登録して、取得するところを見てみることができました。

おわりに

とりあえず動かしてみたレベルでしか無いので、次回はより実践的な内容で使ってみたいと思います。実際の業務やサービスで使えるレベルにできるようになるよう頑張りたいと思います。

ふぅ

はい、11件目の更新でした。まだまだ追いつくことができていませんが、なんとかしていきたいと思います。ネタ的には失速感が半端ないですが、続けることに意味があると自分に言い聞かせております。。(2日途切れさせたのに)

なんだか書き方的に某書籍を書いている時のような感じがしましたw

アラートメールにSlackで返事をする w/ SES + S3 + Lambda + API Gateway 〜Outgoing篇〜

10日目です。9日目が当日中にできなかったのでネタを2分割して2日分しています。すみません、すみません。

9日目にメールからSlack通知をするシステムを作りましたので、今日はSlackポストから通知されたメールに返信する部分を作成しましょう。

f:id:matetsu:20151211015200p:plain

Lambda + API Gatewayの設定

SlackのOutgoing webhooksからのリクエストを受けるためのAPI Gatewayと実際の処理を行うLambda Functionの設定を行います。

Lambdaの設定

今回はAPI Gatewayと連携してmicro serviceのEndpointとして動くことを想定しているので、選択するBlueprintはmicroservice-http-endpointになります。

f:id:matetsu:20151211005447p:plain

Lambda Functionは最初から作ってしまいましょう。RoleはIncomingのときと同じものを使います。

コードは大雑把に書くとこんな感じ。S3に格納されている元メールへの返信となるように、Subjectに「Re: 」をつけたり、元のMessage-IDだったものを「In-Reply-To」や「Resources」に設定するようにもしています。

Slackユーザとメールアドレスを紐付けることは面倒なので、no-replyからの送信にしたうえで本文に誰からの返信かを書くようにしました。


Slack -> (API Gateway -> ) Lambda -> SES (Reply Em ...

slackポストのパースは簡易的に正規表現で「@alert_bot reply 返信対象メールのS3_KEY 本文」となっていることを期待しています。また、今回はAPI Gatewayでの認証を設定しないので、outgoing webhooksで設定されているTokeがEventにセットされていない場合は処理をしないようにもしています。

API Gatewayの設定

Lambda Functionの設定の流れでAPI Gatewayの設定も出てきます。先ほど書いたとおり、今回は認証をしないでPOSTを受け付ける設定です。

f:id:matetsu:20151211011430p:plain

Lambda Functionの作成が完了すると「API Endpoint」タブが表示されているので、「Method」のPOSTのリンクをクリックしてAPI Gatewayの設定を更に進めます。

個々の設定はこちらのQiita記事が参考になるので、ご参照ください。特にMapping Templateの設定が重要ですのでお忘れなく。

qiita.com

こrでAPI Gatewayの設定も完了し、Endpoint URLが決定します。(設定時と同じくLambdaのAPI Endpointタブに表示されています)

SlackのOutgoing Webhooksの設定

裏側の設定が完了したので、次はSlack側の設定です。発言に対して処理をするにはOutgoing webhookを利用します。

設定は以下のようにします。URL欄はAPI GatewayAPI Endpoint URLを設定します。またhookのTriggerはBotへのメンションとしています。

f:id:matetsu:20151211001259p:plain

それでは、対象チャンネルで発言をしてみましょう。

f:id:matetsu:20151211014600p:plain

そしてメーラを確認すると、9日目のネタで送信したメールへの返信としてメールが来ています。

f:id:matetsu:20151211014927p:plain

うまくいきましたね。

おわりに

ニーズがあるかわかりませんが、メールへの返信を疎かにしないでSlackだけで完結できる仕組みができました。これで、ますますメール離れができますね!

返信が面倒なんで、Slackで返信が簡単にできるような仕組みがあるといいんですけどね。

そして、動かしてみて気付きましたが、Lambda Functionの返り値をincoming webhookの形式にし忘れたので、発言に対してSlackは何の反応もしません。。。これだと、ちょっと不安かもしれませんね。(今日は直さないけど)

しめ

10日目も無事書き終わりました。本当に無理くり感が半端なくなってきています。それではまた明日。

アラートメールにSlackで返事をする w/ SES + S3 + Lambda + API Gateway 〜Incoming篇〜

Advent的

9日目。ネタ切れ感半端なさすぎてむりくりネタを作っています。

今日は監視システムからのアラートメールに対してSlackで返信をするというものです。最近はSlackにアラートを飛ばしたりするところも増えていますし、自分のいる部署でもメールとSlackに通知するようにしています。

こういった2つの通知先に通知を送った場合に、片方で返信をしても他方しか見ていないメンバーがいた場合に状況がわからないなんてこともあると思います。こんな状況を(第一報に対しては)解決できるであろう構成です。

f:id:matetsu:20151211015745p:plain

ニーズがあるかどうかは知りません。

Route 53の設定

メールを受信するドメインの設定をします。今回はOreginの受信用Endpointを利用するので、以下のとおりに。

f:id:matetsu:20151210004022p:plain

受信したメールを保存しておくS3バケットの設定

適当な名前でバケットを作成し、Bucket Policyに以下のようなPolicyを設定する。ドキュメントとしては、こちら

{
	"Version": "2008-10-17",
	"Statement": [
		{
			"Sid": "GiveSESPermissionToWriteEmail",
			"Effect": "Allow",
			"Principal": {
				"Service": [
					"ses.amazonaws.com"
				]
			},
			"Action": [
				"s3:PutObjectAcl",
				"s3:PutObject"
			],
			"Resource": "arn:aws:s3:::BUCKET_NAME/*",
			"Condition": {
				"StringEquals": {
					"aws:Referer": "ACCOUNT_NO"
				}
			}
		}
	]
}

SESの受信設定

受信用ドメインの認証をしますので、さきほどRoute 53でMXレコードとして設定したドメインをRecipientsとして登録して「Verify Domain」をします。

f:id:matetsu:20151210004508p:plain

「Verify Domain」をクリックするとダイアログが出てきます。今回はRoute 53を使っているので、素直に「Use Route 53」ボタンをどうぞ。

Verifyされたら次は受信時のアクションを設定していきます。今回は以下のように設定。

f:id:matetsu:20151210012226p:plain

ルールの詳細(TLS有効にするか、SPAMやウイルスチェックをするかなど)を入力。

f:id:matetsu:20151210010325p:plain

内容を確認して、「Create Rule」をします。

S3ではなく、SNSやLambdaで直接受けたほうが便利なのではとお思いでしょうが、S3でないとメールの本文を取得することができないのです。(他のブログでも書かれていますし、結構重要な点ですが、Amazonさんならそのうち取得できるようにしてくれるはず。)

イベント内容確認のためにLambda Functionの枠だけを作成しておく

Lambda Functionの作り方は、これまで何度もやってきたので省略。Blueprintのhello-world-pythonを少し変えて、以下の用意しておきます。

from __future__ import print_function

import json

print('Loading function')


def lambda_handler(event, context):
    print("Received event: " + json.dumps(event, indent=2))
   
    return "CONTINUE" 

割り当てるRoleのPolicyはこんな感じ。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::BUCKET_NAME/*"
      ]
    },
        {
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        }
  ]
}

これで、どんなEventが来たかをCloudWatch Logsから確認できます。


S3バケットに更新Eventを設定する

こんな感じで設定。先ほど作成したLambda FunctionにEventを送信するようにしています。
(名前の適当さは気にしないでくださいw)

f:id:matetsu:20151210231018p:plain

メールを受信して、eventの内容を確認

以上でメールを受信した時に発せられるEventを確認できる体制ができまたので、普段使っているメーラからSESで受信できるようにしたドメイン宛にメールを送ります(Local Partは適当で良いです)。

f:id:matetsu:20151210231449p:plain

CloudWatch LogsでEvent内容を確認

このような内容のEventが来ます。Bucket名とKey名がわかるので、これを使えばS3からメールの内容を取得することができますね。

{
  "Records": [
    {
      "eventVersion": "2.0",
      "eventTime": "2015-12-10T01:47:57.525Z",
      "requestParameters": {
        "sourceIPAddress": "IP.ADD.RE.SS"
      },
      "s3": {
        "configurationId": "notify-receiving-to-lambda",
        "object": {
          "eTag": "77ca66e4622ae4c5c113e9fd07109df0",
          "sequencer": "005668D9CD4BB230B3",
          "key": "KEY",
          "size": 1662
        },
        "bucket": {
          "arn": "arn:aws:s3:::BUCKET",
          "name": "BUCKET",
          "ownerIdentity": {
            "principalId": "PRINCIPAL_ID"
          }
        },
        "s3SchemaVersion": "1.0"
      },
      "responseElements": {
        "x-amz-id-2": "iDu7YW04ChE4HHC5HjGXC8/v1X8KzZZqNY6l7oXwulcjWQUiXY2DZfuNcHjsOfeW5Rriy8RfWTk=",
        "x-amz-request-id": "BBB6B16CB9E173EA"
      },
      "awsRegion": "ap-northeast-1",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "AWS:XXXXXXXXXXX"
      },
      "eventSource": "aws:s3"
    }
  ]
}

Lambda Functionを修正

Slackにも通知するために、こんな感じで修正します。SlackのIncoming Webhooksは各自で設定をしてください。


SES -> S3 -> Lambda -> Slack

これでテキストメールを受信するとSlackに内容が通知されます。HTMLメールやMultipartなメールはとりあえずは非対応ということで。

試してみる。

メーラからメールを送ってみると、こんな感じになります。keyと表示されてるのは、返信で使うためのS3のkeyです(イケてないですが見逃してつかあさい)。

f:id:matetsu:20151210235234p:plain

おわりに

はい、これで無事メールからSlack通知ができました。アラートメールなどの宛先に1つ追加しておけばよいので、簡単ですね。ただ、ここまでの流れであれば監視システムから直接Slackのincoming-webhooksを使ってポストしてあげれば簡単なんですけど、次回のOutgoing篇でこのシステムの真価を発揮できると思いますのでお楽しみに。

というわけで、第9日目を10日に更新するという事になってしまいましたが、無事終了です。

簡易コメントシステムからのメール通知 w/ DynamoDB streams + Lambda + SES

はい、ネタも尽きかけた今日このごろ。捻出したのは、5日目に書いたネタの拡張です。

5日目はS3(Hosting) + Cognito(AWSリソースへの時限式アクセス権の提供) + DynamoDB(Storage)と言った構成でした。これをさらに拡張して、コメント投稿があった場合には管理者にメールで通知するというものです。この拡張部分に利用するのがDynamoDBの更新イベントなどを通知するDynamoDB Streamsとメール配信サービスのSESです(どこからともなくSESではなくSendGrid*1を使ってくださいと言われそうですが、時間に限りがあるので今回はSESです)。

人間というのは面倒くさがりなので、Pushで更新通知などが無いとなかなか更新があったことに気づかないものです。そんな人でも、サーバレスで簡単に通知システムを組むことができちゃうので、安心してサイト運営ができます(サイト運営と言うとちょっとだいぶ言い過ぎですね)。

Lambda Functionの枠を用意する

今回もまずはLambda Functionの枠だけ用意して、Eventとしてどんな構造のものが来るのかを確認できるようにしましょう。BluePrintから「dynamodb-process-stream-python」を選択します。

f:id:matetsu:20151209003420p:plain

扱うデータも頻繁に更新もされませんし、更新された順に通知してくれればいよいので、Batch Sizeは「1」、Starting Positionは「Trim horizon」にします。

f:id:matetsu:20151209004059p:plain

Lambda Functionはとりあえずはサンプルのままにしておきます。

このLambda Functionで利用するIAM Roleはdynamodbを扱える必要があるのと、SESからメールを送信できる必要があるので以下のようにします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "lambda:InvokeFunction"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetRecords",
        "dynamodb:GetShardIterator",
        "dynamodb:DescribeStream",
        "dynamodb:ListStreams",
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail"
      ],
      "Resource": "*"
    }
  ]
}

これは、「DynamoDB event stream role」というサンプルロールにSESへの権限を追加したものになります。サンプルは「role」のところから、こんな感じで選ぶ。

f:id:matetsu:20151209005220p:plain

あとは、Timeoutを10秒位にしておいて、有効にしつつ作成完了。

DynamoDBのトリガ設定

DynamoDB StreamsとLambdaを結びつけるために、トリガの設定をします。対象のテーブルでトリガを作成すると自動的にStreamが有効になります。

f:id:matetsu:20151209005712p:plain

StreamからくるLambdaのEvent内容を確認

DynamoDBに新規アイテムを追加して、どのようなEventが来るのかを確認します。適当にコメントを追記して、CloudWatchLogsを確認します。

f:id:matetsu:20151209010325p:plain

jsonの構造がわかりましたので、実際にSESで投稿内容をお知らせするLambda Functionを作成しましょう。なお、SESの設定は省略します。

Lambda Functionの修正

こんな感じ。

# coding: utf-8
import boto3
import json
import datetime

print('Loading function')


def lambda_handler(event, context):
    record = event['Records'][0]
    ses = boto3.client('ses', region_name='us-east-1')
    response = ses.send_email(
        Source = "matetsu@gmail.com",
        Destination = {
            "ToAddresses": ["matetsu@gmail.com"],
            },
        Message = {
            "Subject": {
                "Data": u"コメント投稿がありました"
                },
            "Body": {
                "Text": {
                    "Data": u"""コメント投稿がありました。
                    
                    Article: %s
                    Timestamp: %s
                    Comment: %s
                    """ % (record['dynamodb']['NewImage']['article']['S'], datetime.datetime.fromtimestamp(int(record['dynamodb']['NewImage']['timestamp']['N'])), record['dynamodb']['NewImage']['data']['S'])
                    }
                }
            }
        )

    return "Sent message id: %s" % response['MessageId']

コメントをして、メールが来るか確認

こんな投稿をしてみると。。。

f:id:matetsu:20151209015552p:plain

こんなメールが届きます。

f:id:matetsu:20151209015614p:plain

おわりに

はい、このネタもパクリやんかと言われそうですが、まあパクリと言ってしまえばパクリです。ネタがないんです。さらに、ほとんどはブログの会社さんが書いてしまっているんです。

いいわけでした。

ということで、なんとか8日目も終わりました。また明日、どんなネタが飛び出すか、お楽しみに!

fluentdをsystemdで管理する

Advent的

6日目が少々反則的な内容となってしまいましたが、きにせず7日目行きたいと思います。今日は仕事からの帰りが遅かったので書き始めから日付を超えているというミラクル。もう、時間を気にせず書けます。

と、前置きはこれくらいにして本題に入りたいと思います。いままでのガッツリな感じ(?)とは異なり、時間の関係でサラッとしたネタです。参考にさせていただいたのはRHELやSystemdやDockerの説明では知らない人はいないであろう中井さんの下記サイトです。

enakai00.hatenablog.com


ログ収集をする場合にはデファクトスタンダードとなっているであろうfluentdをCentOS7などのSystemdなディストリビューションで起動/停止の管理をしたい場合の設定方法です。といっても、ひとまず動かしてみたレベルなので、問題もあるかもしれません。

通常はtd-agentをRPMからインストールしますし、まだCentOS7でもCentOS6の時のようにinitスクリプトで管理することもできますし、パッケージに付属しているのもinitスクリプトとなっています。ただ、私、結構変わり者でして、gem install fluentdとして使うのが好きだったりします(ツッコミたい気持ちは抑えてください)。

というわけで、rbenvでシステム全体にインストールしたRubyを使って、fluentdをSystemdの管理下で動かしてみたいと思います。

Fluentdのインストールと初期設定

jemallocは入れなくてもいいとも言われていますが、とりあえず入れる場合の手順です。入れない場合は、関連する部分を削っていただければ。jemallocはepelリポジトリが必要なので、先にepel-releaseをインストールしてリポジトリ情報を追加してから。
※パス周りがいろいろあるので、rootで作業しちゃいます。あしからず。

インストールと起動直前まで設定はこんな感じ。

# yum install epel-release -y
# yum install jemalloc -y 
# gem install fluentd --no-document
# useradd -M -s /bin/false fluentd
# mkdir /var/log/fluentd
# chown fluentd. /var/log/fluentd
# vim /etc/fluent/fluent.conf
# vim /etc/sysconfig/fluentd

/etc/fluent/fluent.conf

<source>
  type forward
</source>

@include conf.d/*.conf

<match fluent.**>
  type file
  path /var/log/fluentd/fluentd_internal.log
</match>

<match **>
  type file
  path /var/log/fluentd/else
  compress gz
</match>

/etc/sysconfig/fluentd

LD_PRELOAD=/usr/lib64/libjemalloc.so.1
FLUENTD_USER=fluentd
FLUENTD_GROUP=fluentd
FLUENTD_LOGFILE=/var/log/fluentd/fluentd.log

これが今日のメインともいうべきファイル。SystemdのUnitファイルと言うんですかね、設定ファイルはRPMでインストールしたものは /usr/lib/systemd/system/hoge.service に配置され、それを上書きをしたり自前で作る場合は /etc/systemd/system/hoge.service に配置します。

/etc/systemd/system/fluentd.service

[Unit]
Description=Fluentd daemon
After=network.service

[Service]
EnvironmentFile=/etc/sysconfig/fluentd
LimitNOFILE=65536
ExecStart=/usr/local/rbenv/versions/2.2.3/bin/fluentd --user ${FLUENTD_USER} --group ${FLUENTD_GROUP} --log ${FLUENTD_LOGFILE}
ExecStop=/bin/kill -INT ${MAINPID}
ExecReload=/bin/kill -HUP ${MAINPID}
Restart=always

[Install]
WantedBy=multi-user.target

それぞれの説明

  • Unit
    • Description: Unitの説明
    • After: このサービスの後に起動されるように
  • Service
    • EnvironmentFile: 環境変数を定義したファイル
    • LimitNOFILE: ulimit -fで指定するようにmax open fileを指定
    • ExecStart: 起動コマンド
    • ExecStop: 停止コマンド($MAINPIDでsystemdが管理しているpidを取得できる)
    • ExecReload: Reloadコマンド(同上)
    • Restart: プロセスの再起動条件(respawnと同じような感じ)
  • Install (enable/disableに関連する設定)
    • WantedBy: enable時にこのUnitのtargetディレクトリ(multi-user.target.wants)にsymlinkが作成される
      • multi-user.targetはこれまでの run-level 3に相当します。(run-level 5はgraphcal.target)

これにて起動してみましょう。

# systemctl enable fluentd
ln -s '/etc/systemd/system/fluentd.service' '/etc/systemd/system/multi-user.target.wants/fluentd.service'
# systemctl start fluentd
 systemctl status fluentd
fluentd.service - Fluentd daemon
   Loaded: loaded (/etc/systemd/system/fluentd.service; enabled)
   Active: active (running) since Mon 2015-12-07 16:30:42 UTC; 5s ago
 Main PID: 28406 (fluentd)
   CGroup: /system.slice/fluentd.service
           ├─28406 /usr/local/rbenv/versions/2.2.3/bin/ruby /usr/local/rbenv/versions/2.2.3/bin/fluentd --user fluentd --group fluentd --log /var/log/fluentd/fluentd.log
           └─28408 /usr/local/rbenv/versions/2.2.3/bin/ruby /usr/local/rbenv/versions/2.2.3/bin/fluentd --user fluentd --group fluentd --log /var/log/fluentd/fluentd.log

Dec 07 16:30:42 ip-10-11-0-199.localdomain systemd[1]: Starting Fluentd daemon...
Dec 07 16:30:42 ip-10-11-0-199.localdomain systemd[1]: Started Fluentd daemon.

無事起動しました。それでは、ちょっと設定ファイルを変えて、reloadしてみましょう。

# systemctl reload fluentd
# cat /vat/log/fluentd/fluentd.log
(一部抜粋)
2015-12-07 16:37:59 +0000 [info]: restarting
2015-12-07 16:37:59 +0000 [info]: reading config file path="/etc/fluent/fluent.conf"
2015-12-07 16:37:59 +0000 [info]: shutting down fluentd
2015-12-07 16:37:59 +0000 [info]: shutting down input type="forward" plugin_id="object:3fb66434acd8"
2015-12-07 16:37:59 +0000 [info]: shutting down input type="debug_agent" plugin_id="object:3fb66434a10c"
2015-12-07 16:37:59 +0000 [info]: shutting down output type="file" plugin_id="object:3fb66628a300"
2015-12-07 16:37:59 +0000 [info]: shutting down output type="stdout" plugin_id="object:3fb664310cb8"
2015-12-07 16:37:59 +0000 [info]: shutting down output type="file" plugin_id="object:3fb6662a89f4"
2015-12-07 16:37:59 +0000 [info]: process finished code=0
2015-12-07 16:37:59 +0000 [error]: fluentd main process died unexpectedly. restarting.
2015-12-07 16:37:59 +0000 [info]: starting fluentd-0.12.17
2015-12-07 16:37:59 +0000 [info]: gem 'fluentd' version '0.12.17'
2015-12-07 16:37:59 +0000 [info]: adding match pattern="debug.**" type="stdout"
2015-12-07 16:37:59 +0000 [info]: adding match pattern="fluent.**" type="file"
2015-12-07 16:37:59 +0000 [info]: adding match pattern="**" type="file"
2015-12-07 16:37:59 +0000 [info]: adding source type="forward"
2015-12-07 16:37:59 +0000 [info]: adding source type="debug_agent"
2015-12-07 16:37:59 +0000 [info]: using configuration file: <ROOT>
  <source>
    type forward
  </source>
  <source>
    type debug_agent
    port 24230
  </source>
  <match debug.**>
    type stdout
  </match>
  <match fluent.**>
    type file
    path /var/log/fluentd/fluentd_internal.log
    buffer_path /var/log/fluentd/fluentd_internal.log.*
  </match>
  <match **>
    type file
    path /var/log/fluentd/else
    compress gz
    buffer_path /var/log/fluentd/else.*
  </match>
</ROOT>
2015-12-07 16:37:59 +0000 [info]: listening fluent socket on 0.0.0.0:24224
2015-12-07 16:37:59 +0000 [info]: listening dRuby uri="druby://0.0.0.0:24230" object="Engine"
# systemctl status fluentd
(一部抜粋)
  Process: 28701 ExecReload=/bin/kill -HUP ${MAINPID} (code=exited, status=0/SUCCESS)

よさ気ですね。次に強制停止してみましょう。

# kill -9 28406
# systemctl status fluentd
fluentd.service - Fluentd daemon
   Loaded: loaded (/etc/systemd/system/fluentd.service; enabled)
   Active: active (running) since Mon 2015-12-07 16:45:32 UTC; 1s ago
  Process: 11982 ExecStop=/bin/kill -INT ${MAINPID} (code=exited, status=1/FAILURE)
 Main PID: 11986 (fluentd)
   CGroup: /system.slice/fluentd.service
           ├─11986 /usr/local/rbenv/versions/2.2.3/bin/ruby /usr/local/rbenv/versions/2.2.3/bin/fluentd --user fluentd --group fluentd --log /var/log/fluentd/fluentd.log
           └─11991 /usr/local/rbenv/versions/2.2.3/bin/ruby /usr/local/rbenv/versions/2.2.3/bin/fluentd --user fluentd --group fluentd --log /var/log/fluentd/fluentd.log

Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: fluentd.service holdoff time over, scheduling restart.
Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: Stopping Fluentd daemon...
Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: Starting Fluentd daemon...
Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: Started Fluentd daemon.

ちゃんと再起動されています。最後に正しく停止してみましょう。

# systemctl stop fluentd
# systemctl status fluentd
fluentd.service - Fluentd daemon
   Loaded: loaded (/etc/systemd/system/fluentd.service; enabled)
   Active: inactive (dead) since Mon 2015-12-07 16:48:50 UTC; 2s ago
  Process: 12004 ExecStop=/bin/kill -INT ${MAINPID} (code=exited, status=0/SUCCESS)
  Process: 11986 ExecStart=/usr/local/rbenv/versions/2.2.3/bin/fluentd --user ${FLUENTD_USER} --group ${FLUENTD_GROUP} --log ${FLUENTD_LOGFILE} (code=exited, status=0/SUCCESS)
 Main PID: 11986 (code=exited, status=0/SUCCESS)

Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: fluentd.service holdoff time over, scheduling restart.
Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: Stopping Fluentd daemon...
Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: Starting Fluentd daemon...
Dec 07 16:45:32 ip-10-11-0-199.localdomain systemd[1]: Started Fluentd daemon.
Dec 07 16:48:49 ip-10-11-0-199.localdomain systemd[1]: Stopping Fluentd daemon...
Dec 07 16:48:50 ip-10-11-0-199.localdomain systemd[1]: Stopped Fluentd daemon.
# ps aux|grep [f]luentd
#

fluentdのログも見てましょう。

# cat /var/log/fluentd/fluentd.log
2015-12-07 16:48:49 +0000 [info]: shutting down fluentd
2015-12-07 16:48:49 +0000 [info]: shutting down input type="forward" plugin_id="object:3f92e82725a8"
2015-12-07 16:48:49 +0000 [info]: shutting down input type="debug_agent" plugin_id="object:3f92e634d4d4"
2015-12-07 16:48:50 +0000 [info]: shutting down output type="file" plugin_id="object:3f92e6312b7c"
2015-12-07 16:48:50 +0000 [info]: shutting down output type="file" plugin_id="object:3f92e63385fc"
2015-12-07 16:48:50 +0000 [info]: shutting down output type="stdout" plugin_id="object:3f92e63081b8"
2015-12-07 16:48:50 +0000 [info]: process finished code=0

はい、ちゃんと停止されましたね。めでたしめでたし。

おわりに

まだ設定をして軽く動作を確認して見た程度ですが、うまく行っていそうな感じではあります。以前からも今後も長くお世話になるfluentd、これから仲良くしていかなければならないSystemd、どちらもちゃんと使えるようになりたいですね。

これにて7日目の更新は終了です。それでは、また明日!

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と、セキュリティに関する内容でバラエティに富んだ発表がたくさんでした。

感想と反省

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

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

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