CI部1課の山﨑です。
前回はAmazon Athenaを利用してS3に保管されているAWS Config のログを調査する方法を調べましたが、今回はAWS CloudTrail のログを調査する方法を調べてみました。
おさらい
AWS CloudTrail
AWS CloudTrail とは
AWS CloudTrail は、AWS アカウントのガバナンス、コンプライアンス、運用監査、リスク監査を行うためのサービスです。CloudTrail を使用すると、AWS インフラストラクチャ全体でアカウントアクティビティをログに記録し、継続的に監視し、保持できます。CloudTrail では、AWS マネジメントコンソール、AWS の SDK やコマンドラインツール、その他の AWS のサービスを使用して実行されるアクションなど、AWS アカウントアクティビティのイベント履歴を把握できます。このイベント履歴により、セキュリティ分析、リソース変更の追跡、トラブルシューティングをより簡単に実行できるようになります。 さらに、CloudTrail を使用して、AWS アカウントの異常なアクティビティを検出することができます。こうした機能は、運用分析とトラブルシューティングの簡素化に役立ちます。
※引用元:https://aws.amazon.com/jp/cloudtrail/
AWS CloudTrail は一般的にAWS上のアクティビティログを記録・管理することができるサービスとして認知されています。具体的には「いつ、誰が、どのリソースに、どのようなAPIを実行したのか」といったことを記録・管理することができます。
その他CloudTrail については以下のブログをご覧ください。
Amazon Athena
Amazon Athena とは
Amazon Athena は、標準的な SQL を使用して Amazon Simple Storage Service (Amazon S3) 内のデータを直接分析することを容易にするインタラクティブなクエリサービスです。AWS Management Console でいくつかのアクションを実行するだけで、Athena にデータの保存先の Amazon S3 を設定し、標準 SQL を使用してアドホッククエリの実行を開始できます。結果は数秒で返されます。
Athena はサーバーレスであるため、インフラストラクチャの設定や管理は不要です。また、実行したクエリにのみ課金されます。自動的にスケールしてクエリを並列実行するため、大規模なデータベースや複雑なクエリでも結果がすぐに返されます。 ※引用元:https://docs.aws.amazon.com/ja_jp/athena/latest/ug/what-is.html
Amazon Athenaは Apache Hiveのデータ定義言語(DDL)を使ってテーブルを作成し、Hadoopの分散型SQLエンジンでアドホックな分析を行うのに適しているPrestoを使ってクエリを実行しています。S3バケットはオブジェクトストレージであり、非構造化データあるいはスキーマレスデータが保管されています。Amazon Athena でクエリを実行する際はCREATE TABLEを使ってS3内のデータを構造化データに整形した上で、SQLを使ってクエリをかけていきます。
執筆時期が古いですが、Athenaについては以下のブログもご覧ください。
S3のログを調査してみる
Athena でクエリを実行するテーブルを作成
テーブルを作成する前にS3に保管されているログの構造をある程度把握しておきます。「ある程度」と言っているのは実行されるAPIによってログの構造が異なるためです。
以下は IAM ユーザー(Alice) がAWS Management Console から AddUserToGroupアクションを実行して、管理者グループに IAMユーザー(Bob) を追加した時の例です。
※引用元:CloudTrail ログファイルの例 - AWS CloudTrail
CloudTrail ログのサンプル
{"Records": [{ "eventVersion": "1.0", "userIdentity": { "type": "IAMUser", "principalId": "EX_PRINCIPAL_ID", "arn": "arn:aws:iam::123456789012:user/Alice", "accountId": "123456789012", "accessKeyId": "EXAMPLE_KEY_ID", "userName": "Alice", "sessionContext": {"attributes": { "mfaAuthenticated": "false", "creationDate": "2014-03-25T18:45:11Z" }} }, "eventTime": "2014-03-25T21:08:14Z", "eventSource": "iam.amazonaws.com", "eventName": "AddUserToGroup", "awsRegion": "us-east-2", "sourceIPAddress": "127.0.0.1", "userAgent": "AWSConsole", "requestParameters": { "userName": "Bob", "groupName": "admin" }, "responseElements": null }]}
今回は以下の参考ドキュメントを参考にパーティション射影(Partition Projection)を使用したテーブルを作成します。
CREATE EXTERNAL TABLE cloudtrail_logs_pp( eventVersion STRING, userIdentity STRUCT< type: STRING, principalId: STRING, arn: STRING, accountId: STRING, invokedBy: STRING, accessKeyId: STRING, userName: STRING, sessionContext: STRUCT< attributes: STRUCT< mfaAuthenticated: STRING, creationDate: STRING>, sessionIssuer: STRUCT< type: STRING, principalId: STRING, arn: STRING, accountId: STRING, userName: STRING>>>, eventTime STRING, eventSource STRING, eventName STRING, awsRegion STRING, sourceIpAddress STRING, userAgent STRING, errorCode STRING, errorMessage STRING, requestParameters STRING, responseElements STRING, additionalEventData STRING, requestId STRING, eventId STRING, readOnly STRING, resources ARRAY<STRUCT< arn: STRING, accountId: STRING, type: STRING>>, eventType STRING, apiVersion STRING, recipientAccountId STRING, serviceEventDetails STRING, sharedEventID STRING, vpcEndpointId STRING ) PARTITIONED BY ( `timestamp` string) ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde' STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat' LOCATION 's3://バケット名/AWSLogs/アカウントID/CloudTrail/ap-northeast-1/' TBLPROPERTIES ( 'projection.enabled'='true', 'projection.timestamp.format'='yyyy/MM/dd', 'projection.timestamp.interval'='1', 'projection.timestamp.interval.unit'='DAYS', 'projection.timestamp.range'='2021/01/01,NOW', 'projection.timestamp.type'='date', 'storage.location.template'='s3://バケット名/AWSLogs/アカウントID/CloudTrail/ap-northeast-1/${timestamp}')
Athena でクエリを記述する際に指定する ROW FORMAT SERDE の「SERDE」はSerialize と Deserialize の頭文字を取っているものでデータソースからAthenaテーブルへのデータの読み書きに使用するSerialize と Deserialize の対応を明示的に指定するものです。
Amazon Athenaは Apache Hiveのデータ定義言語(DDL)を使ってテーブルを作成しますが、Apache Hive は JSON 形式のファイルをネイティブにサポートしていません。そのためSerDe(Serialize と Deserialize) を使用して Hive がJSONで記述されたレコードをどのように処理すべきかを理解できるようにする必要があります。Deserialize はデータを取得してJavaオブジェクトに変換し、Serialize はJavaオブジェクトを使用可能な表現に変換します。
docs.aws.amazon.comテーブルの作成に関するスキーマ情報などのメタデータはAWS Glue Data Catalog のテーブルに保管されます。
Amazon Athena のパーティション射影(Partition Projection)
上述の通り、Athena ではテーブルの作成に関するスキーマ情報などのメタデータはAWS Glue Data Catalog のテーブルに保管されます。これはテーブルをパーティション分割した際も同様です。テーブルをパーティション分割する場合、AthenaはGlue Data Catalog に対して GetPartitions API を呼び出してパーティションに関する情報を読み込みます。パーティション数が少なければ大きな問題にはなりませんが、非常に多くのパーティションがあるテーブルに対してクエリを実行する場合はGetPartitions API の呼び出しがクエリパフォーマンスに悪影響を与える可能性があります。
Amazon Athena のパーティション射影(Partition Projection) はパーティション情報をAWS Glue Data Catalog のテーブルに保管せずにAthena 自体が設定情報を保持して実行処理することにより、クエリパフォーマンスの向上を図っています。
AWSドキュメントでは下記のユースケースが紹介されています。
- 高度にパーティション化されたテーブルに対するクエリが、思ったほどすぐに完了しない。
- データに新しい日付または時刻パーティションが作成されたとき、定期的にパーティションをテーブルに追加する。パーティション射影で、新しいデータが到着したときに使用できる相対日付範囲を設定している。
- Amazon S3 に高度にパーティション分割されたデータがある。データは、AWS Glue Data Catalog または Hive メタストア内のモデルに対して実用的ではなく、クエリがそのごく一部のみを読み取る。
以下の箇所がパーティション射影(Partition Projection) を設定する記述に該当します。TBLPROPERTIES で設定されている値については以下のドキュメントをご覧ください
パーティション射影のサポートされている型 - Amazon Athena
PARTITIONED BY ( `timestamp` string) ---- TBLPROPERTIES ( 'projection.enabled'='true', 'projection.timestamp.format'='yyyy/MM/dd', 'projection.timestamp.interval'='1', 'projection.timestamp.interval.unit'='DAYS', 'projection.timestamp.range'='2021/01/01,NOW', 'projection.timestamp.type'='date', 'storage.location.template'='s3://バケット名/AWSLogs/アカウントID/CloudTrail/ap-northeast-1/${timestamp}')
今回は以下の理由でパーティション射影(Partition Projection) を用いたパーティション分割を行いました。
- CloudTrail のログは前回検証したConfig のログと比較してデータ量が多く、Athena のクエリ処理時間が長くなったのでこれを短縮したかった
- CloudTrail のログは毎日必ず取得されるもので、時間の経過とともに ALTER TABLE ADD PARTITION でパーティションを手動で追加する手間を省きたかった
Athena でクエリを実行する
1. Root ユーザーによるアクティビティを調査
select count (*) as TotalEvents, eventname, useridentity.invokedby from cloudtrail_logs_app where 'timestamp' >= '2021-01-01T00:00:00Z' and useridentity.type = 'Root' group by useridentity.username, eventname, useridentity.invokedby order by TotalEvents desc;
上記のクエリを実行することで、ある任意の期間内にRoot ユーザーによるアクティビティの有無を調べることができます。
前節でパーティション射影(Partition Projection) を用いたパーティション分割の話をしたので、実際にパーティション分割をした場合としなかった場合でどの程度クエリパフォーマンスに影響があったのかも見てみましたがその差は一目瞭然でした。CloudTrail のログに対してAthena を実行する場合はパーティション分割をした方が良さそうです。
パーティション分割をした場合(パーティションキーのtimestampで条件指定)
- クエリ処理時間:9.4秒
- スキャンしたデータ量:837MB
パーティション分割をしなかった場合(evetTimeで条件指定)
- クエリ処理時間:56.9秒
- スキャンしたデータ量:1.19GB
2. AWSイベントエラーのトップ10を調べる
select count (*) as TotalEvents, eventname, errorcode, errormessage from cloudtrail_logs_pp where errorcode is not null and 'timestamp' >= '2021-01-01T00:00:00Z' group by eventname, errorcode, errormessage order by TotalEvents desc limit 10;
上記のクエリを実行することで、ある任意の期間内に発生したAWSイベントエラーの数を調べることができます。繰り返し発生するエラーメッセージは、ポリシーが正しく構成されていない、アプリケーションに適用されているアクセス許可が間違っている、またはワークロードに不明な変更があったことを示している可能性があります。
以下のクエリ結果からはS3バケットの設定やポリシーが正しく構成されていない可能性があると考えられます。
その他サンプルクエリ
その他のサンプルクエリは以下のAWS blog にも記載されておりますのでご覧ください。
まとめ
ということで、今回はAmazon Athena を利用してS3に保管されているAWS CloudTrail のログを調査する方法を調べてみました。AWS Config の時もそうでしたが、Amazon Athena を使ってクエリをかける際は「どのような情報を、どういった目的で取得したいのか」を設定することがとても重要だなと検証をしながら改めて感じました。本ブログがどなたかのお役に立てば幸いです。
また、VPC Flow Logs のログを調査する方法についても別ブログでまとめていますので是非ご覧ください。
山﨑 翔平 (Shohei Yamasaki) 記事一覧はコチラ
カスタマーサクセス部所属。2019年12月にインフラ未経験で入社し、AWSエンジニアとしてのキャリアを始める。2023 Japan AWS Ambassadors/2023-2024 Japan AWS Top Engineers