AWS Inspectorの評価内容をメール通知

# 経緯
InspectorのWebコンソールにアクセスせずとも結果を見たい。
かつ通知は重要度が高のものに限定し大量の通知は避けたい。
標準では評価結果を通知することが出来ない為、lambdaで頑張ります。
※プログラミング素人の為、参考にさせて頂いた方とほぼ同じです。

# Inspector
InspectorはEC2の脆弱性を評価し、結果をレポートにまとめてくれる素晴らしいサービスです。

[公式](https://docs.aws.amazon.com/ja_jp/inspector/latest/userguide/inspector_introduction.html) 
[ブラックベルト](https://d1.awsstatic.com/webinars/jp/pdf/services/20160622_AWS_BlackBelt-Inspector-public.pdf)

## メールの通知に必要な3つのJSON
inspectorからSNS連携すればよいのかと思っていましたが、違いました。
検知された脆弱性の詳細を得るには3つのJSONファイルを順番に参照する必要があります。


- 「実行完了」時に受信できるJSON

```json
{
    "template": "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:target/x-xxxxxxxx/template/x-xxxxxxxx",
    "findingsCount": "{arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:rulespackage/x-xxxxxxxxxx, arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:rulespackage/x-xxxxxxxxxx, arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:rulespackage/x-xxxxxxxxxx, arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:rulespackage/x-xxxxxxxxxx, arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:rulespackage/x-xxxxxxxxxx}",
    "run": "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:target/x-xxxxxxxx/template/x-xxxxxxxx/run/x-xxxxxxxx",
    "event": "ASSESSMENT_RUN_COMPLETED",
    "target": "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:target/x-xxxxxxxx"
}
```
- 上記のrunに記載されてあるArnから拾える「評価結果」一覧   
※検知された脆弱性の数で以下arnの件数は増減します。

```json
{
    "findingArns": [
        "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:target/x-xxxxxxxx/template/x-xxxxxxxx/run/x-xxxxxxxx/finding/x-xxxxxxxx",
        "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxx:target/x-xxxxxxxx/template/x-xxxxxxxx/run/x-xxxxxxxx/finding/x-xxxxxxxx",
    ]
}
```
- 「評価結果」のArnから拾える脆弱性の詳細

```json

{
    "findings": [
        {
            "assetType": "ec2-instance",
            "confidence": 10,
            "numericSeverity": 9.0,
            "description": "During key agreement in a TLS handshake using a DH(E) based ciphersuite a malicious server can send a very large prime value to the client. This will cause the client to spend an unreasonably long period of time generating a key for this prime resulting in a hang until the client has finished. This could be exploited in a Denial Of Service attack. Fixed in OpenSSL 1.1.0i-dev (Affected 1.1.0-1.1.0h). Fixed in OpenSSL 1.0.2p-dev (Affected 1.0.2-1.0.2o).",
            "service": "Inspector",
            "title": "Instance i-xxxxxxxxxxxxxxxxx is vulnerable to CVE-2018-0732",
            "assetAttributes": {
                "amiId": "ami-xxxxxxxxxxxxxxxxx",
                "hostname": "ec2-xx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com",
            },
            "recommendation": "Use your Operating System's update feature to update package openssl-1:1.0.2k-12.amzn2.0.3, openssl-libs-1:1.0.2k-12.amzn2.0.3. For more information see [https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0732](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-0732)",
            "id": "CVE-2018-0732",
            "severity": "High"
        }
    ]
}
```

## Lambda(Python3.6)

```python

import json
import boto3

TOPIC_ARN = 'SNSトピックのArn'

def mail(subject, msg):

    client = boto3.client('sns')

    request = {
        'TopicArn': TOPIC_ARN,
        'Message': msg,
        'Subject': subject
    }

    response = client.publish(**request)
    return response


# 評価結果
def get_findingArns(assessmentRunArn):
    client = boto3.client('inspector')

    findingArns = []
    nextToken = ""

    while(True):
        if(nextToken != ""):
            response = client.list_findings(
                assessmentRunArns=[
                    assessmentRunArn,
                ],
                maxResults=500,
                nextToken=nextToken
            )
        else:
            response = client.list_findings(
                assessmentRunArns=[
                    assessmentRunArn,
                ],
                maxResults=500
            )

        if("nextToken" not in response):
            for arn in response["findingArns"]:
                findingArns.append(arn)
            break
        else:
            for arn in response["findingArns"]:
                findingArns.append(arn)
            nextToken = response["nextToken"]

    return findingArns


# main
def lambda_handler(event, context):

    try:
        client = boto3.client('inspector')

        # RunArn
        obj = event["Records"][0]["Sns"]["Message"]
        obj = json.loads(obj)
        assessmentRunArn = obj["run"]
        assessmentTemplateArn = assessmentRunArn.rsplit("/", 2)[0]

        # findingArns
        findingArns = get_findingArns(assessmentRunArn)
        size = 100
        output = ""
        describe_findings = []
        findingArns_split = [findingArns[x:x + size] for x in range(0, len(findingArns), size)]
        for findingArns_100 in findingArns_split:
            describe_findings = describe_findings + client.describe_findings(findingArns=findingArns_100, locale='EN_US')["findings"]
        # 脆弱性の詳細内の"severity" = "High"である条件に合致した内容から以下の項目抜粋
        for finding in describe_findings:
            if finding["severity"] == "High": 
                title = finding["title"]
                amiId = finding["assetAttributes"]["amiId"]
                ec2name = finding["assetAttributes"]["hostname"]
                cveid = finding["id"]
                severity = finding["severity"]
                recommendation = finding["recommendation"]

                output = output + '【' + str(title) + '】' + \
                    '\n ami Id :' + str(amiId) + \
                    '\n ec2 name :' + str(ec2name) + \
                    '\n vulnerability id :' + str(cveid) + \
                    '\n severity :' + str(severity) + \
                    '\n recommendation :' + str(recommendation) + \
                    '\n inspector arn :' + finding["arn"] + '\n \n'
            else:
                pass

        # アラート
        if(output != ""):
            mail('Inspector alert', output)
        else:
            output = "Nothing to alert."
            mail('Inspector alert', output)

        return output

    except:
        import traceback
        output = traceback.format_exc()
        mail('Inspector error', output + "\n\n" + json.dumps(event, indent=4))
        return output
```

## Lambdaの必要なPolicy
- AmazonInspectorReadOnlyAccess 
- "sns:Publish"

## 通知内容

![image.png](https://qiita-image-store.s3.amazonaws.com/0/316662/9bbac7f3-469b-6225-66ec-4b1b7b4c3162.png)


# まとめ
当初SNS連携では詳細が得られなかった為、無理なのだと思いましたが、
様々な方のナレッジのお陰で実装することができました。
内容は薄いですが、誰かの参考になれば幸いです。

# 参考リンク
[Inspectorの実行結果をCSVファイルとして出力する](https://qiita.com/onooooo/items/dcd59f060267ba4d44be)
[Amazon Inspector の評価結果詳細を自動通知できるか調べてみた](https://www.techscore.com/blog/2018/12/11/amazon-inspector/)
[Amazon Inspectorの脆弱性診断結果の差分を通知する](https://hacknote.jp/archives/42294/)