こんにちは、テクマトリックス株式会社の大久保です。

Inspector、とても便利なサービスですよね。
AWSでとりあえず有効化しておくべきサービスの1つに入ると思います。

Inspectorには現在、

  • v1【Amazon Inspector Classic】
  • v2【Amazon Inspector】

の2つのバージョンがあります。

v1ではEC2に専用のエージェントのインストールを行い、
手動、もしくはEventBridgeと連携してスケジューリングし、定期的にスキャンをする必要がありました。
しかしv2では、SSMのエージェントがインストールされていれば、スケジューリングを行わなくてもすべて自動でやってくれるようになりました。

今回はそんな便利なInspector v2をさらに便利にするべく、脆弱性を検出したらTeamsに通知する方法について書きたいと思います。

今回やることの概要


フロー図

フロー

  1. Inspectorが稼働中にEC2に対してスキャンを実行
  2. EventBridgeでスキャンイベントを受信
  3. EventBridgeルールで、以下を場合のみスキャンイベントをLambdaへ通知
    • severityが「Medium」「HIGH」「CRITICAL」のいずれか
    • 対象のEC2に以下のタグが設定されている
      • Name:任意の値
      • InspectorV2:true
  4. Lambdaでスキャンイベントの情報を整形し、Teamsへ通知

前提条件

  • AWSにAdministratorAccess権限を持っているユーザーでログインしている。
  • Teamsに通知先のチャンネルが作成してあり、Incoming Webhookが設定してある。
  • Inspector v2が有効化されている。
  • スキャン対象のEC2にSSMのエージェントがインストールされている。
  • スキャン対象のEC2に「AmazonSSMManagedInstanceCore」ポリシーがアタッチされた、IAMロールがアタッチされている。
  • スキャン対象のEC2がインターネットと接続できる状態になっている。
  • スキャン対象のEC2に以下のタグが設定されている。
    • Name:任意の名前
    • InspectorV2:true

設定手順


Lambda用のIAMロールの作成

まずはLambda用のIAMロールを作成します。

  • IAMのコンソールから、「ロール」をクリックし、「ロールを作成」をクリック。
  • 以下を入力し、「次へ」をクリック。
    • 信頼されたエンティティタイプ:AWSのサービス
    • ユースケース:Lambda
  • 検索欄で「Basic」と入力して検索し、「AWSLambdaBasicExecutionRole」をアタッチ。
  • 「次へ」をクリック。
  • 以下を入力し、「ロールの作成」をクリック。
    • ロール名:任意の名前
    • 説明:任意の内容

通知を行うLambdaの作成

次にTeamsに通知を飛ばすLambdaの作成を行います。

  • コードは以下になります。
import os
import json
import urllib3
http = urllib3.PoolManager()
import datetime
url = os.environ['TEAMS_URL']

def utc_to_jst(str_utc, date_format='%Y-%m-%d %H:%M:%S'):
    time = datetime.datetime.strptime(str_utc, '%Y-%m-%dT%H:%M:%S%z')
    time += datetime.timedelta(hours=9)
    return time.strftime(date_format)


def lambda_handler(event, context):

    message_json = event
    #print(message_json)
    
    JST_TIME = utc_to_jst(message_json['time'])
    REGION = message_json['region']
    EVENT_TYPE = message_json['detail']['type']
    EVENT_SEVERITY = message_json['detail']['severity']
    EVENT_TITLE = message_json['detail']['title']
    RECOMMENDATION = message_json['detail']['remediation']['recommendation']['text']
    EVENT_STATUS = message_json['detail']['status']
    EVENT_RESOURCE = message_json['detail']['resources'][0]['tags']['Name']
    
    

    payload = {
                       "summary":"Inspector notification",   
                       "sections":[
                          {
                             "activityTitle":"<b>Inspector notification</b>"
                          },
                          {
                             "markdown": False,
                             "facts":[
                                {
                                   "name":"JST:",
                                   "value":JST_TIME
                                },
                                {
                                   "name":"Region:",
                                   "value":REGION
                                },
                                {
                                   "name":"EventType:",
                                   "value":EVENT_TYPE
                                },
                                {
                                   "name":"EventSeverity:",
                                   "value":EVENT_SEVERITY
                                },
                                {
                                   "name":"EventTitle:",
                                   "value":EVENT_TITLE
                                },
                                {
                                   "name":"Recommendation:",
                                   "value":RECOMMENDATION
                                },
                                {
                                   "name":"EventStatus:",
                                   "value":EVENT_STATUS
                                },
                                {
                                   "name":"ResourceName:",
                                   "value":EVENT_RESOURCE
                                }
                             ]
                          }
                       ]
                }
    print(payload)
            
    encoded_msg = json.dumps(payload).encode('utf-8')
    #print(encoded_msg)
    resp = http.request('POST',url, body=encoded_msg)

動きは以下になります。

  • EventBridgeから飛んでくる情報をパースし変数に代入
  • 代入した変数を用いて、JSON形式で通知内容を作成
  • Teamsに通知

注意点として、Nameタグを取得するように書いてあるので、Nameタグが付いてないEC2インスタンスだとLambdaが失敗します。

ちなみにEventBridgeから飛んでくるJSONはこんな感じです。

{
  "version": "0",
  "id": "efb7474e-473e-dfb6-c5fb-6476e2a875f3",
  "detail-type": "Inspector2 Finding",
  "source": "aws.inspector2",
  "account": "123456789012",
  "time": "2022-04-01T16:38:07Z",
  "region": "us-east-1",
  "resources": ["arn:aws:ecr:us-east-1:123456789012:repository/my-repo", "i-12345678"],
  "detail": {
    "awsAccountId": "123456789012",
    "description": "Multiple integer overflows in libwebp allows attackers to have unspecified impact via unknown vectors.",
    "findingArn": "arn:aws:inspector2:us-east-1:123456789012:finding/FINDING_ID",
    "firstObservedAt": "Apr 1, 2022, 4:38:07 PM",
    "inspectorScore": 7.8,
    "inspectorScoreDetails": {
      "adjustedCvss": {
        "adjustments": [],
        "cvssSource": "REDHAT_CVE",
        "score": 7.8,
        "scoreSource": "REDHAT_CVE",
        "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
        "version": "3.1"
      }
    },
    "lastObservedAt": "Apr 1, 2022, 4:38:07 PM",
    "packageVulnerabilityDetails": {
      "cvss": [{
        "baseScore": 7.8,
        "scoringVector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
        "source": "REDHAT_CVE",
        "version": "3.1"
      }, {
        "baseScore": 4.6,
        "scoringVector": "AV:L/AC:L/Au:N/C:P/I:P/A:P",
        "source": "NVD",
        "version": "2.0"
      }],
      "referenceUrls": [],
      "source": "REDHAT_CVE",
      "sourceUrl": "https://access.redhat.com/security/cve/CVE-2018-25020",
      "vendorCreatedAt": "May 17, 2018, 12:00:00 AM",
      "vendorSeverity": "Moderate",
      "vulnerabilityId": "CVE-2018-25020",
      "vulnerablePackages": [{
        "arch": "X86_64",
        "epoch": 0,
        "name": "kernel",
        "packageManager": "OS",
        "release": "200.489.amzn2",
        "version": "4.14.262"
      }]
    },
    "remediation": {
      "recommendation": {
        "text": "The default Red Hat Enterprise Linux kernel prevents unprivileged users from being able to use eBPF by the kernel.unprivileged_bpf_disabled sysctl. This would require a privileged user with CAP_SYS_ADMIN or root to be able to abuse this flaw reducing its attack space.\n\nFor the Red Hat Enterprise Linux 7 the eBPF for unprivileged users is always disabled.\nFor the Red Hat Enterprise Linux 8 to confirm the current state, inspect the sysctl with the command:\n\n# cat /proc/sys/kernel/unprivileged_bpf_disabled\n\nThe setting of 1 would mean that unprivileged users can not use eBPF, mitigating the flaw."
      }
    },
    "resources": [{
      "details": {
        "awsEc2Instance": {
          "iamInstanceProfileArn": "arn:aws:iam::123456789012:my-profile/my-role",
          "imageId": "ami-033eb06b56512a90f",
          "ipV4Addresses": ["3.84.189.204", "172.31.89.59"],
          "ipV6Addresses": [],
          "launchedAt": "Apr 1, 2022, 4:36:48 PM",
          "platform": "AMAZON_LINUX_2",
          "subnetId": "subnet-7aceba5b",
          "type": "c4.large",
          "vpcId": "vpc-56eb6e2b"
        }
      },
      "id": "i-07543563af836b6a8",
      "partition": "aws",
      "region": "us-east-1",
      "tags": {
                    "InspectorV2": "true",
                    "Name": "test-1"
                },
      "type": "AWS_EC2_INSTANCE"
    }],
    "severity": "HIGH",
    "status": "ACTIVE",
    "title": "CVE-2018-25020 - kernel",
    "type": "PACKAGE_VULNERABILITY",
    "updatedAt": "Apr 1, 2022, 4:38:07 PM"
  }
}

実際の作成手順について

  • Lambdaコンソールから、「関数の作成」をクリック。
  • 以下の設定を入力し、「関数の作成」をクリック。
    • 関数名:任意の名前
    • ランタイム:Python3.9
    • アーキテクチャ:x86_64
    • 実行ロール:先ほど作成したLambda用のIAMロール
  • コードソースにコードをコピペし、「Deploy」をクリック。
  • 「設定」「環境変数」で、TeamsのIncoming WebhookのURLを「TEAMS_URL」という環境変数として登録します。

Lambdaの検証

実際にちゃんとLambdaがTeamsに通知を飛ばしてくれるか検証します。

  • テストタブをクリックし、先ほど紹介したJSONのサンプルイベントを「イベントJSON」にコピペして、「テスト」をクリック。
  • 設定した内容に問題がなければ、実行結果が成功になります。
  • Teamsのチャンネルを見てみると、通知が来ていると思います、やったね。

EventBridgeのルールの作成

最後にLambdaをトリガーするためのEventBridgeのルールの作成を行います。

  • EventBridgeのコンソールから「ルール」「ルールを作成」をクリック。
  • 以下の設定を入力し、「次へ」をクリック。
    • 名前:任意の名前
    • 説明:任意の説明
    • イベントバス:default
    • ルールタイプ:イベントパターンを持つルール
  • 「イベントパターン」で「パターンを編集」をクリックし、以下のJSONをコピペし、「次へ」をクリック。
{
  "source": ["aws.inspector2"],
  "detail": {
    "resources": {
      "tags": {
        "InspectorV2": ["true"]
      }
    },
    "severity": ["MEDIUM", "HIGH", "CRITICAL"]
  }
}
  • 以下の設定を入力し、「次へ」をクリック。
    • ターゲットタイプ:AWSのサービス
    • ターゲットを選択:Lambda関数
    • 機能:作成したLambda関数

この後のタグ設定については、お好みで行ってください。

これで下準備はすべて終了です。

結果


EC2を起動してみる

ではでは、実際にEC2で脆弱性が検出された場合、本当に通知が飛ぶのか検証していきます。

  • EC2のコンソールから、「インスタンスの起動」をクリック。
  • 以下を入力し、「インスタンスを起動」をクリック。
    • 名前とタグ
      • Name:任意の名前
      • InspectorV2:true
    • アプリケーションおよび OS イメージ
      • Ubuntu, 20.04 LTS
    • インスタンスタイプ
      • 任意のタイプ
    • キーペア
      • キーペアなしで続行
    • ネットワーク設定
      • インターネットにつながっているサブネットを選択
    • ストレージ
      • デフォルトのまま
    • 高度な詳細
      • IAM インスタンスプロフィール:「AmazonSSMManagedInstanceCore」がついているIAMロール
  • Inspectorのコンソールから、「By instance」「Instances」で対象のEC2のIDをクリック。
はい、9個脆弱性が検出されてます。

通知きた

Teamsの方にもちゃんと通知がきてました、パチパチ。

まとめ


Inspectorは元々便利なサービスですが、スキャン結果をリアルタイムで通知させることでさらに便利なサービスとなります。
設定の方法もそこまで難しくないので、Inspectorを使っている方はぜひ試してみてください!

参考にした記事


By okubo

主にAWS上でのインフラ構築を担当してます。 ・AWS Certified Solutions Architect - Professional ・AWS Certified DevOps Engineer - Professional ・AWS Certified Database - Specialty ・AWS Certified Security - Specialty