今回は、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: false , removalPolicy: 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として管理することで、環境間での一貫した設定の展開や、設定変更の履歴管理も可能になります。 皆さんのお役に立てば幸いです。