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