LoginSignup
1
3

AWS CDK : 特定のAPIコールを監視してみた (CloudTrail, EventBridge)

Last updated at Posted at 2024-04-28

概要

CloudTrailを用いて特定のAPIコールを監視する方法として下記の2パターンがあります。

image.png

今回は「1. EventBridgeのイベントソースとしてAPIを指定」のような「CloudTrailを用いて管理されたAPIコールをEventBridgeでフィルタリングし、特定のAPIが呼び出された時にSNS通知を行う」という構成をCDKで検証してまとめてみました。基本的にはBLEAテンプレートをベースにしております。

構成図

以下に示したようなAPIコールを監視する設計としています。

  • IAM Policyの作成・削除、アタッチ・デタッチ
  • Rootユーザのコンソールサインイン
  • Access Keyの新規作成
  • SGルールの作成・削除
  • NACLの作成・削除、NACLルールの作成・削除、NACLのサブネットへの関連付け
  • CloudTrailのロギング停止、証跡削除、証跡の設定変更

image.png

IAMリソースの操作ログはus-east-1に記録されるため、IAMの操作ログをSNSと連携するためのEventBridgeをus-east-1に、それ以外の操作ログをSNSと連携するためのEventBridgeをap-northeast-1に構築する必要があることに注意が必要です。

コンソール画面

ap-northeast-1

image.png
image.png
image.png
image.png

us-east-1

image.png
image.png

ディレクトリ構造

本記事ではbin, lib配下のファイルを紹介します。

├── bin
│   └── aws_cloud_trail_sample.ts
├── lib
│   └── Stack
│       ├── tokyo-stack.ts
│       └── us-stack.ts
│   └── Construct
│       ├── cloudtrail.ts
│       ├── eventbridge.ts
│       └── sns.ts
├── node_modules
├── test
├── cdk.json
├── jest.config.js
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json

CDK実装コード

bin

aws_cloud_trail_sample.ts

Stackのenv.regionプロパティを指定することにより、Stack毎にDeploy先のリージョンを指定する必要があります。指定しないとAWSの認証情報で設定したリージョンにデプロイされてしまいます。(aws configureで確認可能)

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AwsTokyoStack } from '../lib/Stack/tokyo-stack';
import { AwsUsStack } from '../lib/Stack/us-stack';

const app = new cdk.App();

new AwsTokyoStack(app, 'AwsTokyoStack', {
    env: {region: "ap-northeast-1"},
});

new AwsUsStack(app, 'AwsUsStack', {
    env: {region: "us-east-1"},
});

lib/Stack

  • tokyo-stack.ts : ap-northeast-1に構築するCloudTrail証跡、EventBridgeルール、SNSトピックを定義
  • us-stack.ts : us-east-1に構築するEventBridgeルール、SNSトピックを定義

今回の構成では証跡のホームリージョンはap-northeast-1に設定し、マルチリージョン証跡を有効化しています。そのため、全てのリージョンの証跡ログを格納するS3バケットはap-northeast-1に配置されます。

tokyo-stack.ts

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SnsConstruct } from '../Construct/sns';
import { TrailConstruct } from '../Construct/cloudtrail';
import { EventsConstruct } from '../Construct/eventbridge';

export class AwsTokyoStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ----- SNS Topic -----
    const Topic_for_Alarm = new SnsConstruct(this, "Topic1", {
      notifyEmail: "xxxxxxx@gmail.com"
    }).createTopic();
    
    // ----- CloudTrail Trail -----
    new TrailConstruct(this, "Test", {})

    // -----EventBridge Rules -----
    new EventsConstruct(this, "Rules", {
      region: "ap-northeast-1",
      topic: Topic_for_Alarm,
    })
    
  }
}

us-stack.ts

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SnsConstruct } from '../Construct/sns';
import { EventsConstruct } from '../Construct/eventbridge';

export class AwsUsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ----- SNS Topic -----
    const Topic_for_Alarm = new SnsConstruct(this, "Topic1", {
      notifyEmail: "xxxxxxx@gmail.com"
    }).createTopic();

    // -----EventBridge Rules -----
    new EventsConstruct(this, "Rules", {
      region: "us-east-1",
      topic: Topic_for_Alarm,
    })
    
  }
}

lib/Construct

cloudtrail.ts

証跡ログを格納するS3バケットとCloudTrail証跡を定義しています。今回はCloudWatch Logsに配信はしていません。(Logsは高額になりかねませんからね...)

import { Construct } from "constructs";
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as trail from 'aws-cdk-lib/aws-cloudtrail';

export interface TrailProps{}

export class TrailConstruct extends Construct{

    constructor(scope: Construct, id: string, props: TrailProps) {
        super(scope, id);

        // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html
        const TrailBucket = new s3.Bucket(this, "TrailBucket", {
            blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
            encryption: s3.BucketEncryption.S3_MANAGED,
        });

        // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudtrail.Trail.html#sendtocloudwatchlogs
        new trail.Trail(this, "TestTrail", {
            bucket: TrailBucket,
            enableFileValidation: true,
            isMultiRegionTrail: true,
            sendToCloudWatchLogs: false,
        });
    }

}

eventbridge.ts

env.regionプロパティの値がap-northeast-1なのかus-east-1なのかに応じて呼び出す関数を変えることで、リージョンごとに異なるEventBridgeルールを一つのConstructでデプロイすることを実現しています。

import { Construct } from "constructs";
import * as events from 'aws-cdk-lib/aws-events'
import * as sns from "aws-cdk-lib/aws-sns"
import * as eventsTarget from 'aws-cdk-lib/aws-events-targets';

export interface EventsProps {
    region: string,
    topic: sns.ITopic,
}

export class EventsConstruct extends Construct {

    constructor(scope: Construct, id: string, props: EventsProps) {
        super(scope, id);

        if (props.region === "ap-northeast-1") {
            this.createRulesInTokyo(props);
        } else if (props.region === "us-east-1") {
            this.createRuleInUs(props);
        }

    }

    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events-readme.html
    // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_events_targets-readme.html
    private createRulesInTokyo(props: EventsProps): void{
        new events.Rule(this, "SgChangeEventRule", {
            description: 'Notify to create, update or delete a Security Group.',
            enabled: true,
            eventPattern: {
                source: ['aws.ec2'],
                detailType: ['AWS API Call via CloudTrail'],
                detail: {
                    eventSource: ['ec2.amazonaws.com'],
                    eventName: [
                        'AuthorizeSecurityGroupIngress',
                        'AuthorizeSecurityGroupEgress',
                        'RevokeSecurityGroupIngress',
                        'RevokeSecurityGroupEgress',
                    ],
                },
            },
            targets: [new eventsTarget.SnsTopic(props.topic)],
        });

        new events.Rule(this, "NACLChangeEventRule", {
            description: 'Notify to create, update or delete a Network ACL.',
            enabled: true,
            eventPattern: {
                source: ['aws.ec2'],
                detailType: ['AWS API Call via CloudTrail'],
                detail: {
                    eventSource: ['ec2.amazonaws.com'],
                    eventName: [
                        'CreateNetworkAcl',
                        'CreateNetworkAclEntry',
                        'DeleteNetworkAcl',
                        'DeleteNetworkAclEntry',
                        'ReplaceNetworkAclEntry',
                        'ReplaceNetworkAclAssociation',
                    ],
                },
            },
            targets: [new eventsTarget.SnsTopic(props.topic)],
        });

        new events.Rule(this, "CloudTrailChangeEventRule", {
            description: 'Notify to change on CloudTrail log configuration',
            enabled: true,
            eventPattern: {
                detailType: ['AWS API Call via CloudTrail'],
                detail: {
                    eventSource: ['cloudtrail.amazonaws.com'],
                    eventName: ['StopLogging', 'DeleteTrail', 'UpdateTrail'],
                },
            },
            targets: [new eventsTarget.SnsTopic(props.topic)],
        });
    }
        
    private createRuleInUs(props: EventsProps): void{
        new events.Rule(this, "IAMPolicyChangeEventRule", {
            description: 'Notify to change IAM Configuration.',
            enabled: true,
            eventPattern: {
                source: ['aws.iam'],
                detailType: ['AWS API Call via CloudTrail'],
                detail: {
                    eventSource: ['iam.amazonaws.com'],
                    eventName: [
                        'DeleteGroupPolicy',
                        'DeleteRolePolicy',
                        'DeleteUserPolicy',
                        'PutGroupPolicy',
                        'PutRolePolicy',
                        'PutUserPolicy',
                        'CreatePolicy',
                        'DeletePolicy',
                        'CreatePolicyVersion',
                        'DeletePolicyVersion',
                        'AttachRolePolicy',
                        'DetachRolePolicy',
                        'AttachUserPolicy',
                        'DetachUserPolicy',
                        'AttachGroupPolicy',
                        'DetachGroupPolicy'
                    ]
                }
            },
            targets: [new eventsTarget.SnsTopic(props.topic)],
        });

        new events.Rule(this, "RootSigninRule", {
            description: "Events rule for monitoring root AWS Console Sign In activity",
            enabled: true,
            eventPattern: {
                source: ["aws.signin"],
                detailType: ["AWS Console Sign In via CloudTrail"],
                detail: {
                    userIdentity: {
                        type: ["Root"],
                    },
                    responseElements: {
                        ConsoleLogin: ["Success"],
                    }
                }
            },
            targets: [new eventsTarget.SnsTopic(props.topic)],
        });

        new events.Rule(this, "NewAccessKeyCreatedRule", {
            description: "Events rule for monitoring access key creation",
            enabled: true,
            eventPattern: {
                source: ["aws.iam"],
                detailType: ["AWS Console Sign In via CloudTrail"],
                detail: {
                    eventSource: ["iam.amazonaws.com"],
                    eventName: ["CreateAccessKey"]
                }
            },
            targets: [new eventsTarget.SnsTopic(props.topic)],
        });
    }

}

sns.ts

import { Construct } from "constructs";
import * as sns from "aws-cdk-lib/aws-sns"

export interface SnsProps {
    notifyEmail: string;
}

export class SnsConstruct extends Construct {
    private readonly topic: sns.ITopic

    constructor(scope: Construct, id: string, props: SnsProps) {
        super(scope, id);

        // https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_sns-readme.html
        const topic = new sns.Topic(this, "Topic for Alarm",{});

        new sns.Subscription(this, "MonitorAlarmEmail", {
            endpoint: props.notifyEmail,
            protocol: sns.SubscriptionProtocol.EMAIL,
            topic: topic,
        });
        this.topic = topic;
    }

    // not SnsConstruct but sns.ITopic
    public createTopic(): sns.ITopic {
        return this.topic;
    }
}

最後に

この構成にすると複数リージョンにEventBridgeやSNSをデプロイする必要があるので、少し管理も面倒ですね。CloudTrail→CloudWatch Logs→メトリクスフィルタ→Alarm→SNSの構成の方が柔軟かつシンプルに管理できそうです。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3