Amazon GuardDuty で検出結果の S3 への保存をクロスアカウントしようとしてハマった話

記事タイトルとURLをコピーする

マネージドサービス部 佐竹です。
Amazon GuardDuty の検出結果をクロスアカウントアクセスして S3 バケットに保存する設定を行う際に、少し権限でハマったためその記録を残しておきます。

はじめに

AWS Organizations を利用し、複数の AWS アカウント(マルチアカウント)を管理・運用するエンタープライズ企業様が増えています。

弊社ではその管理・運用を代行させて頂くことも多々ありますが、その中においてベストプラクティスに則り「ログ保管用の AWS アカウント」を作成し、そのアカウントの S3 バケットにログを集約することがあります。

今回は、その前提において Amazon GuardDuty で検出結果をログ保管用アカウントの S3 バケットにエクスポートし集約する実装を行いたい状況となりました。

クロスアカウントを伴う検出結果のエクスポート

構成図にすると、上図の通りとなります。セキュリティアカウント(Account-S)の Amazon GuardDuty の設定において、ログ保管アカウント(Account-L)の Amazon S3 を指定する構成になります。

AWS 公式ドキュメント

docs.aws.amazon.com

「検出結果のエクスポート」については上記ドキュメントに一通りまとまっています。

ただ少々理解が難しい部分もありますので、詳しく説明した内容を以下に記載していきます。

先に構成図とハマったポイントについて

先ほどの構成図は「分かり易さ」を重視して、掲載するコンポーネントを可能な限り減らした概念的な構成図でした。こちらの構成図は、今回設定が必要な4か所を①②③④で示すと共に、設定に登場する全てのコンポーネントを表記しています。

ハマったポイントは「③のバケットポリシー」と「④の IAM Role への権限追加」です。

今回の設定では、前提として S3 バケットのクロスアカウントアクセスが必須になります。これはつまり、キュリティアカウント(Account-S)側の IAM エンティティ(Role か User)がログ保管アカウント(Account-L)の 所有する S3 バケットにアクセスできなければなりません。

このような S3 バケットのクロスアカウントアクセスの権限は、他のサービスのログ集約設定では恐らく求められることは無いと思うのですが、Amazon GuardDuty ではそれが必要となっており、権限でかなりハマってしまいました。

具体的な設定方法

①②③④の順に具体的な設定について解説していきます。

① Customer Managed Key の作成とキーポリシーの設定

GuardDuty の検出結果を S3 バケットに出力する場合、AWS Key Management Service(KMS)による暗号化が必須となっています。

加えて、GuardDuty には AWS Managed Key が(2023年7月現在)存在しておりません。よってセキュリティアカウントにおいて Customer Managed Key の作成が必要となります。

Customer Managed Key を作成した後に重要な点は、その鍵のキーポリシーです。デフォルトのキーポリシーでは、GuardDuty が利用することが不可能となっていますため、これを修正します。

AWS 公式ドキュメントに記載があるステートメントは以下の通りです。

{    
    "Sid": "AllowGuardDutyKey",
    "Effect": "Allow",
    "Principal": {
        "Service": "guardduty.amazonaws.com"
    },
    "Action": "kms:GenerateDataKey",
    "Resource": "arn:aws:kms:Region1:444455556666:key/KMSKeyId",
    "Condition": {
        "StringEquals": {
            "aws:SourceAccount": "123456789012",
            "aws:SourceArn": "arn:aws:guardduty:Region2:123456789012:detector/SourceDetectorID"  
        }
    }
}

以下のように修正します。「Account-SのID」と「KMSKeyId」「SourceDetectorID」は適切な値に修正ください。

        {
            "Sid": "AllowGuardDutyKey",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "kms:GenerateDataKey",
            "Resource": "arn:aws:kms:ap-northeast-1:Account-SのID:key/35369d6c-d026-4b5b-example-7c41809ba999",
            "Condition": {
                "StringEquals": {
                    "aws:SourceArn": "arn:aws:guardduty:ap-northeast-1:Account-SのID:detector/2cc282bc6ea-example-005aa16b1a7",
                    "aws:SourceAccount": "Account-SのID"
                }
            }
        }

既存のキーポリシーの末尾に本ステートメントを追加します。

設定が完了したら、後の工程のために Customer Managed Key の Arn を保存しておきます。

② S3 バケットの作成とバケットポリシーの設定

次に、GuardDuty の検出結果を保存するための S3 バケットを作成します。なお S3 バケットはログ保管アカウント(Account-L)の Amazon S3 を利用するため、ここでクロスアカウントになります。

S3 バケットの作成を行った後、そのバケットにおいて「バケットポリシー」を設定します。

バケットポリシーも AWS 公式ドキュメントに記載がある通りですが、例がかなり長いので少し省略して以下に設定例を記載します。*1

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowGuardDutygetBucketLocation",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:GetBucketLocation",
            "Resource": "arn:aws:s3:::bucket-name-for-guardduty",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "Account-SのID",
                    "aws:SourceArn": "arn:aws:guardduty:ap-northeast-1:Account-SのID:detector/2cc282bc6ea-example-005aa16b1a7"
                }
            }
        },
        {
            "Sid": "AllowGuardDutyPutObject",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::bucket-name-for-guardduty/*",
            "Condition": {
                "StringEquals": {
                    "aws:SourceAccount": "Account-SのID",
                    "aws:SourceArn": "arn:aws:guardduty:ap-northeast-1:Account-SのID:detector/2cc282bc6ea-example-005aa16b1a7"
                }
            }
        },
        {
            "Sid": "AllowSSLRequestsOnly",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::bucket-name-for-guardduty",
                "arn:aws:s3:::bucket-name-for-guardduty/*"
            ],
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "false"
                }
            }
        }
    ]
}

bucket-name-for-guarddutyAccount-SのIDarn:aws:guardduty:ap-northeast-1:Account-SのID:detector/2cc282bc6ea-example-005aa16b1a7 は適切に修正ください。

設定が完了したら、後の工程のために S3 バケットの Arn を保存しておきます。

ここまでの振り返り

ここまでの作業で、上画像の①と②の設定が完了しました。正直なところ「これだけで十分に設定が足りている」ようにも思われます。

ということで、このタイミングで一度セキュリティアカウント(Account-S)にログインし、GuardDuty の「検出結果のエクスポート」設定を行ってみましょう。どうなるでしょうか?

Failed to configure export settings because you do not have access to either the s3:GetObject or s3:ListBucket action. のエラーと共に失敗しました。

何故でしょうか?ここでかなり頭を悩ませたのですが、このヒントが AWS 公式ドキュメントに記載されていました

該当箇所を引用します。

検出結果のエクスポートの設定に必要な許可
検出結果のエクスポートのオプションを設定するときは、検出結果を保存するバケットと、データ暗号化に使用する KMS キーを選択します。GuardDuty アクションに対する許可に加えて、検出結果のエクスポートのオプションを設定するには、次のアクションに対する許可も必要です。

  • kms:ListAliases
  • s3:CreateBucket
  • s3:GetBucketLocation
  • s3:ListAllMyBuckets
  • s3:PutBucketAcl
  • s3:PutBucketPublicAccessBlock
  • s3:PutBucketPolicy
  • s3:PutObject

この文章が「一体何を示しているのか」を理解するのに小一時間ほど時を要したのですが、ようは「設定作業を行う作業者に上記の権限が必要だ」ということを示しているようでした。

我々が作業を行うときは基本的に弊社管理の PowerUser を利用しています。これは Administrator に等しい権限が付与されています。このため「設定作業を行う作業者=サーバーワークスの PU」が権限に困ることはまずないのですが、今回は例外です。

KMS の鍵についてはセキュリティアカウント内に存在しているため kms:ListAliases はパスできます。つまり今回エラーの原因となっているのは 他アカウントが持つ S3 バケットに対する権限です。

Amazon S3 のクロスアカウントアクセスについて

少しここで解説をすると、S3 のアクセス許可に関しては少々癖があります。ざっくりとですがまとめると以下のような動作となります。

  • 自アカウントの S3 バケット
    • IAM エンティティにアクセス権が付与されていればバケットポリシーで許可がなくともアクセスが可能
    • IAM エンティティにアクセス権を付与しなくともバケットポリシーで許可されていればアクセスが可能
  • 他アカウントの S3 バケット
    • IAM エンティティにも、バケットポリシーにもどちらにも許可を明示的に記載する必要がある

今回は、セキュリティアカウント(Account-S)からログ保管アカウント(Account-L)の S3 バケットを指定することになるため、上記「他アカウントの S3 バケット」の権限設計が必要です。このために作業者が利用している Account-S の IAM Role にも、Account-L のバケットポリシーにも、どちらにも明示的にアクセス許可を記載しなければなりません。

そしてこれらが「Account-S の IAM Role = ④」 &「Account-L のバケットポリシー = ③」の作業にそれぞれ相当します。

また参考にバケットのクロスアカウントに関する AWS 公式ドキュメントを紹介します。

docs.aws.amazon.com

というわけで、作業の続きです。

③ クロスアカウントアクセスのためのバケットポリシー設定

②でバケットポリシーを設定したため、その続きとして先にバケットポリシーに設定を施します。

        {
            "Sid": "AllowCrossAccountAccess",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::Account-SのID:role/作業用IAMRole名"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::bucket-name-for-guardduty",
                "arn:aws:s3:::bucket-name-for-guardduty/*"
            ]
        }

arn:aws:iam::Account-SのID:role/作業用IAMRole名bucket-name-for-guardduty については適切に修正の上、本ステートメントをバケットポリシーに追記してください。

なお、今回の設定例では許可する Action を s3:* とかなり広めに許可しています。必要最低限の権限にしたい場合は、こちらを修正してご利用ください。

④ クロスアカウントアクセスのための IAM ポリシー設定

続けて、セキュリティアカウント(Account-S)の作業用 IAM Role に IAM ポリシーを付与する必要があります。

先に記載したように「Administrator に等しい権限が付与されている」としても S3 バケットのクロスアカウントアクセスは不可能なためです。

まずは以下のステートメントで、IAM ポリシーを作成します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowCrossAccountAccessForGuardDuty",
            "Effect": "Allow",
            "Action": [
                "s3:GetBucketLocation",
                "s3:List*",
                "s3:Put*"
            ],
            "Resource": [
                "arn:aws:s3:::bucket-name-for-guardduty",
                "arn:aws:s3:::bucket-name-for-guardduty/*"
            ]
        }
    ]
}

bucket-name-for-guardduty については適切に修正してください。権限については s3:CreateBucket はバケット作成済のため不要としました。また、List と Put はまとめて * で許可していますが、必要最低限の権限にしたい場合は修正してご利用ください。

IAM ポリシーを作成したら、忘れずに IAM Role にアタッチします。

動作確認として、以下の CLI コマンド等をセキュリティアカウント(Account-S)から実行してみるのも良いでしょう。

$ aws s3api list-objects --bucket bucket-name-for-guardduty

{
    "RequestCharged": null
}

設定が正しければ、正しく結果が返ってきます。設定が不足している場合は An error occurred (AccessDenied) when calling the ListObjects operation: Access Denied などのエラーになります。この場合は、③と④の設定どちらかが正しく完了していません。

というわけで、ここまでの「①②③④」の作業が全て完了して初めて、今回の設定に必要となる前提の権限付与が完了となります。

GuardDuty の検出結果エクスポート設定を行う

最後に GuardDuty の検出結果エクスポート設定を行いますが、マネジメントコンソールだと先に画面キャプチャーで紹介した通り Arn をそれぞれ入力するだけのためここでは AWS CLI のコマンドを紹介します。

利用するコマンドは「create-publishing-destination」です。

$ aws guardduty create-publishing-destination \
 --detector-id 2cc282bc6ea-example-005aa16b1a7 \
 --destination-type S3 \
 --destination-properties DestinationArn=arn:aws:s3:::bucket-name-for-guardduty/FolderName/,KmsKeyArn=arn:aws:kms:ap-northeast-1:Account-SのID:key/35369d6c-d026-4b5b-example-7c41809ba999

{
    "DestinationId": "0ad4b60d-example-aaaec8293f"
}

正しく設定ができれば、DestinationId が返り値として得られます。

余談ですが、S3 バケットの Arn に FolderName を入力する場合は予めそのフォルダを作成しておく必要がありました。

An error occurred (BadRequestException) when calling the CreatePublishingDestination operation:  
The request failed because the resource folder specified in the destinationArn parameter does not exist.

設定時にフォルダが見つからない場合は、上記のエラーが返ります。

まとめ

Amazon GuardDuty の検出結果を他アカウントの S3 バケットに保存する設定を行う際に、権限周りでハマったためブログにさせて頂きました。

GuardDuty の検出結果エクスポート設定には予め「①と②」だけではなく S3 のクロスアカウントアクセスに必要な「③と④」も必要な点にご注意ください。

そして(本文に記載した内容の再掲ですが)以下が S3 のアクセス権に関するざっくりとしたまとめです。

  • 自アカウントの S3 バケット
    • IAM エンティティにアクセス権が付与されていればバケットポリシーで許可がなくともアクセスが可能
    • IAM エンティティにアクセス権を付与しなくともバケットポリシーで許可されていればアクセスが可能
  • 他アカウントの S3 バケット
    • IAM エンティティにも、バケットポリシーにもどちらにも許可を明示的に記載する必要がある

こちらも仕様として覚えておくと今後役立つ日がくると思われます。ということで以上になります。

では、またお会いしましょう。

*1:「AllowSSLRequestsOnly」は https 以外のアクセスを拒否するポリシーです。これは記載しておいた方が良いためここでも紹介していますが、本筋には関係がないため除外して頂いても問題ありません

佐竹 陽一 (Yoichi Satake) エンジニアブログの記事一覧はコチラ

マネージドサービス部所属。AWS資格全冠。2010年1月からAWSを利用してきています。2021-2022 AWS Ambassadors/2023 Japan AWS Top Engineers/2020-2023 All Certifications Engineers。AWSのコスト削減、最適化を得意としています。