AWS CDKでCloudTrailの監査ログ収集を実装してみた

今回は、AWS Systems Managerのパッチ適用自動化に続いて、CloudTrailの監査ログ収集をAWS CDKで実装する方法をまとめました。

はじめに

今回は、CloudTrailログの収集からS3への保存、KMS暗号化による保護までをAWS CDKでコード化していきます。

 

今回作成するリソース

  • S3バケット: CloudTrailログの保存先
  • KMSキー: ログファイルの暗号化用
  • CloudTrail: API呼び出しの記録と配信
  • IAMポリシー: S3バケットアクセス制御とKMS権限設定
  • ライフサイクルポリシー: 366日での自動削除設定

 

AWS CDK ソースコード

S3バケットの設定

const cloudTrailBucket = new s3.Bucket(this, 'CloudTrailLogsBucket', {
  bucketName: `s3b-cloudtrail-logs-bucket01`,                                    	// バケット名
  blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,                                 	// パブリックアクセスをすべてブロック
  encryption: s3.BucketEncryption.S3_MANAGED,                                        	// 暗号化タイプ:SSE-S3
  autoDeleteObjects: true,                                                              // バケット削除時にオブジェクトも削除 デプロイ成功後にfalseに設定
  enforceSSL: true,                                                                  	// SSL/TLS暗号化を強制
  versioned: true,                                                                   	// バージョニング有効化
  lifecycleRules: [                                                                  	// ライフサイクルルール作成
    {
      id: 'CloudTrailLogsLifecycle',                                                 	// ライフサイクルルール名
      expiration: cdk.Duration.days(366),                                            	// 1年間保持
    }
  ],
  removalPolicy: cdk.RemovalPolicy.DESTROY,                                          	// スタック削除時にバケットも削除 デプロイ成功後にRETAINに変更
});

ポイント:

  • bucketName: 環境に応じてユニークな名前に変更が必要
  • blockPublicAccess: セキュリティのため完全ブロック
  • encryption: S3管理暗号化を使用
  • versioned: ログ改ざん防止のためバージョニング有効
  • lifecycleRules: コンプライアンス要件に応じて保持期間を調整
  • 本番環境ではautoDeleteObjects: falseremovalPolicy: RETAINに変更を推奨

S3バケットポリシーの設定

// 初回デプロイ用: CloudTrailバケットポリシー(デプロイ後コメントアウト)
// GetBucketAcl には条件を付けない
cloudTrailBucket.addToResourcePolicy(new iam.PolicyStatement({          	    // バケットポリシー追加1
  sid: 'AWSCloudTrailGetBucketAcl',                                           // ステートメントID
  effect: iam.Effect.ALLOW,                                                         // 許可
  principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
  actions: [                                                                        // 許可するアクション
    's3:GetBucketAcl',                                                              // バケットのACLを取得
  ],
  resources: [cloudTrailBucket.bucketArn]                                           // 対象バケット
}));

// 初回デプロイ用: CloudTrailバケットポリシー(デプロイ後コメントアウト)
// PutObject のみ条件付き
cloudTrailBucket.addToResourcePolicy(new iam.PolicyStatement({            	    // バケットポリシー追加2
  sid: 'AWSCloudTrailPutObject',                                                    // ステートメントID
  effect: iam.Effect.ALLOW,                                                         // 許可
  principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
  actions: ['s3:PutObject'],                                                        // オブジェクトの配置を許可
  resources: [`${cloudTrailBucket.bucketArn}/*`],                                   // 対象バケット配下のすべてのオブジェクト
  conditions: {                                                                     // 条件
    StringEquals: {
      's3:x-amz-acl': 'bucket-owner-full-control'                                   // バケット所有者にフルコントロールを付与
    }
  }
}));

ポイント:

  • CloudTrailサービスのみにアクセス権限を付与
  • s3:x-amz-acl条件でバケット所有者権限を保証
  • ログファイルの改ざん防止対策
  • CloudTrail作成後バケットポリシーが重複するため、構築後バケットポリシーをコメントアウト

KMSキーの設定

const cloudTrailKey = new kms.Key(this, 'CloudTrailKey', {
  description: 'KMS key for CloudTrail logs encryption',                            // キーの説明
  enableKeyRotation: true,                                                          // キーローテーション有効化
  removalPolicy: cdk.RemovalPolicy.DESTROY,                                         // スタック削除時にキーも削除 デプロイ成功後にRETAINに変更
});
cdk.Tags.of(cloudTrailKey).add('Name', 'CloudTrailKey');                            // タグ付け

// KMSキーのエイリアスを作成
new kms.Alias(this, 'CloudTrailKeyAlias', {
  aliasName: 'alias/cloudtrail-audit-key',                                          // エイリアス名
  targetKey: cloudTrailKey,                                                         // 対象キー
});

ポイント:

  • enableKeyRotation: true: セキュリティ強化のため自動キーローテーション
  • エイリアス設定により、キーの管理と識別が容易
  • 本番環境ではremovalPolicy: RETAINに変更を推奨

KMSキーポリシーの設定

// CloudTrailサービスがログを暗号化できる権限
cloudTrailKey.addToResourcePolicy(new iam.PolicyStatement({                	    // KMSキーポリシー追加1
  sid: 'Allow CloudTrail to encrypt logs',                                          // ステートメントID
  effect: iam.Effect.ALLOW,                                                         // 許可
  principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
  actions: ['kms:GenerateDataKey*'],                                                // データキー生成を許可
  resources: ['*'],                                                                 // すべてのリソース
  conditions: {                                                                     // 条件
    StringLike: {
      'kms:EncryptionContext:aws:cloudtrail:arn': `arn:aws:cloudtrail:*:${cdk.Aws.ACCOUNT_ID}:trail/*`
    }
  }
}));

// CloudTrailサービスがキーを記述できる権限
cloudTrailKey.addToResourcePolicy(new iam.PolicyStatement({                         // KMSキーポリシー追加2
  sid: 'Allow CloudTrail to describe key',                                          // ステートメントID
  effect: iam.Effect.ALLOW,                                                         // 許可
  principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
  actions: ['kms:DescribeKey'],                                                     // キー情報の記述を許可
  resources: ['*']                                                                  // すべてのリソース
}));

// アカウント内のプリンシパルがログファイルを復号化できる権限
cloudTrailKey.addToResourcePolicy(new iam.PolicyStatement({          		    // KMSキーポリシー追加3
  sid: 'Allow principals in the account to decrypt log files',                      // ステートメントID
  effect: iam.Effect.ALLOW,                                                         // 許可
  principals: [new iam.AnyPrincipal()],                                             // すべてのプリンシパル
  actions: [                                                                        // 許可するアクション
    'kms:Decrypt',                                                                  // 復号化
    'kms:ReEncryptFrom'                                                             // 再暗号化
  ],
  resources: ['*'],                                                                 // すべてのリソース
  conditions: {                                                                     // 条件
    StringEquals: {
      'kms:CallerAccount': cdk.Aws.ACCOUNT_ID                                       // 呼び出し元アカウント
    },
    StringLike: {
      'kms:EncryptionContext:aws:cloudtrail:arn': `arn:aws:cloudtrail:*:${cdk.Aws.ACCOUNT_ID}:trail/*`
    }
  }
}));

ポイント:

  • CloudTrailサービス専用の暗号化権限設定
  • アカウント内限定の復号化権限
  • kms:EncryptionContext条件でCloudTrail専用に制限

CloudTrailの設定

const trail = new cloudtrail.Trail(this, 'CloudTrail', {
  trailName: 'Audit',                                                               // CloudTrail名
  bucket: cloudTrailBucket,                                                         // ログ出力先S3バケット
  includeGlobalServiceEvents: true,                                                 // グローバルサービスイベントを含める
  isMultiRegionTrail: true,                                                         // マルチリージョントレイルとして設定
  enableFileValidation: true,                                                       // ファイル検証を有効化
  sendToCloudWatchLogs: false,                                                      // CloudWatch Logsへの送信は無効
  managementEvents: cloudtrail.ReadWriteType.ALL,                                   // 管理イベント:すべて(読み取り・書き込み)
  encryptionKey: cloudTrailKey,                                                     // 暗号化キー
});

ポイント:

  • isMultiRegionTrail: true: 全リージョンのイベントを記録
  • includeGlobalServiceEvents: true: IAM、CloudFrontなどグローバルサービスも記録
  • enableFileValidation: true: ログファイルの完全性検証
  • managementEvents: ALL: 読み取り・書き込み両方の管理イベントを記録
  • データイベントは必要に応じて別途設定
  • 管理イベントのみ記録

 

今回実装したコンストラクトファイルまとめ

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


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

    //===========================================
    // CloudTrail ログ用のS3バケット作成
    //===========================================
    const cloudTrailBucket = new s3.Bucket(this, 'CloudTrailLogsBucket', {
      bucketName: `s3b-cloudtrail-logs-bucket01`,                                    		// バケット名
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,                                 	// パブリックアクセスをすべてブロック
      encryption: s3.BucketEncryption.S3_MANAGED,                                        	// 暗号化タイプ:SSE-S3
      autoDeleteObjects: true,                                                                  // バケット削除時にオブジェクトも削除 デプロイ成功後にfalseに設定
      enforceSSL: true,                                                                  	// SSL/TLS暗号化を強制
      versioned: true,                                                                   	// バージョニング有効化
      lifecycleRules: [                                                                  	// ライフサイクルルール作成
        {
          id: 'CloudTrailLogsLifecycle',                                                 	// ライフサイクルルール名
          expiration: cdk.Duration.days(366),                                            	// 1年間保持
        }
      ],
      removalPolicy: cdk.RemovalPolicy.DESTROY,                                          	// スタック削除時にバケットも削除 デプロイ成功後にRETAINに変更
    });

    // 初回デプロイ用: CloudTrailバケットポリシー(デプロイ後コメントアウト)
    // GetBucketAcl には条件を付けない
    cloudTrailBucket.addToResourcePolicy(new iam.PolicyStatement({          		// バケットポリシー追加1
      sid: 'AWSCloudTrailGetBucketAcl',                                           // ステートメントID
      effect: iam.Effect.ALLOW,                                                         // 許可
      principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
      actions: [                                                                        // 許可するアクション
        's3:GetBucketAcl',                                                              // バケットのACLを取得
      ],
      resources: [cloudTrailBucket.bucketArn]                                           // 対象バケット
    }));

    // 初回デプロイ用: CloudTrailバケットポリシー(デプロイ後コメントアウト)
    // PutObject のみ条件付き
    cloudTrailBucket.addToResourcePolicy(new iam.PolicyStatement({            	        // バケットポリシー追加2
      sid: 'AWSCloudTrailPutObject',                                                    // ステートメントID
      effect: iam.Effect.ALLOW,                                                         // 許可
      principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
      actions: ['s3:PutObject'],                                                        // オブジェクトの配置を許可
      resources: [`${cloudTrailBucket.bucketArn}/*`],                                   // 対象バケット配下のすべてのオブジェクト
      conditions: {                                                                     // 条件
        StringEquals: {
          's3:x-amz-acl': 'bucket-owner-full-control'                                   // バケット所有者にフルコントロールを付与
        }
      }
    }));

    //===========================================
    // CloudTrail用のKMSキーを作成
    //===========================================
    const cloudTrailKey = new kms.Key(this, 'CloudTrailKey', {
      description: 'KMS key for CloudTrail logs encryption',                            // キーの説明
      enableKeyRotation: true,                                                          // キーローテーション有効化
      removalPolicy: cdk.RemovalPolicy.DESTROY,                                         // スタック削除時にキーも削除 デプロイ成功後にRETAINに変更
    });
    cdk.Tags.of(cloudTrailKey).add('Name', 'CloudTrailKey');                            // タグ付け

    // KMSキーのエイリアスを作成
    new kms.Alias(this, 'CloudTrailKeyAlias', {
      aliasName: 'alias/cloudtrail-audit-key',                                          // エイリアス名
      targetKey: cloudTrailKey,                                                         // 対象キー
    });

    // CloudTrailサービスがログを暗号化できる権限
    cloudTrailKey.addToResourcePolicy(new iam.PolicyStatement({                	        // KMSキーポリシー追加1
      sid: 'Allow CloudTrail to encrypt logs',                                          // ステートメントID
      effect: iam.Effect.ALLOW,                                                         // 許可
      principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
      actions: ['kms:GenerateDataKey*'],                                                // データキー生成を許可
      resources: ['*'],                                                                 // すべてのリソース
      conditions: {                                                                     // 条件
        StringLike: {
          'kms:EncryptionContext:aws:cloudtrail:arn': `arn:aws:cloudtrail:*:${cdk.Aws.ACCOUNT_ID}:trail/*`
        }
      }
    }));

    // CloudTrailサービスがキーを記述できる権限
    cloudTrailKey.addToResourcePolicy(new iam.PolicyStatement({              		// KMSキーポリシー追加2
      sid: 'Allow CloudTrail to describe key',                                          // ステートメントID
      effect: iam.Effect.ALLOW,                                                         // 許可
      principals: [new iam.ServicePrincipal('cloudtrail.amazonaws.com')],               // CloudTrailサービス
      actions: ['kms:DescribeKey'],                                                     // キー情報の記述を許可
      resources: ['*']                                                                  // すべてのリソース
    }));

    // アカウント内のプリンシパルがログファイルを復号化できる権限
    cloudTrailKey.addToResourcePolicy(new iam.PolicyStatement({          		// KMSキーポリシー追加3
      sid: 'Allow principals in the account to decrypt log files',                      // ステートメントID
      effect: iam.Effect.ALLOW,                                                         // 許可
      principals: [new iam.AnyPrincipal()],                                             // すべてのプリンシパル
      actions: [                                                                        // 許可するアクション
        'kms:Decrypt',                                                                  // 復号化
        'kms:ReEncryptFrom'                                                             // 再暗号化
      ],
      resources: ['*'],                                                                 // すべてのリソース
      conditions: {                                                                     // 条件
        StringEquals: {
          'kms:CallerAccount': cdk.Aws.ACCOUNT_ID                                       // 呼び出し元アカウント
        },
        StringLike: {
          'kms:EncryptionContext:aws:cloudtrail:arn': `arn:aws:cloudtrail:*:${cdk.Aws.ACCOUNT_ID}:trail/*`
        }
      }
    }));

    //===========================================
    // CloudTrailの作成
    //===========================================
    const trail = new cloudtrail.Trail(this, 'CloudTrail', {
      trailName: 'Audit',                                                               // CloudTrail名
      bucket: cloudTrailBucket,                                                         // ログ出力先S3バケット
      includeGlobalServiceEvents: true,                                                 // グローバルサービスイベントを含める
      isMultiRegionTrail: true,                                                         // マルチリージョントレイルとして設定
      enableFileValidation: true,                                                       // ファイル検証を有効化
      sendToCloudWatchLogs: false,                                                      // CloudWatch Logsへの送信は無効
      managementEvents: cloudtrail.ReadWriteType.ALL,                                   // 管理イベント:すべて(読み取り・書き込み)
      encryptionKey: cloudTrailKey,                                                     // 暗号化キー
    });
  }
}

 

まとめ

今回は、AWS CloudTrailを使用した監査ログ収集システムをAWS CDKで実装しました。
IaCとして管理することで、環境間での一貫した設定の展開や、設定変更の履歴管理も可能になります。
皆さんのお役に立てば幸いです。

著者について

AWSの基盤構築を担当しています。
小物集めにはまっています。

ameiをフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWS
シェアする
タイトルとURLをコピーしました