セキュリティグループが変更されたのを検知してmattermostに通知する

- ハンズラボさんのブログにslackに通知する記事が出てたのでmattermost版を作ってみた  
[セキュリティーグループが変更されたらSlackに通知する](https://www.hands-lab.com/tech/entry/2102.html)  

仕組みは上記のものとほとんど同じで、
CloudTrailが検知したイベントログをCloudWatchLogsに吐いて
そのログをトリガーにLambda Functionを実行してmattermostにwebhookします。

# 手順
## CloudTrailの設定
1. Trailsから任意のtrailを選択する
2. CloudWatch LogsセクションのConfigureをクリック
3. Log Groupを作成(Continue)
4. Log GroupとLog Streamを作成してよいか的なことを聞かれるのでAllowをクリック

## webhook用のURLの取得
1. サイドメニューから総合機能>>内向きのウェブフック>>内向きのウェブフックを追加するをクリック
2. 表示名と説明を入力し、通知したいチャンネルを選択して保存します。
3. URLが表示されるので記録しておきます。

## Lambda Functionの作成
### 作業ディレクトリの作成
```
$ mkdir lambda-work
$ cd lambda-work
```
### requestsのインストール
今回はpythonの外部ライブラリのrequestsを使用したのでそれをインストールします
`$ pip install requests -t .`
### mattermost-echo.pyの作成
Lambdaハンドラーから呼び出されるプログラムです。

```````python:mattermost-echo.py
# -*- coding: utf-8 -*-
from __future__ import print_function

import base64
import zlib
import json
import time
import boto3
import requests
import sys
import logging
from ast import literal_eval

print('Loading function')
url = 'https://chat.infra.active-ssa.com/hooks/d1snbwu8wi8g38g7ssu5wj7wtw'
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    print(event)
    logger.info(event)
    data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
    data_dict = literal_eval(data)
    data_list = (data_dict['logEvents'])
    print(data_list)

    for log in data_list:
        print(json.loads(json.dumps(log['message'])))
        log_json = json.loads(log['message'])
        name = str(log_json['eventName'])
        user = str(log_json['userIdentity']['userName'])
        region = str(log_json['awsRegion'])
        aws_account_id = str(log_json['recipientAccountId'])
        if 'ipPermissions' in log_json['requestParameters']:
            target_group = str(log_json['requestParameters']['groupId'])
            changes = json.loads(json.dumps(log_json['requestParameters']['ipPermissions']['items'], indent=2))
        else:
            target_group = str(log_json['responseElements']['groupId'])
            changes = None

        attache = []

        if not changes is None:
            for rule in changes:
                fromport = str(rule['fromPort'])
                if rule['ipRanges']:
                  ipranges = str(rule['ipRanges']['items'][0]['cidrIp'])
                else:
                  ipranges = None

                if rule['groups']:
                    groups = str(rule['groups']['items'][0]['groupId'])
                else:
                    groups = None

                toport = str(rule['toPort'])
                protocol = str(rule['ipProtocol'])

                if rule['ipv6Ranges']:
                    ipv6ranges = json.loads(json.dumps(rule['ipv6Ranges']))
                else:
                    ipv6ranges = None

                property = "|%(protocol)s|%(fromport)s|%(toport)s|%(ipranges)s|%(groups)s|%(ipv6ranges)s|" % locals ()
                attache.append(property)

        columns = ''
        for obj in attache:
            columns = columns + obj + "\n"

        if name == 'AuthorizeSecurityGroupIngress' or name == 'AuthorizeSecurityGroupEgress':
            message = 'セキュリティグループのルールが追加されました。'
        elif name == 'RevokeSecurityGroupIngress' or name == 'RevokeSecurityGroupEgress':
            message = 'セキュリティグループのルールが削除されました。'
        elif name == 'CreateSecurityGroup':
            message = 'セキュリティグループが作成されました。'
        elif name == 'DeleteSecurityGroup':
            message = 'セキュリティグループが削除されました。'

        payload = {
            'icon_url': 'http://recipe.kc-cloud.jp/wp-content/uploads/2014/04/cloudtrail%E3%82%A2%E3%82%A4%E3%82%B3%E3%83%B3.png',
            'username': 'Changes Security Group',
            'text': "\n\
- *Permission changes*\n\
\n\
---\n\
|protocol    |from port   |to port   |ip ranges   |groups    |ipv6 ranges   |\n\
|:-----------|:-----------|:---------|:-----------|:---------|:-------------|\n\
%(columns)s\n\
---\n\
:warning: %(message)s\n\
```\n\
AWSAccountID : %(aws_account_id)s\n\
Region       : %(region)s\n\
TargetGroup  : %(target_group)s\n\
EventName    : %(name)s\n\
UserName     : %(user)s\n\
```" % locals ()
        }

        headers = {'content-type': 'application/json'}
        session = requests.Session()
        session.mount('http://', requests.adapters.HTTPAdapter(max_retries=2))
        session.mount('https://', requests.adapters.HTTPAdapter(max_retries=2))
        try:
            resp = session.post(url,
                                data=json.dumps(payload, ensure_ascii=False),
                                headers=headers,
                                timeout=30)
            logger.info(resp)
        except Exception as e:
            logger.error(e)
            time.sleep(30)
            resp = session.post(url,
                                data=json.dumps(payload, ensure_ascii=False),
                                headers=headers,
                                timeout=30)
            logger.info(resp)

    return str(resp)

```````
URL等は任意に調整してください。
ほとんど初めて書くpythonコードなのでご容赦ください(rubyist)
and 間違いがあればご指摘ください。

これらをzipにかためてアップロードします
`$ zip -r mattermost-echo.zip *`
"mattermost-echo"という名前のLambda Function作成します。
Upload a ZIP fileを選択して、mattermost-echo.zipをアップロードします。
Handlerは`mattermost-echo.lambda_handler`と入力してください。
Timeoutは1minにしました。(とりあえず)
そのままSave。

###　トリガーの設定
1. CloudWatchLogsの画面を開く
2. 先程作成したLog Groupのチェックボックスを選択する（デフォルトならCloudTrail/DefaultLogGroup)
3. Stream to AWS Lambdaをクリック
4. mattermost-echoを選択
5. ログ形式を選択します。AWS CloudTrailを選択
6. filter patternを聞かれるので下記を入力してNextをクリックした後、Start Streamingをクリックします。

```
{ ($.eventName = "CreateSecurityGroup") || ($.eventName = "DeleteSecurityGroup") || ($.eventName = "AuthorizeSecurityGroupIngress") || ($.eventName = "RevokeSecurityGroupIngress")}
```
この時点でセキュリティグループに変更があれば通知されると思います。
CloudTrailの保存間隔が5分のため若干ラグがあるのでリアルタイムではないですが、十分です。
かなり一部分ですが、こんな感じの通知が来ます。
<img width="724" alt="スクリーンショット 2016-10-24 20.15.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/129135/7cb3ea85-649e-798c-9e33-d54c1efcd8f6.png">
他にもアカウントIDや変更操作したユーザー名等も出すようになっています。
mattermostのテーブル表示を使っているのでslackの人はslackの記法でtextを整形してあげると綺麗になると思います。

- いつのまにかSGのルールが変わってる！？っていう環境下の人は是非使ってみてください。

以上!!