はじめに
ご覧いただきありがとうございます。
Amazon InspectorでEC2の脆弱性診断を行い、評価結果が"High"の対象のみを抽出してSNS通知を送れるようにしてみました。
概要
- Inspector~SNS~Lambda間の連携確認
- Lambdaの実装
事前準備
- AWSアカウント作成
- AdministratorAccessを付与したIAMユーザーの作成
- EC2作成/Amazon Inspectorのセットアップ
診断を行うと、AWSマネジメントコンソールから、診断結果をダウンロードできます。
High,Mediumが混在した38件の結果が表示されました。
それぞれ個別に内容を確認できます。
今回は診断結果が"High"のものをフィルタリングして、Lambdaを経由してEメールに通知を行うようにしていきます。
1.Inspector~SNS~Lambda間の連携確認
Inspectorの結果をEメールに通知するまでの流れは以下の通りです。
①Inspector(EventBridgeでスケジュール実行/SNS通知を設定)
②SNS(Lambda関数をサブスクライブ)
③Lambda
④SNS
⑤Eメールアドレス
まずはInspector~Lambda間を連携させていきます。
Lambda用のロール作成
- 信頼されたエンティティタイプ: AWSのサービス
- ユースケース: Lambda
- ポリシー: AmazonSNSFullAccess/AmazonInspectorFullAccess/AWSLambdaBasicExecutionRole
- ロール名: 任意の名前
Lambda関数の作成
- オプション: 一から作成
- 関数名: 任意の名前
- ランタイム: Python3.8
- アーキテクチャ: x86_64
- 既存のロールを使用する: 先ほど作成したIAMロールをアタッチ
関数作成後、設定タブで「タイムアウト」を10秒に変更する。
SNSトピック/サブスクリプションの設定
今回は2つのSNSトピックを作ります。
(1)
トピックの作成をクリック。
- タイプ: スタンダード
- 名前: 任意の名前
サブスクリプションの作成
- プロトコル: AWS Lambda
- エンドポイント: Lambda関数のARN
こちらのトピックは、Amazon Inspectorの評価テンプレートに紐づけてください。
(2)
トピックの作成をクリック。
- タイプ: スタンダード
- 名前: 任意の名前
サブスクリプションの作成
- プロトコル: Eメール
- エンドポイント: 通知先のEメールアドレスを入力
こちらのトピックを、後ほど作成するLambdaで利用します。
SNSからイベントを受け取る準備をする
次にLambdaがSNSからの通知を受け取るようにコードを書きます。
実際にInspectorからSNS通知を飛ばす前に、テストイベントで挙動を確認します。
テストタブで、テンプレートから「sns-notification」を選択します。
受け取ったイベントからSNSのメッセージを取り出します。
"Message"にある"example message"を取得します。
「sns-notification」でテストイベントを作成しましょう。
次にコードを作成します。
import boto3
import json
sns = boto3.client('sns')
def lambda_handler(event, context):
message = event['Records'][0]['Sns']['Message']
print(message)
return message
Testをします。
"example message"が表示されました。
では実際にInspectorの評価を実行して、SNS通知を受け取れるかを確認しましょう。
実際の運用では診断をスケジュール実行していきますが、今回は検証のため手動実行します。
Inspector側で分析が完了したら、CloudWatch Logsを確認します。
ロググループの中から該当のLambda関数のロググループを選択⇒最新のログストリームを選択。
SNSからメッセージを受け取ることができています。
2.Lambdaの実装
評価情報取得
前回CloudWatch Logsのログに表示された"run"の項目にあるarnをコピーしてください。
評価情報取得の仕方を確認します。
CloudShellを開きます。
list-findingsの「--assessment-run-arns」以下に上記のarnを指定します。
$ aws inspector list-findings --assessment-run-arns xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
{
"findingArns": [
"arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
・・・・・・・・・・・・・・・・・・・・・・・・・・・・・(省略)・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・
]
}
38件のarnが表示されます。
うち一つのarnをコピーして、describe-findingsの「--finding-arns」以下に指定します。
$ aws inspector describe-findings --finding-arns xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
{
"findings": [
{
"arn": "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"schemaVersion": 1,
"service": "Inspector",
"serviceAttributes": {
"schemaVersion": 1,
"assessmentRunArn": "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"rulesPackageArn": "arn:aws:inspector:ap-northeast-1:xxxxxxxxxxxxxxxxxxxxxxxxx"
},
"assetType": "ec2-instance",
"assetAttributes": {
"schemaVersion": 1,
"agentId": "xxxxxxxxxxxxxxxxxxxxxx",
"amiId": "xxxxxxxxxxxxxxxxxxxxx",
"hostname": "ec2-xxxxxxxxxxxxxxx.ap-northeast-1.compute.amazonaws.com",
"ipv4Addresses": [],
"tags": [
{
"key": "Name",
"value": "xxxxxxxxxxxxxxxxxxx"
}
],
"networkInterfaces": [
{
"networkInterfaceId": "xxxxxxxxxxxxxxxxxxx",
"subnetId": "xxxxxxxxxxxxxxxxxxxxxxxx",
"vpcId": "xxxxxxxxxxxxxxxxxxxxxxxxxx",
"privateDnsName": "ip-xxxxxxxxxxxx.ap-northeast-1.compute.internal",
"privateIpAddress": "xxxxxxxxxxxxxx",
"privateIpAddresses": [
{
"privateDnsName": "ip-xxxxxxxxxxxx.ap-northeast-1.compute.internal",
"privateIpAddress": "xxxxxxxxxxxxx"
}
],
"publicDnsName": "ec2-xxxxxxx.ap-northeast-1.compute.amazonaws.com",
"publicIp": "xxxxxxxxxxxxxxx",
"ipv6Addresses": [],
"securityGroups": [
{
"groupName": "xxxxxxxxxxxxxxxxx",
"groupId": "xxxxxxxxxxxxxxxxxx"
}
]
}
]
},
"id": "CVE-2022-0413",
"title": "Instance i-xxxxxxxxxxxxxxxxx is vulnerable to CVE-2022-0413",
"description": "Use After Free in GitHub repository vim/vim prior to 8.2.",
"recommendation": "Use your Operating System's update feature to update package vim-common-2:8.2.4428-1.amzn2.0.3(※以下略)",
"severity": "High",
"numericSeverity": 9.0,
"confidence": 10,
"indicatorOfCompromise": false,
以上内容が確認できます。
今回はboto3を使用して同じように情報を取得していきます。
"run"の情報を抜き出す
ではLambdaのコードを実装していきます。
import json
import boto3
sns = boto3.client('sns')
inspector = boto3.client('inspector')
def lambda_handler(event, context):
message = event["Records"][0]["Sns"]["Message"]
obj = json.loads(message)
# Run Arn
assessmentRunArn = obj["run"]
print(assessmentRunArn)
return assessmentRunArn
ここでは受け取ったSNSメッセージから、"run"の情報を抜き出しています。
findingArns
import json
import boto3
sns = boto3.client('sns')
inspector = boto3.client('inspector')
def lambda_handler(event, context):
message = event["Records"][0]["Sns"]["Message"]
obj = json.loads(message)
# Run Arn
assessmentRunArn = obj["run"]
# findingArns
response = inspector.list_findings(
assessmentRunArns=[
assessmentRunArn
]
)
finding_arns = response["findingArns"]
print(len(finding_arns))
print(finding_arns)
return finding_arns
list_findingsは、評価実行のARNによって指定された評価実行によって生成された結果を一覧表示します。
戻り値としてはdict型で、主にARNのリストが返ってきます。
5つのARNが返ってきました。
High/Mediumで38件のFindingsがあったので、数が合いません。
import json
import boto3
sns = boto3.client('sns')
inspector = boto3.client('inspector')
def get_findingArns(assessmentRunArn):
client = boto3.client('inspector')
findingArns = []
nextToken = ""
while(True):
if(nextToken != ""):
response = client.list_findings(
assessmentRunArns=[
assessmentRunArn,
],
maxResults=100,
nextToken=nextToken
)
else:
response = client.list_findings(
assessmentRunArns=[
assessmentRunArn,
],
maxResults=100
)
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
def lambda_handler(event, context):
message = event["Records"][0]["Sns"]["Message"]
obj = json.loads(message)
# Run Arn
assessmentRunArn = obj["run"]
# findingArns
response = get_findingArns(assessmentRunArn)
print(len(response))
print(response)
return response
nextTokenおよびmaxResultsの設定を追加しました。
改めて、Inspectorの診断を行いましょう。
CloudWatch Logsに38件のARNが出力されました。
続いて38件のARNのうち、Highのものを抽出します。
テスト用に"High"と"それ以外"を集計する変数を作ります。
import json
import boto3
sns = boto3.client('sns')
inspector = boto3.client('inspector')
def get_findingArns(assessmentRunArn):
client = boto3.client('inspector')
findingArns = []
nextToken = ""
while(True):
if(nextToken != ""):
response = client.list_findings(
assessmentRunArns=[
assessmentRunArn,
],
maxResults=100,
nextToken=nextToken
)
else:
response = client.list_findings(
assessmentRunArns=[
assessmentRunArn,
],
maxResults=100
)
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
def lambda_handler(event, context):
message = event["Records"][0]["Sns"]["Message"]
obj = json.loads(message)
# Run Arn
assessmentRunArn = obj["run"]
# findingArns
response = get_findingArns(assessmentRunArn)
# test
high_count = 0
other_count = 0
for arn in response:
finding = inspector.describe_findings(
findingArns=[
arn
]
)
if finding["findings"][0]["severity"] == 'High':
high_count += 1
else:
other_count += 1
print(high_count)
print(other_count)
return True
describe_findingsの使い方はこちらを御覧ください。
severityが"High"のものを抽出します。
結果はどうでしょうか。
Highが24件、それ以外が14件あるようです。
評価レポートの情報と一致しています。
ではHighの評価があるものを対象にして、メールに通知を行います。
import json
import boto3
sns_client = boto3.client('sns')
inspector = boto3.client('inspector')
topic_arn = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
def get_findingArns(assessmentRunArn):
client = boto3.client('inspector')
findingArns = []
nextToken = ""
while(True):
if(nextToken != ""):
response = client.list_findings(
assessmentRunArns=[
assessmentRunArn,
],
maxResults=100,
nextToken=nextToken
)
else:
response = client.list_findings(
assessmentRunArns=[
assessmentRunArn,
],
maxResults=100
)
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
def lambda_handler(event, context):
message = event["Records"][0]["Sns"]["Message"]
obj = json.loads(message)
# Run Arn
assessmentRunArn = obj["run"]
# findingArns
response = get_findingArns(assessmentRunArn)
for arn in response:
finding = inspector.describe_findings(
findingArns=[
arn
]
)
if finding["findings"][0]["severity"] == 'High':
id = finding["findings"][0]["id"]
title = finding["findings"][0]["title"]
description = finding["findings"][0]["description"]
recommendation = finding["findings"][0]["recommendation"]
severity = finding["findings"][0]["severity"]
mail_title = "Amazon Inspector診断結果"
message = f"【調査結果ID: {id}\nタイトル: {title}\n説明: {description}\n推奨: {recommendation}\n重要度: {severity}】"
mail = sns_client.publish(
TopicArn=topic_arn,
Subject=mail_title,
Message=message
)
return True
topic_arnは、各自のSNSトピックのARNをお使いください。
※今回のブログでは、2つ目に作成したSNSのトピックです。
結果はどうでしょうか。
上記のようなメールが24件来ていました。
想定どおりHighのものを対象として、メール通知がされています。
さいごに
以上、Inspectorの診断結果から欲しい情報をピックアップしてEメールに送ることができました。
御覧いただきありがとうございます!