AWS CloudTrail ログを1つのAWSアカウントに集約する

こんにちは、インフラストラクチャー部の沼沢です。

複数の AWS アカウントを運用していると、それぞれのアカウントの S3 バケットに CloudTrail のログが溜まっていきますが、そのログ、1箇所に集約して監視や可視化をしたくはありませんか?

そこで今回は、複数の AWS アカウント上にそれぞれ保存されている CloudTrail ログを集約・可視化する仕組みについてです。

構成図

① CloudTrail が S3 にログを Put したのをトリガーに、集約先に用意している Lambda を起動
② Lambda から S3 へログを取りに行く(Roleで権限委譲して取得)
③ Lambda でログファイルを展開し、内容を無加工で CloudWatch Logs に投入
④ CloudWatch Logs からストリーミングで Elasticsearch Service にログを流す

では、これを構築する手順を解説していきます。

構築手順

  • 000000000000 は A環境(集約元)の AWS アカウント ID で読み替えましょう
  • 999999999999 は B環境(集約先)の AWS アカウント ID で読み替えましょう
  • 今回は東京リージョン(ap-northeast-1)に構築することを前提としています
  • ② に関しては某ブログの会社の「S3保管したCloudTrailログに別アカウントのLambdaからアクセスする」を大いに参考にしています

B環境に Elasticsearch Service のドメイン作成

  • AccessPolicy ↓
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-1:999999999999:domain/<設定したドメイン名>/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "xxx.xxx.xxx.xxx/32"
        }
      }
    }
  ]
}
  • Condition -> IpAddress で、Kibana へのアクセスを xxx.xxx.xxx.xxx のみに制限
  • AccessPolicy 以外は全て任意に設定

B環境に CloudWatch Logs のロググループ作成

  • ロググループ名: 任意のロググループ名
  • ログストリーム名: 任意のログストリーム名
  • 作成したロググループにチェックを入れ、以下の通り設定
    • アクションから「Amazon Elasticsearch Service へのストリーミングの開始」を選択
    • Amazon ES クラスター: ↑で作成した Elasticsearch Service のドメインを指定
    • ログの形式: AWS CloudTrail

A環境で権限委譲用の IAM ロール/カスタムポリシーを作成

  • 以下の条件でカスタムポリシーを作成
    • ポリシー名: 任意
    • ポリシーJSON ↓
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": "arn:aws:s3:::<CloudTrailのログ出力先のバケット名>/*"
        }
    ]
}
  • 以下の条件でロールを作成
    • ロール名: 任意
    • ロールタイプ: クロスアカウントアクセスのロール -> 所有している AWS アカウント間のアクセスを提供します
    • アカウントID: 999999999999
    • ポリシー
      • ↑で作成したカスタムポリシー

B環境の Lambda 用の IAM ロール/カスタムポリシーを作成

  • 以下の条件でカスタムポリシーを作成
    • ポリシー名: 任意
    • ポリシーJSON ↓
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "sts:AssumeRole"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}
  • 以下の条件でロールを作成
    • ロール名: 任意
    • ロールタイプ: AWS サービスロール -> AWS Lambda
    • ポリシー
      • ↑で作成したカスタムポリシー
      • AmazonS3FullAccess ※バケットを絞るなどは適宜行ってください
      • AWSLambdaExecute

B環境に Lambda ファンクション作成

  • ファンクション名: 任意
  • Runtime: Python2.7
  • Handler: lambda_function.handler
  • Role: B環境に作成した Lambda 用の IAM ロール
  • Timeout: 10 sec
  • ソースコードはこちら

作成されたファンクションの ARN のメモを取っておきましょう

A環境の S3 バケットから B環境の Lambda を実行するための権限を Lambda に付与する

2016年09月現在、ManagementConsole からではからこの設定ができないため、CLI で権限を付与

$ aws lambda add-permission \
    --region ap-northeast-1 \
    --function-name <対象のLambdaFunction名> \
    --statement-id <任意で一意となるID> \
    --principal s3.amazonaws.com \
    --action lambda:InvokeFunction \
    --source-arn arn:aws:s3:::<CloudTrailのログ出力先のバケット名> \
    --source-account 000000000000

A環境の CloudTrail 用 S3 バケットにイベント登録

  • 対象のバケットを選択し、プロパティを表示
  • イベント -> 「通知の追加」 -> 以下を入力/選択して「保存」
    • 名前: 任意
    • イベント: Put
    • 送信先: Lambda 関数
    • Lambda 関数の ARN を追加
    • Lambda 関数の ARN: 作成したB環境の Lambda ファンクションの ARN

CloudTrail のログが複数のバケットにある場合は必要な分だけ上記を登録

まとめ

上記の設定が完了すると、S3 に CloudTrail のログが出力される度に Lambda が起動し、CloudWatch Logs を経由して Elasticsearch にログが放り込まれることになります。
あとは、Elasticsearch の Kibana の URL にアクセスし、好きに閲覧してみてください。

なお、途中に CloudWatch Logs を経由していますので、ここでログ監視を入れることも可能です。

また、今回は簡単に可視化を実現するために Elasticsearch Service を利用しましたが、Elasticsearch Service では、構築時にディスクサイズを決める必要があるため、集約したい環境が増えていくと、ディスクが足りなくなる可能性があります。
可視化部分を自分で実装する手間をかけられるのであれば、Lambda から DynamoDB にログを放り込んで、可視化部分を自力で実装するようにすれば、ディスクサイズを気にしなくて済みますね。

この構成をもとに、いろいろアレンジしてみてください。