Amazon GuardDutyの検知結果をSlackへ通知


##概要
先日、AWS re:Invent2017でAmazon GuardDuty(※)が発表されました。
これは数クリックで簡単にAWSアカウント上の脅威・リスクを検知するフルマネージドのサービスです。
[※Amazon GuardDuty概要](https://aws.amazon.com/jp/guardduty/)

脅威検知した際の通知についてはSNSを利用する等、自分で設定する必要がありますので
**Slack/OpsGenieへ通知してみました。**:surfer: 　　　

##AWS GuardDutyの導入方法
事前準備としてチェック対象ログ(CloudTrail、VPCフローログなど)の取得を開始しておく必要があります。設定していれば、このボタンをクリックし有効化するだけです。
![guardduty01.PNG](https://qiita-image-store.s3.amazonaws.com/0/224467/311ceffd-d98b-9926-c17c-f164f0fd8b91.png)

##通知してみる
今回はLambdaを使ってSlackとOpsGenieへ通知する2パターンを試してみました。
※それぞれ他ツールと連携するための事前準備手順については省略します。
　(Slack Incoming Webhooksの設定、OpsGenieのAPIキー発行など)

###1-1. Slackへ通知したい場合
- Slackへ通知するLambda関数(コードは後述)を登録し、あわせて下記環境変数を登録します。
    - slack Webhook URLとして "SLACK_URL" を登録
    - 同様に投稿先のChannelとして "SLACK_CHANNEL" を登録 

```python:slack用サンプルコード
#!/usr/bin/env python2
#-*- coding: utf-8 -*-
import os
import json
import logging
from urllib2 import Request, urlopen, URLError, HTTPError

logger = logging.getLogger()
logger.setLevel(logging.INFO)

slack_url = os.environ['SLACK_URL']
slack_channel = os.environ['SLACK_CHANNEL']

def guard2slack(event, context):
    detail = event['detail']
    description = detail['description']
    type = detail['type']
    date = detail['service']['eventFirstSeen']
    severity = detail['severity']
    text = str(type) + '検知!!\n 日時: ' + str(date) + \
            '\n 詳細: '+ str(description) + \
            '\n 深刻度: ' + str(severity)

    if severity < 6:
        color = "warning"
        emoji = ":fearful:"
        message = text
    else:
        color = "danger"
        emoji = ":cold_sweat:"
        message = '<!here>' + text
    
    slack_message = {
        'channel': slack_channel,
        'username': "AWS Check",
        'icon_emoji' : emoji,
        'attachments': [
                        {
                            'color': color,
                            'text': message,
                            'title': "GuardDuty"
                        }
                       ]
        }

    r = Request(slack_url, json.dumps(slack_message))
    try:
        response = urlopen(r)
        response.read()
    except HTTPError as e:
        logger.error("HTTP Error: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("URL Error: %s", e.reason)
```

###1-2. OpsGenieへ通知したい場合
- OpsGenieへ通知するLambda関数(コードは後述)を登録し、あわせて下記環境変数を登録します。
    - OpsGenieのAPIキーとして環境変数 "API_KEY" を登録
    - 同様に通知先のチーム名として "TEAMS" を登録 
- コード内にてOpsGenieSDKを利用しているので、  
ローカル環境にてopsgenie-sdkを落としてからコードと一緒にアップロードして下さい。

```python:OpsGenie用サンプルコード
#!/usr/bin/env python2
#-*- coding: utf-8 -*-
import os
import logging
from opsgenie.swagger_client import AlertApi
from opsgenie.swagger_client import configuration
from opsgenie.swagger_client.models import *
from opsgenie.swagger_client.rest import ApiException

logger = logging.getLogger()
logger.setLevel(logging.INFO)

api_key = os.environ['API_KEY']
teams = os.environ['TEAMS']

configuration.api_key['Authorization'] = api_key
configuration.api_key_prefix['Authorization'] = 'GenieKey'

def guard2ops(event, context):
    detail = event['detail']
    description = detail['description']
    type = detail['type']
    date = detail['service']['eventFirstSeen']
    severity = detail['severity']
    if severity < 6:
         priority='P3'
    else:
         priority='P1'
    text = str(type) + '検知!!\n 日時: ' + str(date) + \
            '\n 詳細: '+ str(description) + \
            '\n 深刻度: ' + str(severity)

    body = CreateAlertRequest(
        message= 'GuardDuty Alart: '+ str(type),
        description=text,
        teams=[TeamRecipient(name=teams)],
        visible_to=[TeamRecipient(name=teams, type='team')],
        entity='GuardDuty',
        priority=priority)

    try:
        response = AlertApi().create_alert(body=body)
        logger.info('request id: {}'.format(response.request_id))
        logger.info('took: {}'.format(response.took))
        logger.info('result: {}'.format(response.result))
    except ApiException as err:
        logger.error("Exception when calling AlertApi->create_alert: %s\n" % err)
```

###2. CloudWatchイベント設定
最後にCloudWatchイベントのルール設定画面にて
サービス名：GuardDuty、イベントタイプ：GuardDuty Findingを選択し、
ターゲットとして作成したLambda関数を指定します。
![guardduty03.PNG](https://qiita-image-store.s3.amazonaws.com/0/224467/981ec109-c3c7-c9fe-82a3-61f153434c5b.png)

###動作確認
- GuardDutyコンソール画面にて脅威検出時の結果サンプルを入手可能ですので、このサンプルを使って動作確認が可能です。
※注意:サンプルは大量に生成されるため、生成する場合は通知設定前 or 解除して取得することをお勧めします。
![guardduty06.PNG](https://qiita-image-store.s3.amazonaws.com/0/224467/4927b8d7-5d25-fb6e-3552-608fb2b146e6.png)


- サンプルイベントを使って無事に通知の確認をすることができました。  
    - Slack  
![guardduty04.PNG](https://qiita-image-store.s3.amazonaws.com/0/224467/2c69bff9-452a-85ff-c430-355e2baf4cf5.png)
    - OpsGenie
![guardduty05.PNG](https://qiita-image-store.s3.amazonaws.com/0/224467/f9cf59c2-be55-b800-6f30-42194a277191.png)

##まとめ
GuardDuty × Lambdaで脅威リスクを検知したら自動通知する仕組みを簡単に実現することができました。

あとは深刻度に応じた通知先/通知範囲や検出時の対応など運用ルールを詰めた上で実装していけば、 
AWS環境のセキュリティインシデント対応の自動化、セキュリティレベル向上に貢献できそうです。

##参考
OpsGenie python SDK: <https://github.com/opsgenie/opsgenie-python-sdk>