GuardDutyのFindingsをS3にエクスポートしてAthenaでテーブル化するときにトラブった話


記事概要

GuardDutyからS3にエクスポートした検知結果をGlue/Athena/QuickSightで分析しようとした。

遭遇したエラー

Glueのクローラを作成して実行したところ、下記のようなエラーが発生。

[e102dd64-92e3-47f5-bd18-8e61c1236d7c] ERROR : Error Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied; Request ID: 9B26CDF2818CD5E0; S3 Extended Request ID: ydaNgMzEk+bSreDMVS6TCT/+dA0bHK6KBKnZwQbNa2J1hW4EN5uMoybHTq9ISd+kxDV1Vz0AzNg=) retrieving file at s3://test-chida/AWSLogs/200356542217/GuardDuty/ap-northeast-1/2020/02/25/ccd7c013-77e8-3bf2-b212-4e1e6671c308.jsonl.gz. Tables created did not infer schemas from this file.

原因調査

stackoverflowで同じような事象を発見。

疑わしいのは

  • KMS
  • S3へのアクセス権限

HTTPの403エラーはForbiddenエラーと言ってアクセス権限関連のエラーなので、確かに怪しい。S3へのアクセス権限は問題なさそうなのでKMSが原因の可能性が高い。

そういえばGuardDutyのエクスポート設定時にKMSのキーを指定したような気が…

確認してみるとGuardDutyの検知結果は、指定したKMSキーで暗号化されてS3に出力されるようだ。

KMSで暗号化されたデータをGlueのクローラでテーブル化する方法を考えればいい。

対処方法

調べてみると下記の記事に遭遇。

amazon web services - Can you ingest an encrypted S3 Object (Text file) using AWS Glue? - Stack Overflow

どうやら

  • S3の暗号化が「SSE-KMS1」の場合はGlueのIAMロールにKMS権限を与えればOK
  • S3の暗号化が「CSE-KMS2」の場合は無理

SSE-KMSの場合はGlueのIAMロールに下記のポリシーを与えればいいらしい。

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": [
            "kms:Decrypt"
        ],
        "Resource": [
            "arn:aws:kms:<Your Region>:<Your Account Number>:key/<Your Key ID>"
        ]
    }
}

結果、Glueのクローラはうまく動いた!
が!

結局ダメ。Athenaでテーブル確認できず…

AWS Developer Forums: Metadata Error in Athena on table from ...

GlueとAthenaの連携がうまくとれていないのか?Glue何もわからん。

暗号化されたデータをAthenaでテーブル化する

そもそもGuardDutyの検知結果がもっとシンプルだったらAthenaで直接テーブル作成ができたのだ。※複雑だったのでGlueで前処理しようとした。

もう強引にAthenaで直接CREATE文を実行することにした。

JSONSerDe によるマッピングを使って、入れ子の JSON から Amazon Athena のテーブルを作成する | Amazon Web Services ブログ

CREATE EXTERNAL TABLE guradduty_findnings (
        schemaVersion STRING,
        accountId STRING,
        region STRING,
        partition STRING,
        id STRING,
        arn STRING,
        type STRING,
        resource STRUCT < 
            resourceType: STRING,
            accessKeyDetails: STRUCT < 
                accessKeyId: STRING,
                principalId: STRING,
                userType: STRING,
                userName: STRING 
            > 
        >,
        service STRUCT < 
            serviceName: STRING,
            detectorId: STRING,
            action: STRUCT < 
                actionType: STRING,
                awsApiCallAction: STRUCT < 
                    api: STRING,
                    serviceName: STRING,
                    callerType: STRING,
                      remoteIpDetails: STRUCT < 
                        ipAddressV4: STRING,
                        organization: STRUCT < 
                            asn: STRING,
                            asnOrg: STRING,
                            isp: STRING,
                            org: STRING 
                        >,
                        country: STRUCT < 
                            countryName: STRING 
                        >,
                        city: STRUCT < 
                            cityName: STRING 
                        >,
                        geoLocation: STRUCT < 
                            lat: FLOAT,
                            lon: FLOAT 
                        > 
                    >,
                    affectedResources: STRING 
                > 
            >,
            resourceRole: STRING,
            additionalInfo: STRUCT < 
                recentApiCalls: array < 
                    STRUCT < 
                        api: STRING,
                        count: INT 
                    > 
                > 
            >,
            evidence: STRING,
            eventFirstSeen: STRING,
            eventLastSeen: STRING,
            archived: BOOLEAN,
            count: INT
        >,
        severity INT,
        createdAt STRING,
        updatedAt STRING,
        title STRING,
        description STRING 
) 
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
LOCATION 's3://[bucket-name]/AWSLogs/200356542217/GuardDuty/'

今回はなぜか必要なかったが、AthenaならCSE-KMSの場合でもクエリ実行が可能らしい。
発表: Amazon Athena が暗号化されたデータのクエリのサポートを追加 | Amazon Web Services ブログ

補足ポイント

GuardDutyのS3エクスポート機能

GuardDutyの検知結果(Findings)の保持期間は90日間3なので、過去の検知結果はいつの間にか消えてしまう。

とはいえ、「3ヶ月前に怪しい行動してるヤツがいなかったか?」とか「前にもポートスキャン受けてたんだけど…いつだっけ」みたいな過去の検知結果を確認したいケースもあるはずだ。

GuardDutyでは結果を保存するための機能としてS3エクスポート機能がある。CloudWatch Logsへのエクスポートもあるが、結局永久的に保存されるわけではない。

S3暗号化

S3暗号化には5パターンある

暗号方式 説明
SSE-S3 サーバーサイド暗号化(AWS提供のS3鍵を利用)
SSE-KMS サーバーサイド暗号化(KMS鍵を利用)
SSE-C サーバーサイド暗号化(利用者が用意した鍵を利用)
CSE-KMS クライアントサイド暗号化(KMS鍵を利用)
CSE-C クライアントサイド暗号化(利用者が用意した鍵を利用)

サーバーサイド暗号化よりもクライアントサイド暗号化の方がセキュリティ面では強力だが、今回のようにトラブルが発生しやすい。

重要でないデータはSSE-S3で十分だと思われる。

参考

unclear instructions · Issue #2 · aws-samples/aws-glue-samples

AWS Glue の「ERROR:Internal Service Exception」を解決する

S3の暗号化についてまとめてみた(2018年6月版) - 本日も乙

How to visualize Amazon GuardDuty findings: serverless edition | AWS Security Blog


  1. Server-Side-Encryption KMS 

  2. Client-Side-Encryption KMS 

  3. Amazon GuardDuty のクォータ - Amazon GuardDuty