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

2023.04.25

こんにちは。たかやまです。

今回は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 : ログアーカイブアカウントのアカウントID
    • BucketName : ログアーカイブアカウントの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権限は不要なため、明示的に抜いています。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/setting-repl-config-perm-overview.html#setting-repl-config-same-acctowner

  • 信頼関係
{
    "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には*を指定し、ConditionArnLikeを指定することで、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)でした。