CloudTrail ログと Athena を使ってリソース ARN をキーにアクセス違反を検出しようとしたけど簡単ではなかった

2024.01.09

いわさです。

アカウント内で複数サービス間のプリンシパルとリソースの間でアクセス違反が起きていないかを、タグと CloudTrail でうまくチェック出来るのではないかということで最近調査していました。

先日は上記記事でリソースごとのタグとユーザーごとのタグをリストアップしてました。
本日は CloudTrail のログと組み合わせて実際にチェックが出来るのかを確認してみたのですが、結論からいうと想定どおりいきませんでした。

CloudTrail のログは複雑だったという話をします。

Athena で準備を整える

まず、冒頭の記事で生成した JSON ログをちょっと加工して Athena でクエリ出来るようにしました。
この加工については本題から外れるのでここでは触れませんがとんでもなく苦労しました。

次のように、product タグが設定されているリソースとユーザーをクエリ出来るようになりました。

リソースはここではたまたま EC2 を使いました。
ユーザーhoge0108はプロダクトaaaの担当者という想定です。

プロダクトaaabbbの異なるプロダクトの EC2 がこのアカウントには混在しているという設定です。

それに対して CloudTrail のログは公式ドキュメントにテーブル作成方法から全て記載されているので苦労なくクエリするところまでいけました。

あとは CloudTrail に出力されているユーザー名とリソース ARN をそれぞれのテーブルと結合することで、どのプロダクトの担当者がいつどのプロダクトのどのリソースにアクセスしたのかがわかるというわけです。
その結果、プロダクト情報の不一致が起きれば違反として取り扱えば良さそうだと考えていました。

テストしてみたが...

さて、ここでテスト用のユーザーで EC2 を起動してみます。

ちょっと雑なのですが次のようなクエリでおそらく、ユーザー hoge0108 が EC2 にアクセスした履歴が取得出来るだろうということで実行してみました。

SELECT * 
FROM
    (
        SELECT 
            eventtime,
            useridentity.username as username,
            element_at(resources, 1).arn as resourcearn,
            element_at(resources, 1).type as resourcetype,
            eventname
        FROM cloudtrail_logs_pp 
        WHERE timestamp >= '2024/01/09'
    ) ct
INNER JOIN product_users pu
    ON ct.username = pu.username
INNER JOIN product_resources ru
    ON ct.resourcearn = ru.resourcearn

しかし、結果が取得出来ない...!

ログが取れない

StartInstancesアクションのログが出力されていなかったのか?と思ってイベントを指定してみたところ、CloudTrail ログにresourcesプロパティが出力されていないことに、この時やっと気が付きました。

その後、S3 バケットの作成、S3 オブジェクトのアクセス、DynamoDB テーブルへのスキャン、SQS キューへのメッセージ送信などなど、データイベントを含めて色々と操作を行い、それらのイベントのログも確認してみたところ次のようになりました。

データイベント系は概ねresourcesプロパティが出力されているのですが、それ以外のものが出力されていない気がします。

CloudTrail レコードのフィールドはオプションで出力されるものが多い

次の公式ドキュメントに CloudTrail レコードの内容について記載されています。
各フィールドの仕様が説明されているのですが、「オプション:True」のものはそのフィールドが常に存在するとは限らないフィールドなのです。

確認したところ、やはりresourcesフィールドもオプションフィールドでした。

resources

イベントでアクセスされたリソースのリスト。このフィールドには以下の情報が含まれます。

  • リソース ARN
  • リソース所有者のアカウント ID
  • 以下の形式でのリソースタイプ識別子 : AWS::aws-service-name::data-type-name

:

  • 使用可能: 1.01 以降
  • オプション: True

なんてこったい。

requestsparameters はフォーマットがバラバラ

フィールドのひとつであるrequestsparametersについてはオプションではないフィールドで、リクエスト情報に対象リソースの情報が含まれている確率が非常に高いです。

これは使えるかもと思って確認してみましたが、次のようにフォーマットがバラバラでした。

これは機械的な処理でリソース ARN と紐付けるのはちょっと難しそうだなと判断しました。

CloudTrail のイベント履歴機能は使えるか?

「これは困ったな。他に何か方法はないのだろうか。特に、データイベント以外についてはフォーマットがバラバラのrequestsparametersから取得しなければいけなさそうでだいぶ厳しい。」と思いました。

実際に EC2 インスタンス名と S3 バケット名を取得するだけでもそれぞれ独自のフォーマットにあわせて取得先を設定する必要があります。さすがに API の数だけ個別対応するわけにはいきません。

そんな中、CloudTrail のイベント履歴には「リソース名」という共通の列にリソース名が表示されていることに気がつきました。

イベント履歴画面は、証跡でデータイベントを有効化していても、データイベントは表示されないのですが、データイベントについてはresourcesから取得出来る可能性が高いので、ここではデータイベントについては無視します。

このデータイベント、API から使うことも可能です。

% aws cloudtrail lookup-events --lookup-attributes AttributeKey=ReadOnly,AttributeValue=false --max-items 100 | jq -r '.Events[] | select(.Username == "hoge0108") | [.EventName, .Username, .Resources[0].ResourceType, .Resources[0].ResourceName]'
[
  "UpdateTrail",
  "hoge0108",
  "AWS::CloudTrail::Trail",
  "arn:aws:cloudtrail:ap-northeast-1:123456789012:trail/hogeTrail"
]
[
  "PutInsightSelectors",
  "hoge0108",
  "AWS::CloudTrail::Trail",
  "arn:aws:cloudtrail:ap-northeast-1:123456789012:trail/hogeTrail"
]
[
  "PutEventSelectors",
  "hoge0108",
  "AWS::CloudTrail::Trail",
  "arn:aws:cloudtrail:ap-northeast-1:123456789012:trail/hogeTrail"
]
[
  "PutBucketEncryption",
  "hoge0108",
  "AWS::S3::Bucket",
  "hoge0109tmp"
]
[
  "CreateBucket",
  "hoge0108",
  "AWS::S3::Bucket",
  "hoge0109tmp"
]
[
  "StartInstances",
  "hoge0108",
  "AWS::EC2::Instance",
  "i-05ae8f7dcc61815ac"
]

AttributeKeyをひとつしか指定出来ないとか、演算子は使えないとか、柔軟性が少し低いのですが、API 間の差異を少し吸収してくれている気がします。ARN まで出力してくれていたらかなり良いのですが。

さいごに

本日は CloudTrail のログから Athena でリソース ARN を取得しようとしたのですが、うまくいかなかったよというのを紹介することになってしまいました。

ただ、色々と学びがあったなとは思っていて、CloudTrail のログは思ってたより複雑なのでひとつふたつのログをサンプルに実装方針を決めるのは危ないなと思いました。

今回はアクセス違反を検知したかったのですが、もしかすると CloudTrail のログではなく Config などを使ったアプローチのほうが適しているかもしれません。そちらの方向でも考えてみたいと思います。