こんにちは。たかやまです。
今回はCloudTrailを1つの証跡でログアーカイブアカウントにまとめつつ、メンバーアカウントにログを残す方法をご紹介したいと思います。
CloudTrail証跡を2つ作成した場合の問題点
CloudTrailは監査上もっとも重要なサービスで、多くの方はCloudTrailのログを保存する「証跡」を作成していると思います。
また、マルチアカウント環境であれば、各アカウントのCloudTrailログを集約して、ログを一元管理したいというニーズもあると思います。
Organizations環境であれば「組織の証跡」、Organizationsが使えない環境では証跡の送信先をログアーカイブアカウントのS3に設定することで証跡を集約することができます。
ただこのとき、1つの証跡ごとに保管できるログの宛先は1つしか指定できないため、集約される側のメンバーアカウントにCloudTrailログを残すことができません。
これだと、メンバーアカウント側でCloudTrailログに対して調査したい場合はわざわざログアーカイブアカウント側からログを取り出しや連携する手間が発生します。
「証跡を2つ作ればいいのでは?」という声もあると思いますが、CloudTrailは2つの証跡から管理イベントに料金が発生します。発生する料金を許容できるのであれば問題ないと思いますが、このCloudTrailの管理イベント料金がまたそこまで安くありません...
私の検証環境の2023/3月の管理イベント総数は2,756,575イベントでこちらが料金としてかかってくる場合だいたい以下の値段が発生します。検証環境でこの価格なので、実際に利用されるアカウントであればより管理イベントが発生すると思います。
- 2,756,575 / 100,000 * 2 ≒ 約55USD/月
ここまでを図にまとめると以下のような感じです。
そこでメンバーアカウントにログを残しつつコスト効率よく集約する方法として、タイトルのCloudTrail証跡をレプリケーションで集約管理する方法をご紹介したいと思います。
アーキテクチャ
実装するアーキテクチャはこちらです。
先にまとめ
- メンバーアカウントにログを残す必要がなければ、組織の証跡を利用する(組織の証跡使えなければ証跡の宛先をログアーカイブに直接向ける)
- メンバーアカウントにログを残す必要がある場合は、レプリケーションで集約する
- コストはレプリケーションにかかわる料金(1$未満)で実装できる
- メンバーアカウントでCloudTrail証跡を無効化されないようにSCPやIAMでの制御の検討が必要
やってみた
[ログアーカイブアカウント]S3バケットの作成
まずはログアーカイブアカウント側でレプリケーションが可能なS3バケットを用意します。
主な設定箇所は以下の通りです。
バケット名
: 適当な名前を設定バケットのバージョニング
: レプリケーションを利用する場合、有効化が必須オブジェクトロック
: ここでは無効にしてますが、本番環境で監査ログを集約する場合はオブジェクトロックの機能も有効化しておくと良いと思います
[メンバーアカウント]S3バケット/IAM Role/CloudTrailの作成
ログアーカイブアカウントにS3バケットを作成したら、メンバーアカウント側でレプリケーションするためのS3バケット/IAM Role/CloudTrailを作成していきます。
今回はOrganizations運用を想定してCloudFormation StackSetsで行っていきます。S3バケット名はグローバルでユニークである必要があるため、作成時の名前が重複しないよう留意が必要です。
(ここではアカウントIDをバケット名に付与するようにしています)
デプロイするテンプレートファイルはこちらです。
Parameters:
Account:
Type: String
Default: ""
Description: Log Archive AccountID
BucketName:
Type: String
Default: ""
Description: Audit Bucket name from the Log Archive Account
OrgUnitId:
Type: String
Default: ""
Description: Organizations Unit ID
Resources:
AuditBucketB01E0AE8:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
BucketName:
Fn::Join:
- ""
- - organizations-cloudtrail-
- Ref: AWS::AccountId
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
ReplicationConfiguration:
Role:
Fn::GetAtt:
- ReplicationRoleCE149CEC
- Arn
Rules:
- DeleteMarkerReplication:
Status: Disabled
Destination:
AccessControlTranslation:
Owner: Destination
Account:
Ref: Account
Bucket:
Fn::Join:
- ""
- - "arn:aws:s3:::"
- Ref: BucketName
Metrics:
Status: Enabled
ReplicationTime:
Status: Disabled
Time:
Minutes: 15
StorageClass: STANDARD
Filter:
Prefix: ""
Priority: 1
Status: Enabled
VersioningConfiguration:
Status: Enabled
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
AuditBucketPolicy9F807F4B:
Type: AWS::S3::BucketPolicy
Properties:
Bucket:
Ref: AuditBucketB01E0AE8
PolicyDocument:
Statement:
- Action: s3:*
Condition:
Bool:
aws:SecureTransport: "false"
Effect: Deny
Principal:
AWS: "*"
Resource:
- Fn::GetAtt:
- AuditBucketB01E0AE8
- Arn
- Fn::Join:
- ""
- - Fn::GetAtt:
- AuditBucketB01E0AE8
- Arn
- /*
- Action: s3:GetBucketAcl
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource:
Fn::GetAtt:
- AuditBucketB01E0AE8
- Arn
- Action: s3:PutObject
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
Resource:
Fn::Join:
- ""
- - Fn::GetAtt:
- AuditBucketB01E0AE8
- Arn
- /
- Ref: OrgUnitId
- /AWSLogs/
- Ref: AWS::AccountId
- /*
Version: "2012-10-17"
ReplicationRoleCE149CEC:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: s3.amazonaws.com
Version: "2012-10-17"
RoleName: replication-role
ReplicationPolicy48D09A53:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Statement:
- Action:
- s3:GetReplicationConfiguration
- s3:ListBucket
Effect: Allow
Resource:
Fn::GetAtt:
- AuditBucketB01E0AE8
- Arn
- Action:
- s3:GetObjectVersionAcl
- s3:GetObjectVersionForReplication
- s3:GetObjectVersionTagging
Effect: Allow
Resource:
Fn::Join:
- ""
- - Fn::GetAtt:
- AuditBucketB01E0AE8
- Arn
- /*
- Action:
- s3:ReplicateObject
- s3:ReplicateTags
Effect: Allow
Resource:
Fn::Join:
- ""
- - "arn:aws:s3:::"
- Ref: BucketName
- /*
Version: "2012-10-17"
PolicyName: replication-policy
Roles:
- Ref: ReplicationRoleCE149CEC
OrganizationsCloudTrailBED259DC:
Type: AWS::CloudTrail::Trail
Properties:
IsLogging: true
S3BucketName:
Ref: AuditBucketB01E0AE8
EnableLogFileValidation: true
EventSelectors: []
IncludeGlobalServiceEvents: true
IsMultiRegionTrail: true
S3KeyPrefix:
Ref: OrgUnitId
TrailName: organizations-cloudtrail
DependsOn:
- AuditBucketPolicy9F807F4B
デプロイするCloudFormation StackSetsの主な設定はこちらです。
- パラメーター
Account
: ログアーカイブアカウントのアカウントIDBucketName
: ログアーカイブアカウントのS3バケット名OrgUnitId
: Organizations Unit Id(CloudTrailログの保存先パスに使用)
リージョン
: CloudTrailログを保存するリージョン(CloudTrailがマルチリージョン対応しているため1つリージョンで問題ないです)
S3バケット
作成されるバケットの主な設定箇所は以下の通りです。
バケットのバージョニング
: レプリケーションを利用する場合、有効化が必須レプリケーションルール
- 送信先
- 送信先バケット名 : ログアーカイブアカウントのS3バケット名
- オブジェクト所有者 : 送信先バケット所有者への転送
- 追加のレプリケーションオプション
- レプリケーションメトリクスと通知 : 有効
- レプリケーション時間のコントロール : ここでは無効にしてますが、15分以内にレプリケーションしたい場合にはこちも有効にしてください。
- 送信先
IAM Role
作成されるIAM Roleの主な設定箇所は以下の通りです。
- 許可ポリシー
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:GetReplicationConfiguration",
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::<メンバーアカウントS3バケット名>",
"Effect": "Allow"
},
{
"Action": [
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionTagging"
],
"Resource": "arn:aws:s3:::<メンバーアカウントS3バケット名>/*",
"Effect": "Allow"
},
{
"Action": [
"s3:ReplicateObject",
"s3:ReplicateTags"
],
"Resource": "arn:aws:s3:::<ログアーカイブアカウントバケット名>/*",
"Effect": "Allow"
}
]
}
s3:ReplicateDelete
権限は不要なため、明示的に抜いています。
- 信頼関係
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "s3.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
CloudTrail
作成されるCloudTrailの主な設定箇所は以下の通りです。
証跡ログバケット名
: ログアーカイブアカウントのバケット名プレフィックス
: Organizations Unit Id(CloudTrailログの保存先パスに使用)
[ログアーカイブアカウント]バケットポリシー設定
メンバーアカウントでIAM Roleが作成されたらログアーカイブアカウントのS3バケットにレプリケーションを許可するバケットポリシーを設定します。
設定する内容は以下の通りです。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Set permissions for objects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<メンバーアカウントID>:role/service-role/replication-role"
},
"Action": "s3:ReplicateObject",
"Resource": "arn:aws:s3:::<ログアーカイブアカウントバケット名>/*"
},
{
"Sid": "Set permissions on bucket",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<メンバーアカウントID>:role/service-role/replication-role"
},
"Action": [
"s3:List*",
"s3:GetBucketVersioning",
"s3:PutBucketVersioning"
],
"Resource": "arn:aws:s3:::<ログアーカイブアカウントバケット名>"
}
]
}
s3:ReplicateDelete
権限は不要なため、明示的に抜いています。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-walkthrough-2.html
公式で案内されているPrincipal
の設定は、現在存在しているIAM Roleのみ指定することができます。なので、将来的に作成されるアカウントIDが入ったIAM Roleを指定することはできません。
ワークアラウンド的に、Principal
には*
を指定し、Condition
でArnLike
を指定することで、IAM Roleの作成を待たずにレプリケーションを許可することもできます。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Set permissions for objects",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:ReplicateObject",
"Resource": "arn:aws:s3:::<ログアーカイブアカウントバケット名>/*",
"Condition": {
"ArnLike": {
"aws:PrincipalArn": "arn:aws:iam::<メンバーアカウントID>:role/replication-role"
}
}
},
{
"Sid": "Set permissions on bucket",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": [
"s3:List*",
"s3:GetBucketVersioning",
"s3:PutBucketVersioning"
],
"Resource": "arn:aws:s3:::<ログアーカイブアカウントバケット名>",
"Condition": {
"ArnLike": {
"aws:PrincipalArn": "arn:aws:iam::<メンバーアカウントID>:role/replication-role"
}
}
}
]
}
将来的にアカウントを追加する可能性がある場合にはCondition
を利用して管理すると良いかと思います。
動作確認
メンバーアカウント
メンバーアカウントに作成されたS3バケットにCloudTrailログが出力されていることを確認します。
保存されているファイルを選択し、管理設定 -> レプリケーションステータス がCOMPLETED
になっていることを確認します。
ログアーカイブアカウント
ログアーカイブアカウントに先程確認したメンバーアカウントのオブジェクトと同じ階層にCloudTrailログが存在することを確認します。
ログアーカイブアカウント側はレプリケーションステータスがREPLICA
で、所有者がログアーカイブアカウントになっていればレプリケーション完了です!
料金
今回のレプリケーションの料金要素は以下の通りです。
- コピー元ストレージ料金 : スタンダード 0.025USD/GB
- 宛先S3ストレージ料金 : スタンダード 0.025USD/GB
- レプリケーションPUTリクエスト : 1,000リクエストあたり0.0047USD
- 各送信先リージョンへのデータ転送OUT : 0.114USD/GB
- (オプション)レプリケーション時間制御データ転送料金 0.015USD/GB + S3レプリケーションメトリクス料金 : 0.015USD/GB https://aws.amazon.com/jp/s3/pricing/
レプリケーション時間制御は利用しないパターンで、自分の検証環境の2023/3月分のCloudTrailログファイル数とサイズ数をもとに料金を算出していきます。
※S3バケット料金はCloudTrailも同様にかかるため今回の算出では含めません。
aws s3 ls s3://cm-members-cloudtrail-XXXXXXXXXXXX/AWSLogs/ --recursive \
| grep '202303' \
| awk '{ total_files += 1; total_size += $3 } END { print "Number of files: "total_files"\nTotal log size: "total_size/1024/1024" MB" }'
Number of files: 85175
Total log size: 250.196 MB
- レプリケーションPUTリクエスト : 85,175 / 1,000 * 0.0047 ≒ 0.4 USD
- データ転送OUT : 250.196 / 1024 * 0.114 ≒ 0.03 USD
合計 : 0.43 USD
最初にご説明したCloudTrailの料金(55$)に比べて、レプリケーションを利用することでメンバーアカウントにCloudTrailログを残したまま、安価にログアーカイブアカウントに保存できることがわかります。
考慮点
今回作成したサービスはメンバーアカウント側から変更が可能です。
しっかりガバナンスを効かせたい場合はメンバーアカウント側でSCPやIAMなどで今回作成したサービス(CloudFormation/S3/IAM Role/CloudTrail)へのアクセスを制限することをおすすめします。
最後に
今回はCloudTrailログをメンバーアカウントに残したままログアーカイブアカウントにログをレプリケーションする方法をご紹介しました。
メンバーアカウントにCloudTrailログを残したまま、安価にログアーカイブアカウントにログを保存したいという方にこちらのブログが参考になると幸いです。
以上、たかやま(@nyan_kotaroo)でした。