非Organizations環境でCloudTrail証跡をS3レプリケーションで集約管理してみた

非Organizations環境でCloudTrail証跡をS3レプリケーションで集約管理してみた

2025.09.10

こんにちは!クラウド事業本部のおつまみです。

今回は非Organizations環境にて、CloudTrailを1つの証跡でログアーカイブアカウントにまとめつつ、メンバーアカウントにログを残す方法をご紹介します。

アーキテクチャ

実装するアーキテクチャはこちらです。

cloudtrail-aggregation-without-organizations

本手順の前提条件

構築手順

1. 集約アカウント側の設定

まずはログアーカイブアカウント側でレプリケーションが可能なS3バケットを用意します。
<アカウントID>は集約アカウントのAWSアカウントIDに変換してください。

S3バケットの作成

			
			aws s3api create-bucket \
    --bucket aggregation-cloudtrail-logs-<アカウントID> \
    --region ap-northeast-1 \
    --create-bucket-configuration LocationConstraint=ap-northeast-1 \
    --object-lock-enabled-for-bucket

		

バケットのバージョニング有効化

			
			aws s3api put-bucket-versioning \
    --bucket aggregation-cloudtrail-logs-<アカウントID> \
    --versioning-configuration Status=Enabled

		

2. ソースアカウント側の設定

弊社請求代行サービスの「メンバーズ」をご利用のお客様はアカウント払出し時にCloudTrailが設定された状態で払い出されます。
以下は既存のCloudTrail証跡「Members」とS3バケット「cm-members-cloudtrail-<アカウントID>」を使用する場合の手順となります。

既存S3バケットの確認

以下コマンドを実行し、バケットが存在しますと表示されればOKです。

			
			# アカウントIDを取得
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# バケット名の確認
BUCKET_NAME="cm-members-cloudtrail-${ACCOUNT_ID}"
echo "対象バケット: ${BUCKET_NAME}"

# バケットの存在確認
aws s3api head-bucket --bucket ${BUCKET_NAME} --output text > /dev/null 2>&1 && echo "バケットが存在します" || echo "バケットが存在しません"

		

バージョニングの有効化(未設定の場合)

			
			# 現在のバージョニング状態を確認
aws s3api get-bucket-versioning --bucket ${BUCKET_NAME}

		

上記コマンドを実行し、"Status": "Disbled"が表示された場合に以下のコマンドを実行し、バージョニングを有効化します。

			
			# バージョニングが無効な場合は有効化
aws s3api put-bucket-versioning \
    --bucket ${BUCKET_NAME} \
    --versioning-configuration Status=Enabled

		

レプリケーション用IAMロールの作成

以下のyamlファイルを準備します。

s3-replication-role.yaml
			
			AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudTrail S3 Replication IAM Role for cross-account log aggregation'

Parameters:
  DestinationAccountId:
    Type: String
    Description: 'Destination account ID for log aggregation'
    AllowedPattern: '[0-9]{12}'
    ConstraintDescription: 'Must be a valid 12-digit AWS account ID'

Resources:
  S3ReplicationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: S3ReplicationRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: S3ReplicationPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetReplicationConfiguration
                  - s3:ListBucket
                Resource: !Sub 'arn:aws:s3:::cm-members-cloudtrail-${AWS::AccountId}'
              - Effect: Allow
                Action:
                  - s3:GetObjectVersionForReplication
                  - s3:GetObjectVersionAcl
                  - s3:GetObjectVersionTagging
                Resource: !Sub 'arn:aws:s3:::cm-members-cloudtrail-${AWS::AccountId}/*'
              - Effect: Allow
                Action:
                  - s3:ReplicateObject
                  - s3:ReplicateTags
                  - s3:ReplicateDelete
                  - s3:ObjectOwnerOverrideToBucketOwner
                Resource: !Sub 'arn:aws:s3:::aggregation-cloudtrail-logs-${DestinationAccountId}/*'

Outputs:
  ReplicationRoleArn:
    Description: 'ARN of the S3 Replication Role'
    Value: !GetAtt S3ReplicationRole.Arn
    Export:
      Name: !Sub '${AWS::StackName}-ReplicationRoleArn'

  ReplicationRoleName:
    Description: 'Name of the S3 Replication Role'
    Value: !Ref S3ReplicationRole
    Export:
      Name: !Sub '${AWS::StackName}-ReplicationRoleName'

		

以下の<集約先アカウントID>を置換し、コマンドを実行します。

			
			# 集約先アカウントIDを指定してスタックを作成
aws cloudformation create-stack \
    --stack-name cloudtrail-replication-role \
    --template-body file://s3-replication-role.yaml \
    --parameters ParameterKey=DestinationAccountId,ParameterValue=<集約先アカウントID> \
    --capabilities CAPABILITY_NAMED_IAM

# スタック作成の完了を待つ
aws cloudformation wait stack-create-complete \
    --stack-name cloudtrail-replication-role

		

既存バケットにレプリケーション設定を追加

以下のファイルを準備します。${DESTINATION_ACCOUNT_ID}は集約先アカウントのIDに置き換えてください。

replication-config.json
			
			{
    "Role": "arn:aws:iam::${ACCOUNT_ID}:role/S3ReplicationRole",
    "Rules": [
        {
            "ID": "ReplicateCloudTrailLogs",
            "Priority": 1,
            "Status": "Enabled",
            "Filter": {
                "Prefix": "AWSLogs/"
            },
            "Destination": {
                "Bucket": "arn:aws:s3:::aggregation-cloudtrail-logs-${DESTINATION_ACCOUNT_ID}",
                "Account": "${DESTINATION_ACCOUNT_ID}",
                "StorageClass": "STANDARD_IA",
                "AccessControlTranslation": {
                    "Owner": "Destination"
                },
                "ReplicationTime": {
                    "Status": "Enabled",
                    "Time": {
                        "Minutes": 15
                    }
                },
                "Metrics": {
                    "Status": "Enabled",
                    "EventThreshold": {
                        "Minutes": 15
                    }
                }
            },
            "DeleteMarkerReplication": {
                "Status": "Disabled"
            }
        }
    ]
}

		

以下の<集約先アカウントID>を置換し、コマンドを実行します。

			
			# 現在のアカウントIDと集約先アカウントIDを設定
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
DESTINATION_ACCOUNT_ID=<集約先アカウントID>  # ここに実際の集約先アカウントIDを入力

		

以下コマンドを実行し、Enabledが表示されることを確認します。

			
			# アカウントIDを置換してレプリケーション設定を適用
sed -e "s/\${ACCOUNT_ID}/${ACCOUNT_ID}/g" \
    -e "s/\${DESTINATION_ACCOUNT_ID}/${DESTINATION_ACCOUNT_ID}/g" \
    replication-config.json > replication-config-filled.json

# レプリケーション設定を適用
aws s3api put-bucket-replication \
    --bucket cm-members-cloudtrail-${ACCOUNT_ID} \
    --replication-configuration file://replication-config-filled.json

# 設定が適用されたか確認
echo "レプリケーション設定の確認:"
aws s3api get-bucket-replication --bucket cm-members-cloudtrail-${ACCOUNT_ID} --query 'ReplicationConfiguration.Rules[0].Status' --output text

		

3. 集約アカウント側でバケットポリシーの設定

ソースアカウント側の設定が完了したら、集約アカウント側でバケットポリシーを設定します。
これにより、ソースアカウントからのレプリケーションが許可されます。

以下のファイルを準備します。

bucket-policy.json
			
			{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowReplicationFromSourceAccount",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<ソースアカウントID>:role/S3ReplicationRole"
            },
            "Action": [
                "s3:ReplicateObject",
                "s3:ReplicateDelete",
                "s3:ReplicateTags",
                "s3:ObjectOwnerOverrideToBucketOwner"
            ],
            "Resource": "arn:aws:s3:::aggregation-cloudtrail-logs-<集約アカウントID>/*"
        },
        {
            "Sid": "AllowReplicationPolicyCheck",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<ソースアカウントID>:role/S3ReplicationRole"
            },
            "Action": [
                "s3:GetBucketVersioning",
                "s3:PutBucketVersioning",
                "s3:List*"
            ],
            "Resource": "arn:aws:s3:::aggregation-cloudtrail-logs-<集約アカウントID>"
        }
    ]
}

		

以下の<ソースアカウントID>を置換し、コマンドを実行します。

			
			# アカウントIDを設定
DESTINATION_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)  # 集約先アカウントID
SOURCE_ACCOUNT_ID=<ソースアカウントID>    # ここに実際のソースアカウントIDを入力

		

以下コマンドを実行し、設定されたバケットポリシーが表示されることを確認します。

			
			# バケットポリシーファイルの作成(sedで置換)
sed -e "s/<集約アカウントID>/${DESTINATION_ACCOUNT_ID}/g" \
    -e "s/<ソースアカウントID>/${SOURCE_ACCOUNT_ID}/g" \
    bucket-policy.json > bucket-policy-filled.json

# バケットポリシーを適用
aws s3api put-bucket-policy \
    --bucket aggregation-cloudtrail-logs-${DESTINATION_ACCOUNT_ID} \
    --policy file://bucket-policy-filled.json

# ポリシーが適用されたか確認
echo "適用されたバケットポリシー:"
aws s3api get-bucket-policy \
    --bucket aggregation-cloudtrail-logs-${DESTINATION_ACCOUNT_ID} \
    --query Policy --output text | jq .

		

以上で設定は完了です。

ソースアカウントを追加する場合

既存の構成に新たなソースアカウントを追加する場合は、以下の手順で実施します。

1. 新規ソースアカウント側の設定

新規ソースアカウントで、上記「2. ソースアカウント側の設定」の手順を実施します。

主な作業:

  • CloudTrailバケットのバージョニング有効化
  • レプリケーション用IAMロールの作成
  • レプリケーション設定の追加

2. 集約アカウント側のバケットポリシー更新

既存のバケットポリシーに新規ソースアカウントからのレプリケーションを許可する設定を追加します。

新規ソースアカウント用のPrincipal追加

既存のStatementのPrincipalに新しいソースアカウントを追加します。

			
			{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowReplicationFromSourceAccounts",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::<既存ソースアカウントID>:role/S3ReplicationRole",
                    "arn:aws:iam::<新規ソースアカウントID>:role/S3ReplicationRole"
                ]
            },
            "Action": [
                "s3:ReplicateObject",
                "s3:ReplicateDelete",
                "s3:ReplicateTags",
                "s3:ObjectOwnerOverrideToBucketOwner"
            ],
            "Resource": "arn:aws:s3:::aggregation-cloudtrail-logs-<集約アカウントID>/*"
        },
        {
            "Sid": "AllowReplicationPolicyCheck",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:iam::<既存ソースアカウントID>:role/S3ReplicationRole",
                    "arn:aws:iam::<新規ソースアカウントID>:role/S3ReplicationRole"
                ]
            },
            "Action": [
                "s3:GetBucketVersioning",
                "s3:PutBucketVersioning",
                "s3:List*"
            ],
            "Resource": "arn:aws:s3:::aggregation-cloudtrail-logs-<集約アカウントID>"
        }
    ]
}

		

動作確認

設定完了後、以下の点を確認しました。

  1. 新規ソースアカウント側
    • CloudTrailログが生成されていることを確認
    • レプリケーションステータスがCOMPLETEDになっていることを確認

CleanShot 2025-09-10 at 16.17.52@2x

  1. 集約アカウント側
    • 新規ソースアカウントのログが集約バケットに表示されることを確認
    • レプリケーションステータスがREPLICAになっていることを確認

CleanShot 2025-09-10 at 15.58.22@2x

まとめ

今回は、Organizations環境を使わずにS3レプリケーションでCloudTrailログを集約する方法についてご紹介しました。

この方法を使えば、非Organizations環境でも複数AWSアカウントのログを効率的かつコスト効果的に管理でき、セキュリティ監査やコンプライアンス対応が大幅に改善されます。

最後までお読みいただきありがとうございました!

以上、おつまみ(@AWS11077)でした!

参考

この記事をシェアする

FacebookHatena blogX

関連記事