Trusted AdvisorレコメンデーションをEventBridge Schedulerを使って定期的に通知してみた

2023.05.30

こんにちは、ゲームソリューショングループのsoraです。
今回は、Trusted AdvisorレコメンデーションをEventBridge Schedulerを使って定期的に通知してみたことについて書いていきます。

構成

EventBridge Schedulerで指定した間隔で、Lambdaを呼び出して、Trusted Advisorのレコメンデーションを取得する構成です。
取得した後は、SNS経由でSlack通知するようにします。

Terraformで作成

Terraformを使って作成します。
Trusted Advisorのチェック結果はバージニア北部から取得する必要があります。
私だけかもしれませんが、aws_lambda_permissionでLambdaのトリガーを追加することは忘れがちなので注意してください。

main.tf

terraform {
    #AWSプロバイダーのバージョン指定
    required_providers {
        aws = {
            source  = "hashicorp/aws"
            version = "~> 4.51.0"
        }
    }
}
#AWSプロバイダーの定義
provider aws {
    # Trusted Advisorのチェック結果はバージニア北部から取得する必要がある
    region = "us-east-1"
}


#============ IAMロール作成 ============
##EventBridge Sheduler 
data aws_iam_policy_document events_assume_role {
    statement {
        effect = "Allow"
        principals {
            type = "Service"
            identifiers = ["scheduler.amazonaws.com"]
        }
        actions = ["sts:AssumeRole"]
    }
}
resource aws_iam_role iam_for_events {
    name               = "EventBridge_TrustedAdvisor_Notification_Role"
    assume_role_policy = data.aws_iam_policy_document.events_assume_role.json
    managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSLambdaRole"]
}

##Lambda(Trusted Advisorからの情報取得用)
data aws_iam_policy_document lambda_assume_role {
    statement {
        effect = "Allow"
        principals {
            type = "Service"
            identifiers = ["lambda.amazonaws.com"]
        }
        actions = ["sts:AssumeRole"]
    }
}
resource aws_iam_role iam_for_lambda {
    name               = "Lambda_TrustedAdvisor_Notification_Role"
    assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
    inline_policy {
        name = "my_inline_policy"
        policy = jsonencode({
            Version = "2012-10-17"
            Statement = [
                {
                    Action   = ["support:DescribeTrustedAdvisorCheckResult"]
                    Effect   = "Allow"
                    Resource = "*"
                },
            ]
        })
    }
    managed_policy_arns = [
        "arn:aws:iam::aws:policy/AWSTrustedAdvisorPriorityReadOnlyAccess",
        "arn:aws:iam::aws:policy/service-role/AWSIoTDeviceDefenderPublishFindingsToSNSMitigationAction"
    ]
}


#============ リソース作成 ============
##SNS(トピック)
###トピックの作成
resource aws_sns_topic trusted_advisor_topic {
    name = "TrustedAdvisor-notification-topic"
}

##Lambda(Trusted Advisorからの情報取得用)
data archive_file lambda_trustedadvisor {
    type        = "zip"
    source_file = "lambda/lambda_trustedadvisor.py"
    output_path = "lambda_trustedadvisor.zip"
}
resource aws_lambda_function lambda_trustedadvisor {
    filename      = "lambda_trustedadvisor.zip"
    function_name = "trusted-advisor-detection"
    role          = aws_iam_role.iam_for_lambda.arn
    handler       = "lambda_trustedadvisor.lambda_handler"
    source_code_hash = data.archive_file.lambda_trustedadvisor.output_base64sha256
    runtime = "python3.9"
    environment {
        variables = {
            SNSTopicArn = aws_sns_topic.trusted_advisor_topic.arn
        }
    }
}

##Lambda(Slack通知用)
data archive_file lambda_slack {
    type        = "zip"
    source_file = "lambda/lambda_slack.py"
    output_path = "lambda_slack.zip"
}
resource aws_lambda_function lambda_slack {
    filename      = "lambda_slack.zip"
    function_name = "trusted-advisor-notification-to-slack"
    role          = aws_iam_role.iam_for_lambda.arn
    handler       = "lambda_slack.lambda_handler"
    source_code_hash = data.archive_file.lambda_slack.output_base64sha256
    runtime = "python3.9"
    environment {
        variables = {
            ChannelName = "<Slackのチャンネル名>",
            HookURL = "<HookURL>"
        }
    }
}
resource aws_lambda_permission lambda_slack_permission {
    action        = "lambda:InvokeFunction"
    function_name = aws_lambda_function.lambda_slack.function_name
    principal     = "sns.amazonaws.com"
    source_arn    = aws_sns_topic.trusted_advisor_topic.arn
}


##EventBridge Sheduler
resource aws_scheduler_schedule eventbridge {
    name = "trusted-advisor-notification-schedule"
    description = "Trusted Advisor Notification"
    flexible_time_window {
        mode = "OFF"
    }
    schedule_expression = "cron(0 0 1 * ? *)"
    schedule_expression_timezone = "Asia/Tokyo"
    target {
        arn = aws_lambda_function.lambda_trustedadvisor.arn
        role_arn = aws_iam_role.iam_for_events.arn
    }
}


##SNS(サブスクリプション)
###サブスクリプションの登録
resource aws_sns_topic_subscription slack_subscription {
    topic_arn = aws_sns_topic.trusted_advisor_topic.arn
    protocol  = "lambda"
    endpoint  = aws_lambda_function.lambda_slack.arn
}

Lambdaコード

今回の構成では、Trusted Advisorから情報取得する用とSlack通知用の2つがあります。
Trusted Advisorから取得する用のLambdaについて、項目ごとに設定されているチェックIDを使用して情報を取得します。
チェックIDは以下から確認できます。
AWS Trusted Advisor チェックリファレンス

今回はセキュリティグループで無制限アクセスが可能になっているものを検知してみます。
Python SDKのリクエスト/レスポンスについては、以下をご参照ください。 describe_trusted_advisor_check_result

lambda_trustedadvisor.py

import boto3
import json
import os

SNSTopicArn = os.environ['SNSTopicArn']

def lambda_handler(event, context):
    client = boto3.client('support', region_name='us-east-1')
    message = []
    response = client.describe_trusted_advisor_check_result(
        checkId="1iG5NDGVre",
        language='ja'
    )
    check_result = response['result']

    # SNSに通知するメッセージを作成
    message.append("セキュリティグループ — 無制限アクセス")
    for flag in check_result['flaggedResources']:
        if flag["status"] == "error":
            message.append(f"Status: {flag['status']} ,Region: {flag['region']}, SecurtyGroupName: {flag['metadata'][1]}")

    # メッセージをSNSに送信
    sns_client = boto3.client('sns')
    # 通知を送信するSNSトピックのARNを指定
    sns_topic_arn = SNSTopicArn
    sns_client.publish(
        TopicArn=sns_topic_arn,
        Message=json.dumps(message, indent=3, ensure_ascii=False)
    )

Slack通知用のLambdaは色々なところで書かれているため割愛します。

実行結果

EventBridge Schedulerで指定したタイミングで通知がきました。

最後に

今回は、Trusted AdvisorレコメンデーションをEventBridge Schedulerを使って定期的に通知してみたことを記事にしました。
どなたかの参考になると幸いです。