GuardDutyの検出結果をエクスポートするためのKMSキーとS3バケットをCloudFormationで作った

GuardDutyの検出結果をS3にエクスポートすることが可能ですが、いくつか事前準備が必要です。そこをちょっと楽にするCloudFormationテンプレートを作成しました。

コンバンハ、千葉(幸)です。

GuardDutyでは検出結果をS3にエクスポートすることができます。

エクスポート時には、内容を暗号化するためのKMSキーと、必要なバケットポリシーを定義したS3バケットを準備する必要があります。それらをサクッと用意するためのCloudFormationのテンプレートを作成しました。

目次

本記事の対象範囲

以下のイメージです。

青枠で囲っている部分が、今回新たに作成したCloudFormationテンプレートで構成されるリソースです。

補足

  • エクスポート用のKMSキーおよびS3バケットはGuardDutyを有効化するリージョンと同一のリージョンで新規作成するものとしています
  • GuardDutyディテクター、CloudWatchイベントルール、SNSトピックなどのリソースは、先人のテンプレートがあるのでそれを活用させてもらいます
  • S3へのエクスポートも含めてCloudFormationで完結できるのでは、と考えていたのですが、2020年3月時点でリファレンスに記述が見当たらなかったので、そこの設定だけは手動で行うものとしています

GuardDutyの検出結果のS3へのエクスポート

詳細は以下の記事に詳しいです。

公式ドキュメントも一緒に確認しておきましょう。

簡単に言うと、以下の事前設定が必要です。

  • KMSキー(カスタマーマスターキー)
  • S3バケットポリシー

エクスポートの際にはカスタマーマスタキーによる暗号化が必須になっています。暗号化なしや、デフォルトKMSキーを用いた暗号化には対応していません。

また、ここでのKMSキーとS3バケットは同一のリージョンにある必要があります。

KMSキーポリシー

使用するKMSキーはキーポリシーによって以下が許可されている必要があります。

{
    "Sid": "Allow GuardDuty to use the key",
    "Effect": "Allow",
    "Principal": {
        "Service": "guardduty.amazonaws.com"
            },
            "Action": "kms:GenerateDataKey",
            "Resource": "*"
}

S3バケットポリシー

エクスポート先のS3バケットはバケットポリシーによってGuardDutyからのエクスポートを許可していることが必要です。 ドキュメントに記載されているサンプルは以下の通りです。Denyの部分は必須ではありませんが、特に理由がなければ制限しておくに越したことはないでしょう。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Allow GuardDuty to use the getBucketLocation operation",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:GetBucketLocation",
            "Resource": "arn:aws:s3:::myBucketName"
        },
        {
            "Sid": "Allow GuardDuty to upload objects to the bucket",
            "Effect": "Allow",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::myBucketName/[optional prefix]/*"
        },
        {
            "Sid": "Deny unencrypted object uploads. This is optional",
            "Effect": "Deny",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::myBucketName/[optional prefix]/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption": "aws:kms"
                }
            }
        },
        {
            "Sid": "Deny incorrect encryption header. This is optional",
            "Effect": "Deny",
            "Principal": {
                "Service": "guardduty.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::myBucketName/[optional prefix]/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption-aws-kms-key-id": "arn:aws:kms:region:111122223333:key/KMSKeyId"
                }
            }
        },
        {
            "Sid": "Deny non-HTTPS access",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::myBucketName/*",
            "Condition": {
                "Bool": {
                    "aws:SecureTransport": "false"
                }
            }
        }
    ]
}

GuardDutyを有効化するCloudFormationテンプレート

以下の記事の内容を参考にします。

記事の主題は全リージョンで有効化するところまでですが、1つのリージョンで有効化する際のテンプレートが載っているので、そこにアドオンする形で作成していきます。

折り畳み
AWSTemplateFormatVersion: 2010-09-09
Description:
  "enable guardduty and set alert"
Parameters:
  MailAddress:
    Description: Enter email address to send notification.
    Type: String
Resources:
  GDD:
    Type: "AWS::GuardDuty::Detector"
    Properties:
      Enable: true
  SNST:
    Type: "AWS::SNS::Topic"
    Properties:
      TopicName: GuardDutyTopic
  SNSS:
    Type: "AWS::SNS::Subscription"
    Properties:
      Endpoint: !Ref MailAddress
      Protocol: email
      TopicArn: !Ref SNST
  SNSTP:
    Type: "AWS::SNS::TopicPolicy"
    Properties:
      PolicyDocument:
        Id: default_policy_ID
        Version: "2012-10-17"
        Statement:
        - Sid: default_statement_ID
          Effect: Allow
          Principal:
            AWS: "*"
          Action:
            - "SNS:GetTopicAttributes"
            - "SNS:SetTopicAttributes"
            - "SNS:AddPermission"
            - "SNS:RemovePermission"
            - "SNS:DeleteTopic"
            - "SNS:Subscribe"
            - "SNS:ListSubscriptionsByTopic"
            - "SNS:Publish"
            - "SNS:Receive"
          Resource: !Ref SNST
          Condition:
            StringEquals:
              "AWS:SourceOwner": !Ref "AWS::AccountId"
        - Sid: AWSEvents_AlertGuardDutyFindings_Id123
          Effect: Allow
          Principal:
            Service:
            - "events.amazonaws.com"
          Action: "sns:Publish"
          Resource: !Ref SNST
      Topics:
      - !Ref SNST
  ER:
    Type: "AWS::Events::Rule"
    Properties:
      Name: AlertGuardDutyFindings
      Description: "Alert to SNS topic when find threats by GuardDuty"
      EventPattern: {
                      "source": [
                        "aws.guardduty"
                      ],
                      "detail-type": [
                        "GuardDuty Finding"
                      ]
                    }
      Targets:
        - Arn: !Ref SNST
          Id: Id123

追加する前の時点で、以下のリソースが定義されています。

論理ID リソースタイプ
GDD AWS::GuardDuty::Detector
SNST AWS::SNS::Topic
SNSS AWS::SNS::Subscription
SNSTP AWS::SNS::TopicPolicy

追加するCloudFormationテンプレート

以下をアドオンすることにしました。

折り畳み
# ------------------------------------------------------------#
# KMS key for GuardDuty
# ------------------------------------------------------------#
  KMSK:
    Type: AWS::KMS::Key
    Properties:
      Description: "Key for GuardDuty"
      EnableKeyRotation: true
      KeyPolicy:
        Version: "2012-10-17"
        Id: "key-default-1"
        Statement:
         -
            Sid: "Enable IAM User Permissions"
            Effect: "Allow"
            Principal:
               AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
            Action: "kms:*"
            Resource: "*"
         -
            Sid: "Allow GuardDuty to use the key"
            Effect: "Allow"
            Principal:
              Service: "guardduty.amazonaws.com"
            Action:
              - "kms:GenerateDataKey"
            Resource: "*"
      PendingWindowInDays: 7
  KMSA:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: "alias/KMS-GuardDuty"
      TargetKeyId:
        Ref: KMSK
# ------------------------------------------------------------#
# S3 Bucket for GuardDuty
# ------------------------------------------------------------#
  S3B:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: ## バケット名を指定してください ##
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: 365days-All-LifeCycleRule
            Status: Enabled
            ExpirationInDays: 365
            NoncurrentVersionExpirationInDays: 10
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
  S3BP:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket:
        Ref: "S3B"
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: "Deny non-HTTPS access"
            Action: ['s3:*']
            Effect: "Deny"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
            Condition:
              Bool:
                aws:SecureTransport: "false"
          - Sid: "Deny incorrect encryption header"
            Action: ['s3:PutObject']
            Effect: "Deny"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
            Condition:
              StringNotEquals:
                s3:x-amz-server-side-encryption-aws-kms-key-id: !GetAtt KMSK.Arn
          - Sid: "Deny unencrypted object uploads"
            Action: ['s3:PutObject']
            Effect: "Deny"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
            Condition:
              StringNotEquals:
                s3:x-amz-server-side-encryption: "aws:kms"
          - Sid: "Allow PutObject"
            Action: ['s3:PutObject']
            Effect: "Allow"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
          - Sid: "Allow GetBucketLocation"
            Action: ['s3:GetBucketLocation']
            Effect: "Allow"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B']]
            Principal:
              Service: [guardduty.amazonaws.com]

追加したリソースは以下の通りです。

論理ID リソースタイプ
KMSK AWS::KMS::Key
KMSA AWS::KMS::Alias
S3B AWS::S3::Bucket
S3BP AWS::S3::BucketPolicy

以下、リソースを順番に確認していきます。

AWS::KMS::Key

公式リファレンス

  KMSK:
    Type: AWS::KMS::Key
    Properties:
      Description: "Key for GuardDuty"
      EnableKeyRotation: true
      KeyPolicy:
        Version: "2012-10-17"
        Id: "key-default-1"
        Statement:
         -
            Sid: "Enable IAM User Permissions"
            Effect: "Allow"
            Principal:
               AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root"
            Action: "kms:*"
            Resource: "*"
         -
            Sid: "Allow GuardDuty to use the key"
            Effect: "Allow"
            Principal:
              Service: "guardduty.amazonaws.com"
            Action:
              - "kms:GenerateDataKey"
            Resource: "*"
      PendingWindowInDays: 7

キーポリシーとしては、デフォルトのポリシー + GuardDutyの要件を満たすように設定しています。

AWS::KMS::Alias

公式リファレンス

  KMSA:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: "alias/KMS-GuardDuty"
      TargetKeyId:
        Ref: KMSK

エイリアスがリソースとして分かれているのが少し意外でしたが、キーと1対1ではないことが理由のようです。

各エイリアスは 1 つの CMK のみを指し示すことができますが、複数のエイリアスが同じ CMK を指すことができます。

また、先頭に「alias/」を付与しないとCloudFormation実行時にエラーになるので注意が必要です。

各エイリアス名は、alias/ で始まり、その後に名前が続きます (alias/exampleKey など)。エイリアス名に使用できるのは、英数字、スラッシュ (/)、アンダースコア (_)、およびダッシュ (-) のみです。エイリアス名を alias/aws/ で始めることはできません。

AWS::S3::Bucket

公式リファレンス

  S3B:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: ## バケット名を指定してください ##
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: 365days-All-LifeCycleRule
            Status: Enabled
            ExpirationInDays: 365
            NoncurrentVersionExpirationInDays: 10
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True

このサンプルではバージョニングを有効化してライフサイクルルールを設定していますが、エクスポートの要件として必要なわけではありません。必要に応じてカスタマイズしてください。 また、S3バケット名は埋め込みで指定するか、パラメータで渡す形にしてください。

AWS::S3::BucketPolicy

公式リファレンス

  S3BP:
    Type: "AWS::S3::BucketPolicy"
    Properties:
      Bucket:
        Ref: "S3B"
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: "Deny non-HTTPS access"
            Action: ['s3:*']
            Effect: "Deny"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
            Condition:
              Bool:
                aws:SecureTransport: "false"
          - Sid: "Deny incorrect encryption header"
            Action: ['s3:PutObject']
            Effect: "Deny"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
            Condition:
              StringNotEquals:
                s3:x-amz-server-side-encryption-aws-kms-key-id: !GetAtt KMSK.Arn
          - Sid: "Deny unencrypted object uploads"
            Action: ['s3:PutObject']
            Effect: "Deny"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
            Condition:
              StringNotEquals:
                s3:x-amz-server-side-encryption: "aws:kms"
          - Sid: "Allow PutObject"
            Action: ['s3:PutObject']
            Effect: "Allow"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B','/*']]
            Principal:
              Service: [guardduty.amazonaws.com]
          - Sid: "Allow GetBucketLocation"
            Action: ['s3:GetBucketLocation']
            Effect: "Allow"
            Resource:
            - !Join ['', ['arn:aws:s3:::', !Ref 'S3B']]
            Principal:
              Service: [guardduty.amazonaws.com]

上で確認したサンプルのバケットポリシーを再現しています。サンプルではリソースとしてプレフィックス([optional prefix])まで絞った形で記載されていますが、このコードではそこまで制限はしていません。

S3へのエクスポート設定

上記を踏まえた形でCloudFormationを実行すればKMSキーとS3バケットが作成されるので、エクスポートの設定をマネジメントコンソールから実施します。

GuardDutyのコンソール画面に遷移し、[設定]ペインを選択します。S3バケットについて「今すぐ設定する」を押下します。

作成したS3バケットとKMSキーを指定して「保存」を押下すれば設定完了です。オプションでプレフィックスを指定することもできます。

終わりに

GuardDutyの検出結果をエクスポートするためのS3バケットとKMSキーを作成するCloudFormationテンプレートのご紹介でした。

あとはエクスポートの設定まで含めてCloudFormation化されることを期待するばかりです。

実際の構成としては複数リージョンの結果を1つのS3バケットに集約したりなど様々なパターンが考えられますが、「リージョンごとにGuardDuty + KMSキー + S3バケット」というユースケースに合致していれば参考にしていただければと思います。

以上、最近バナナをもりもり食べる千葉(幸)がお送りしました。