AthenaでCloudTrailログを楽々検索！

# はじめに

AWSアカウント内のリソースの操作について過去ログを辿りたい時、皆さんCloudTrailを使っていると思います。

しかしCloudTrailのコンソール画面からだと検索できるのは過去3か月であり、一部の操作ログはそこから検索できなかったりもします。

そんな時はAthenaを使用して、CloudTrailのログを検索しましょう。

CloudTrailのログの実体はS3に保存されているので、Athenaでテーブルを作成することでS3に保存されたログを検索することができます。

# テーブル作成

以下のDDLを、虫食い部分を埋めてAthenaで実行すればOKです。

```sql
CREATE EXTERNAL TABLE IF NOT EXISTS `default`.`cloudtrail_logs` (
  `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
      >,
      ec2RoleDelivery: string,
      webIdFederationData: map <
        string,
        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,
  `resources` array <
    STRUCT <
      arn: STRING,
      accountid: STRING,
      type: STRING
    >
  >,
  `eventtype` string,
  `apiversion` string,
  `readonly` string,
  `recipientaccountid` string,
  `serviceeventdetails` string,
  `sharedeventid` string,
  `vpcendpointid` string,
  `tlsDetails` struct <
    tlsVersion: string,
    cipherSuite: string,
    clientProvidedHostHeader: string
  >
)
PARTITIONED BY (region string, date string)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://{対象のバケット名}/AWSLogs/{AWSアカウントID}/'
TBLPROPERTIES (
    'projection.enabled' = 'true',
    'projection.date.type' = 'date',
    'projection.date.range' = '{任意の開始日(yyyy/MM/dd形式で)},NOW',
    'projection.date.format' = 'yyyy/MM/dd',
    'projection.date.interval' = '1',
    'projection.date.interval.unit' = 'DAYS',
    'projection.region.type' = 'enum',
    'projection.region.values'='us-east-1,us-east-2,us-west-1,us-west-2,af-south-1,ap-east-1,ap-south-1,ap-northeast-2,ap-southeast-1,ap-southeast-2,ap-northeast-1,ca-central-1,eu-central-1,eu-west-1,eu-west-2,eu-south-1,eu-west-3,eu-north-1,me-south-1,sa-east-1',
    'storage.location.template' = 's3://{対象のバケット名}/AWSLogs/{AWSアカウントID}/CloudTrail/${region}/${date}',
    'classification'='cloudtrail',
    'compressionType'='gzip',
    'typeOfData'='file',
    'classification'='cloudtrail'
);
```

虫食い箇所

- `{対象のバケット名}`: CloudTrailログが保存されているS3バケット名
- `{AWSアカウント番号}`: 作業しているAWSのアカウントID
- `{任意の開始日(yyyy/MM/dd形式で)}`: CloudTrailログの保存開始日

実際に埋めるとこんな感じになります。

- `s3://my-cloudtrail-logs-bucket/AWSLogs/123456789012/`
- `'projection.date.range' = '2019/01/01,NOW'`

## パーティショニングについて詳細解説

```sql
PARTITIONED BY (region string, date string)
```

この部分でパーティションとするキーを指定しています。今回はリージョンと日付。

S3に存在するTrailのデータ内にはこの情報はないのですが、S3のPrefixとしてリージョンと日付が切られた形で出力されているため、このようなパーティショニングが可能です。

```sql
    'projection.date.type' = 'date',
    'projection.date.range' = '{任意の開始日(yyyy/MM/dd形式で)},NOW',
    'projection.date.format' = 'yyyy/MM/dd',
    'projection.date.interval' = '1',
    'projection.date.interval.unit' = 'DAYS',
```

この部分で、カラムとしての`date`についての設定をしています。見てわかる通りですが、型や日付書式、いつからいつまでの範囲でパーティションを作成するか、などを指定しています。

```sql
    'projection.region.type' = 'enum',
    'projection.region.values'='us-east-1,us-east-2,us-west-1,us-west-2,af-south-1,ap-east-1,ap-south-1,ap-northeast-2,ap-southeast-1,ap-southeast-2,ap-northeast-1,ca-central-1,eu-central-1,eu-west-1,eu-west-2,eu-south-1,eu-west-3,eu-north-1,me-south-1,sa-east-1',
```

次は`region`についての設定です。リージョンはenum型で、この値でパーティションが切られているよという指定です。

ちなみにここで指定する各リージョンを示す文字列は間違っていても問題はなく、単純にその値で検索してもヒットするものが0件になります。（パーティションが正しく認識されないため）

上記 CREATE EXTERNAL TABLE 文を実行した際も、 SELECT 文を実行した際もエラーとはならないため、ご注意を。

```sql
    'storage.location.template' = 's3://{対象のバケット名}/AWSLogs/{AWSアカウントID}/CloudTrail/${region}/${date}',
```

最後に、パーティションの位置についての設定です。

`${region}`や`${date}`の部分で、これまで設定してきたパーティションの値はS3のパス上のココに入るよ、ということを示しています。

上記のようなパーティショニングが無いとTrailのログを全て読み込むことになり、非常にAthenaの利用料が高くなります。

# 検索例文

**WHERE句に対象期間は必ず入れましょう！**

そうしないとAthenaの利用料が大爆発します。

そもそも検索も遅くなってしまうので、逆に検索したらやけに遅いな…という場合は正しくパーティションを使えていない可能性があるので急ぎ、実行をキャンセルしましょう。

## 操作の内容で検索

```sql
SELECT * FROM cloudtrail_logs
WHERE eventname = 'CreateBucket'
AND date BETWEEN '2021-01-01' AND '2021-01-31'
```

あの操作の詳細情報が見たい、このリソースを作ったのは誰だ？なんて時に。

## サービスの種類で検索

```sql
SELECT * FROM cloudtrail_logs
WHERE eventsource = 's3.amazonaws.com'
AND date BETWEEN '2021-01-01' AND '2021-01-31'
```

サービス単位で操作を追いたい時に。

## 特定ユーザーの操作すべてを検索

```sql
SELECT * FROM cloudtrail_logs
WHERE useridentity.userName = 'myuser'
AND date BETWEEN '2021-01-01' AND '2021-01-31'
```

ストーキング…

## 特定のリソース名で検索

正直これはオススメしません。

なぜなら、サービスごとにどのようなキーに対してどういう値で検索すれば良いかが異なるため、調査や試行錯誤に無駄に時間を取られがちです。

このリソースは誰が作ったんだ？という場合は、そのリソースを作成するAPIの機能名で検索するのが良いでしょう。([操作の内容で検索](#操作の内容で検索))

一応、特定のLambda関数を例にした検索クエリを以下に記載します。

```sql
SELECT * FROM cloudtrail_logs
WHERE requestParameters like '%arn:aws:lambda:ap-northeast-1:123456789012:function:my-function%'
AND date BETWEEN '2021-01-01' AND '2021-01-31'
```

# まとめ

今回のパーティションの追加の仕方は、Partition Projectionという方式を使っています。

手動でパーティションを追加していく方法もありますが、これは非常に手間がかかるので、せめてGlueのCrawlerを使わないとやってられないです…


それでは、よきTrail検索ライフを。