今回は、Amazon GuardDuty による脅威検出と脅威通知を AWS CDK で実装する方法をまとめました。
はじめに
今回は、AWS GuardDutyを使用して、VPCフローログ、CloudTrail、DNSログを機械学習で分析し、悪意のある活動や異常な行動パターンをリアルタイムで検出して通知するリソースをAWS CDKで実装していきます。Guard Dutyによる脅威検出とS3への長期保存、EventBridge経由での即座な通知を組み合わせて実装します。
今回作成するリソース
- SNSトピック: GuardDuty脅威検出結果の通知
- KMS暗号化キー: GuardDutyデータの暗号化
- S3バケット: 検出結果の長期保存とアーカイブ
- AWS GuardDuty: 機械学習ベースの脅威検出エンジン
- EventBridge: 重要度別の自動通知ルール
アーキテクチャ概要
AWS CDK ソースコード
SNS通知設定
const emailAddresses = [ // SNS通知先メーリングリスト(通知先が複数ある場合はアドレスを追加)
'xxxxxx@example.com',
'xxxxxxx@example.com',
];
// GuardDuty用トピック
const guardDutyTopic = new sns.Topic(this, 'GuardDutyTopic', {
topicName: 'guardduty-alertnotification', // トピック名
displayName: 'GuardDuty Alert Notifications' // 表示名
});
// GuardDuty用サブスクリプション
emailAddresses.forEach(email => {
guardDutyTopic.addSubscription(
new subscriptions.EmailSubscription(email) // プロトコル:EMAIL
);
});
ポイント:
- 複数の管理者への通知配信
- アラーム発生時に通知するメールアドレスを指定
KMS暗号化キー設定
const guardDutyKey = new kms.Key(this, 'GuardDutyKey', {
alias: 'alias/guardduty-key', // エイリアス名
description: 'KMS key for GuardDuty encryption', // 説明
enableKeyRotation: true, // ローテーションの有効化
removalPolicy: cdk.RemovalPolicy.DESTROY // スタック削除時にキーを削除する ※デプロイ時にRETAINに変更
});
cdk.Tags.of(guardDutyKey).add('Name', 'guardduty-key'); // Nameタグ
ポイント:
- セキュリティ強化: GuardDuty専用の暗号化キー
- 自動ローテーション: セキュリティ基準に準拠した定期的なキー更新
- アクセス制御: 後述のキーポリシーで細かいアクセス制御
S3バケット設定(検出結果エクスポート)
// GuardDuty用S3バケット
const guardDutyBucket = new s3.Bucket(this, 'GuardDutyBucket', {
bucketName: 's3b-guardduty', // バケット名
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, // パブリックアクセスをすべてブロック
encryption: s3.BucketEncryption.S3_MANAGED, // 暗号化タイプ:SSE-S3
enforceSSL: true, // SSL通信を強制
autoDeleteObjects: true, // スタック削除時にオブジェクトを自動的に削除 ※デプロイ時にコメントアウト
removalPolicy: cdk.RemovalPolicy.DESTROY, // スタック削除時にバケットも削除 ※デプロイ時にRETAINに修正
lifecycleRules: [ // ライフサイクルルール作成
{
id: 'Expiration Rule 12 Months', // ライフサイクルルール名
expiration: cdk.Duration.days(366), // オブジェクトの現行バージョンの有効期限:366日後にオブジェクトを削除
}
]
});
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加1
sid: 'Deny incorrect encryption header',
effect: iam.Effect.DENY,
actions: ['s3:PutObject'],
resources: [`${guardDutyBucket.bucketArn}/*`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringNotLike: {
's3:x-amz-server-side-encryption-aws-kms-key-id': guardDutyKey.keyArn
}
}
}));
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加2
sid: 'Deny unencrypted object uploads',
effect: iam.Effect.DENY,
actions: ['s3:PutObject'],
resources: [`${guardDutyBucket.bucketArn}/*`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringNotEquals: {
's3:x-amz-server-side-encryption': 'aws:kms'
}
}
}));
ポイント:
- セキュア設計: パブリックアクセス完全ブロック、SSL強制
- 暗号化必須: KMS暗号化のみを許可するバケットポリシー
- 長期保存: セキュリティ調査用の1年間保持
- コンプライアンス: 暗号化されていないデータの拒否
AWS GuardDuty設定
const guardDutyDetector = new guardduty.CfnDetector(this, 'GuardDuty', {
enable: true, // GuardDutyの有効化
findingPublishingFrequency: 'FIFTEEN_MINUTES', // 検出結果の更新頻度:15分
dataSources: { // データソースの設定
s3Logs: {
enable: true // S3アクセスログの監視有効化
},
malwareProtection: { // マルウェア保護の設定
scanEc2InstanceWithFindings: {
ebsVolumes: true // EBSボリュームのスキャン有効化
}
}
}
});
const s3Export = new guardduty.CfnPublishingDestination(this, 'S3Export', {
detectorId: guardDutyDetector.ref, // GuardDuty Detectorの参照
destinationType: 'S3', // 出力先のタイプ
destinationProperties: { // 出力先のプロパティ
destinationArn: guardDutyBucket.bucketArn, // 出力先:S3バケット
kmsKeyArn: guardDutyKey.keyArn // KMSキーのARN
}
});
ポイント:
- 包括的監視: VPCフローログ、CloudTrail、DNSログ、S3アクセスログ
- マルウェア保護: EBSボリュームの自動スキャン機能
- リアルタイム更新: 15分間隔での検出結果更新
- 暗号化エクスポート: KMS暗号化でのS3保存
権限設定(KMS・S3ポリシー)
// KMSポリシー
guardDutyKey.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加1
sid: 'Allow GuardDuty to encrypt findings',
effect: iam.Effect.ALLOW,
actions: ['kms:GenerateDataKey*'],
resources: ['*'],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account
},
StringLike: {
'aws:SourceArn': `arn:aws:guardduty:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:detector/${guardDutyDetector.attrId}`
}
}
}));
// GuardDutyポリシー
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加3
sid: 'Allow PutObject',
effect: iam.Effect.ALLOW,
actions: ['s3:PutObject'],
resources: [`${guardDutyBucket.bucketArn}/*`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account,
'aws:SourceArn': `arn:aws:guardduty:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:detector/${guardDutyDetector.attrId}`
}
}
}));
// GuardDutyポリシー
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加4
sid: 'Allow GetBucketLocation',
effect: iam.Effect.ALLOW,
actions: ['s3:GetBucketLocation'],
resources: [`${guardDutyBucket.bucketArn}`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account,
'aws:SourceArn': `arn:aws:guardduty:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:detector/${guardDutyDetector.attrId}`
}
}
}));
ポイント:
- 最小権限: GuardDutyサービスのみに必要な権限を付与
- アカウント制限: SourceAccount条件でクロスアカウントアクセス防止
- ソースARN制限: 特定のGuardDuty Detectorのみからのアクセス許可
EventBridge統合
const guardDutyRule = new events.Rule(this, 'GuardDutyEventRule', { // GuardDuty用のEventBridge
ruleName: 'eventbridge-rule-guardduty', // ルール名
eventPattern: { // イベントパターンを指定
source: ['aws.guardduty'],
detailType: ['GuardDuty Finding'], // GuardDutyによって検出された結果(Findings)がインポートされた際に発行されるイベント
detail: {
severity: [
{
numeric: [ '>=', 7 ] // 重要度高(7.0~8.9)を通知
}
]
}
},
targets: [ // ターゲットを指定
new targets.SnsTopic(guardDutyTopic) // ターゲットタイプ: SNSトピック、トピック: GuardDuty用のトピック
]
});
ポイント:
- 重要度フィルタリング: 7.0以上の高リスク脅威のみ通知
今回実装したコンストラクトファイルまとめ
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as guardduty from 'aws-cdk-lib/aws-guardduty';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
export interface GuardDutyConstructProps {
// 必要に応じて追加のプロパティを定義
}
export class GuardDutyConstruct extends Construct {
constructor(scope: Construct, id: string, props?: GuardDutyConstructProps) {
super(scope, id);
//===========================================
// SNS
//===========================================
const emailAddresses = [ // SNS通知先メーリングリスト(通知先が複数ある場合はアドレスを追加)
'xxxxxx@example.com',
'xxxxxxx@example.com',
];
// GuardDuty用トピック
const guardDutyTopic = new sns.Topic(this, 'GuardDutyTopic', {
topicName: 'guardduty-alertnotification', // トピック名
displayName: 'GuardDuty Alert Notifications' // 表示名
});
// GuardDuty用サブスクリプション
emailAddresses.forEach(email => {
guardDutyTopic.addSubscription(
new subscriptions.EmailSubscription(email) // プロトコル:EMAIL
);
});
//===========================================
// KMS
//===========================================
const guardDutyKey = new kms.Key(this, 'GuardDutyKey', {
alias: 'alias/guardduty-key', // エイリアス名
description: 'KMS key for GuardDuty encryption', // 説明
enableKeyRotation: true, // ローテーションの有効化
removalPolicy: cdk.RemovalPolicy.DESTROY // スタック削除時にキーを削除する ※デプロイ時にRETAINに変更
});
cdk.Tags.of(guardDutyKey).add('Name', 'guardduty-key'); // Nameタグ
//===========================================
// S3
//===========================================
// GuardDuty用S3バケット
const guardDutyBucket = new s3.Bucket(this, 'GuardDutyBucket', {
bucketName: 's3b-guardduty', // バケット名
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, // パブリックアクセスをすべてブロック
encryption: s3.BucketEncryption.S3_MANAGED, // 暗号化タイプ:SSE-S3
enforceSSL: true, // SSL通信を強制
autoDeleteObjects: true, // スタック削除時にオブジェクトを自動的に削除 ※デプロイ時にコメントアウト
removalPolicy: cdk.RemovalPolicy.DESTROY, // スタック削除時にバケットも削除 ※デプロイ時にRETAINに修正
lifecycleRules: [ // ライフサイクルルール作成
{
id: 'Expiration Rule 12 Months', // ライフサイクルルール名
expiration: cdk.Duration.days(366), // オブジェクトの現行バージョンの有効期限:366日後にオブジェクトを削除
}
]
});
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加1
sid: 'Deny incorrect encryption header',
effect: iam.Effect.DENY,
actions: ['s3:PutObject'],
resources: [`${guardDutyBucket.bucketArn}/*`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringNotLike: {
's3:x-amz-server-side-encryption-aws-kms-key-id': guardDutyKey.keyArn
}
}
}));
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加2
sid: 'Deny unencrypted object uploads',
effect: iam.Effect.DENY,
actions: ['s3:PutObject'],
resources: [`${guardDutyBucket.bucketArn}/*`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringNotEquals: {
's3:x-amz-server-side-encryption': 'aws:kms'
}
}
}));
//===========================================
// GuardDuty作成
//===========================================
const guardDutyDetector = new guardduty.CfnDetector(this, 'GuardDuty', {
enable: true, // GuardDutyの有効化
findingPublishingFrequency: 'FIFTEEN_MINUTES', // 検出結果の更新頻度:15分
dataSources: { // データソースの設定
s3Logs: {
enable: true // S3アクセスログの監視有効化
},
malwareProtection: { // マルウェア保護の設定
scanEc2InstanceWithFindings: {
ebsVolumes: true // EBSボリュームのスキャン有効化
}
}
}
});
const s3Export = new guardduty.CfnPublishingDestination(this, 'S3Export', {
detectorId: guardDutyDetector.ref, // GuardDuty Detectorの参照
destinationType: 'S3', // 出力先のタイプ
destinationProperties: { // 出力先のプロパティ
destinationArn: guardDutyBucket.bucketArn, // 出力先:S3バケット
kmsKeyArn: guardDutyKey.keyArn // KMSキーのARN
}
});
// GuardDuty Detectorへの依存関係を追加
s3Export.node.addDependency(guardDutyDetector);
// S3バケットへの依存関係を追加(必要に応じて)
s3Export.node.addDependency(guardDutyBucket);
// KMSキーへの依存関係を追加(必要に応じて)
s3Export.node.addDependency(guardDutyKey);
//===========================================
// KMS/S3 一部ポリシー追加
//===========================================
// KMSポリシー
guardDutyKey.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加1
sid: 'Allow GuardDuty to encrypt findings',
effect: iam.Effect.ALLOW,
actions: ['kms:GenerateDataKey*'],
resources: ['*'],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account
},
StringLike: {
'aws:SourceArn': `arn:aws:guardduty:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:detector/${guardDutyDetector.attrId}`
}
}
}));
// GuardDutyポリシー
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加3
sid: 'Allow PutObject',
effect: iam.Effect.ALLOW,
actions: ['s3:PutObject'],
resources: [`${guardDutyBucket.bucketArn}/*`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account,
'aws:SourceArn': `arn:aws:guardduty:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:detector/${guardDutyDetector.attrId}`
}
}
}));
// GuardDutyポリシー
guardDutyBucket.addToResourcePolicy(new iam.PolicyStatement({ // ポリシー追加4
sid: 'Allow GetBucketLocation',
effect: iam.Effect.ALLOW,
actions: ['s3:GetBucketLocation'],
resources: [`${guardDutyBucket.bucketArn}`],
principals: [new iam.ServicePrincipal('guardduty.amazonaws.com')],
conditions: {
StringEquals: {
'aws:SourceAccount': cdk.Stack.of(this).account,
'aws:SourceArn': `arn:aws:guardduty:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:detector/${guardDutyDetector.attrId}`
}
}
}));
//===========================================
// EventBridge
//===========================================
// GuardDuty用ルール
const guardDutyRule = new events.Rule(this, 'GuardDutyEventRule', { // GuardDuty用のEventBridge
ruleName: 'eventbridge-rule-guardduty', // ルール名
eventPattern: { // イベントパターンを指定
source: ['aws.guardduty'],
detailType: ['GuardDuty Finding'], // GuardDutyによって検出された結果(Findings)がインポートされた際に発行されるイベント
detail: {
severity: [
{
numeric: [ '>=', 7 ] // 重要度高(7.0~8.9)を通知
}
]
}
},
targets: [ // ターゲットを指定
new targets.SnsTopic(guardDutyTopic) // ターゲットタイプ: SNSトピック、トピック: GuardDuty用のトピック
]
});
}
}
まとめ
今回は、AWS GuardDutyを活用した機械学習ベースの脅威検出システムをAWS CDKで実装しました。
皆さんのお役に立てば幸いです。