GuardDutyの検出結果をBedrockで人間が読みやすい形にしてSlackに通知してみた
こんにちは、ゲームソリューション部のsoraです。
今回は、GuardDutyの検出結果をBedrockで読みやすくしてSlackに通知してみたことについて書いていきます。
構成
以下が今回作成する構成です。
GuardDutyの検出結果をEventBridge経由でLambdaに渡して、Bedrockで読みやすい形に変換、その後にSlackへ通知する流れです。
先に通知したメッセージを貼ります。
前提
Bedrockにて使用するモデルは有効化済みとします。
GuardDutyの有効化・設定
GuardDutyを使用するため、有効化していなければ有効化しておきます。
EventBridgeへの送信について、デフォルトでイベントバスdefault
へ送信する形になっています。
EventBridgeルールを作成することで他サービスへの通知が可能です。
Terraformでのリソース作成(EventBridgeルール・Lambda)
AWSの他のリソースはTerraformで構築します。
EventBridgeルールで、GuardDutyからの以下のイベントをキャッチして、ターゲットであるLambdaに流します。
{
"detail-type": ["GuardDuty Finding"],
"source": ["aws.guardduty"]
}
Lambdaでは、AI用のプロンプトを記載してBedrockに通知内容を含めて呼び出します。
その結果として生成された文章を入れてSlackへ通知します。
import json
import boto3
import requests
import os
from datetime import datetime
def lambda_handler(event, context):
try:
print(f"Received event: {json.dumps(event)}")
finding = event['detail']
print(f"Processing finding: {finding.get('type', 'Unknown')}")
# Bedrock client
bedrock = boto3.client('bedrock-runtime')
# AI用プロンプト作成 (Claude Sonnet 4向け最適化)
prompt = f"""
あなたはAWSセキュリティの専門家です。以下のGuardDuty検知結果を分析し、技術者向けに日本語で要約してください:
## 検知内容
- **脅威タイプ**: {finding.get('type', 'Unknown')}
- **重要度**: {finding.get('severity', 'Unknown')}
- **対象リソース**: {finding.get('resource', {}).get('resourceType', 'Unknown')}
- **詳細説明**: {finding.get('description', 'No description')}
## 分析要求
以下の形式で回答してください:
**脅威概要**: [簡潔な説明]
**リスクレベル**: [重要度の説明]
**推奨対応**: [具体的なアクション]
"""
# Bedrock呼び出し (Claude 3.5 Sonnet - 安定版)
response = bedrock.invoke_model(
modelId='anthropic.claude-3-5-sonnet-20240620-v1:0',
body=json.dumps({
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": 2000,
"messages": [{"role": "user", "content": prompt}]
})
)
ai_response = json.loads(response['body'].read())
ai_explanation = ai_response['content'][0]['text']
print(f"AI analysis completed. Response length: {len(ai_explanation)}")
# Slack通知
slack_payload = {
"text": "GuardDuty脅威検知",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "GuardDuty脅威検知アラート"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ai_explanation
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": f"検知時刻: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
}
]
}
]
}
print(f"Sending notification to Slack...")
response = requests.post(
os.environ['SLACK_WEBHOOK_URL'],
json=slack_payload,
headers={'Content-Type': 'application/json'}
)
print(f"Slack response status: {response.status_code}")
return {
'statusCode': 200,
'body': json.dumps('Success')
}
except Exception as e:
print(f"Error: {str(e)}")
return {
'statusCode': 500,
'body': json.dumps(f'Error: {str(e)}')
}
Terraformのコードは以下です。
variables.tf
やterraform.tfvars
、IAM用のjsonなどは本ブログでは省略します。
### provider ###
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "6.0.0-beta3"
}
}
}
# AWSプロバイダーの定義
provider "aws" {
region = "ap-northeast-1"
}
### Resource ###
# EventBridge Rule
resource "aws_cloudwatch_event_rule" "guardduty_findings" {
name = "guardduty-findings-rule"
description = "Capture GuardDuty findings"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail-type = ["GuardDuty Finding"]
})
}
# EventBridge Target
resource "aws_cloudwatch_event_target" "lambda_target" {
rule = aws_cloudwatch_event_rule.guardduty_findings.name
target_id = "GuardDutyLambdaTarget"
arn = aws_lambda_function.guardduty_processor.arn
}
# Lambda Permission for EventBridge
resource "aws_lambda_permission" "allow_eventbridge" {
statement_id = "AllowExecutionFromEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.guardduty_processor.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.guardduty_findings.arn
}
# IAM Role for Lambda
resource "aws_iam_role" "lambda_role" {
name = "guardduty-processor-role"
assume_role_policy = file("${path.module}/policies/lambda-assume-role-policy.json")
}
# IAM Policy for Lambda
resource "aws_iam_role_policy" "lambda_policy" {
name = "guardduty-processor-policy"
role = aws_iam_role.lambda_role.id
policy = file("${path.module}/policies/lambda-execution-policy.json")
}
# Lambda Function Archive
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/lambda"
output_path = "${path.module}/lambda_deployment.zip"
}
# Lambda Function
resource "aws_lambda_function" "guardduty_processor" {
filename = data.archive_file.lambda_zip.output_path
function_name = "guardduty-processor"
role = aws_iam_role.lambda_role.arn
handler = "lambda.lambda_handler"
runtime = "python3.9"
timeout = 60
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
environment {
variables = {
SLACK_WEBHOOK_URL = var.slack_webhook_url
}
}
depends_on = [
aws_iam_role_policy.lambda_policy,
aws_cloudwatch_log_group.lambda_logs,
]
}
# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "lambda_logs" {
name = "/aws/lambda/guardduty-processor"
retention_in_days = 7
}
動作確認
GuardDutyのサンプルFinding(テスト用の偽の脅威検知データ)を作成することで検知・通知を試してみます。
私は全種類でサンプルを作成しましたが、約360個のデータが出てテスト後にアーカイブにするのが手間だったため、何かを指定して作成した方が良いと思います。
# detector-idの取得
aws guardduty list-detectors
# 全種類のサンプルFinding生成
aws guardduty create-sample-findings \
--detector-id 70c5e5972290759d4fb682357a2882ec
サンプルデータ作成後に以下のようにSlackに通知が来ました。
もしLambdaなどでエラーが出た際は、一度サンプルデータをアーカイブして、修正後に再度サンプルデータを作成し直すことで動作します。
最後に
今回は、GuardDutyの検出結果をBedrockで読みやすくしてSlackに通知してみたことを記事にしました。
どなたかの参考になると幸いです。