Amazon GuardDuty S3マルウェアプロテクションをAWS Organizations組織全体で一括有効化しようとして諦めた話

Amazon GuardDuty S3マルウェアプロテクションをAWS Organizations組織全体で一括有効化しようとして諦めた話

CloudFormationカスタムリソースとかで強引にやれば可能かもしれませんが・・・
Clock Icon2025.03.12

お疲れさまです。とーちです。

AWS GuardDuty S3マルウェアプロテクションをOrganizations組織内の全アカウントで有効化しようとして、結局あきらめることにした経緯と発見したことを共有したいと思います。同じことを考えている方の参考になれば幸いです。

想定読者

  • AWS Organizations環境でGuardDuty S3マルウェアプロテクションを全アカウントに展開しようとしている方

GuardDuty S3マルウェアプロテクションの特殊性

まず前提として、S3マルウェアプロテクションは他のGuardDuty機能と比べても若干特殊です。どのような点が特殊なのか見ていきましょう。

GuardDutyを有効化していなくても使える

S3マルウェアプロテクションはGuardDutyの一機能という位置づけにもかかわらず、GuardDutyを有効化しなくても使うことができます。ただし、GuardDutyを有効化していない場合は当然ながらGuardDutyのfindingは生成されません。

GuardDutyはSecurityHubと統合して通知を一括化することが可能ですが、GuardDutyを有効化していない場合はこういった統合も実現できないということになります。

GuardDutyの管理アカウントからメンバーアカウントのS3マルウェアプロテクションを有効化できない

GuardDutyのEC2 Malware Protectionや(マルウェアプロテクションではない)S3 ProtectionはGuardDutyの管理アカウントからすべてのメンバーアカウントに対してまとめて有効化することが可能です。また、今後追加されたアカウントについても自動で有効化するといったことができます。

しかし、S3マルウェアプロテクションはこの機能に対応していません。マネージメントコンソールを見てもS3マルウェアプロテクションについての記載はないことが分かります。

image.png

AWS公式ドキュメントにも以下のように明確に記載があります。

自分のアカウントに属する Amazon S3 バケットに対して、S3 のマルウェア防御を有効にすることができます。委任された GuardDuty 管理者アカウントでは、メンバーアカウントに属する Amazon S3 バケットでこの機能を有効にすることはできません。

参考:Amazon GuardDuty S3 マルウェア防御

CloudFormation StackSetsでの一括有効化は可能か?

「GuardDutyの管理アカウントからはできないけど、StackSetsを使ってOrganizations内の全アカウントに展開できるのでは?」と考え、この方法も試してみました。しかし、以下の点から残念ながらこの方法も難しいことがわかりました。

自分が試したCloudFormationのテンプレートは以下の通りです。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'IAM Role for GuardDuty S3 Malware Protection'

Parameters:
  KMSKeyId:
    Type: String
    Description: 'ID of the KMS key used for bucket encryption (if applicable)'
    Default: ''

  Region:
    Type: String
    Description: 'AWS Region'
    Default: 'ap-northeast-1'

Conditions:
  HasKMSKey: !Not [!Equals [!Ref KMSKeyId, '']]

Resources:
  MalwareProtectionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: 'GuardDutyS3MalwareProtectionRole'
      Description: 'Role for GuardDuty to scan S3 objects for malware'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: malware-protection-plan.guardduty.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: 'S3MalwareProtectionPolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: 'AllowManagedRuleToSendS3EventsToGuardDuty'
                Effect: Allow
                Action:
                  - 'events:PutRule'
                  - 'events:DeleteRule'
                  - 'events:PutTargets'
                  - 'events:RemoveTargets'
                Resource:
                  - !Sub 'arn:aws:events:${Region}:${AWS::AccountId}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*'
                Condition:
                  StringLike:
                    'events:ManagedBy': 'malware-protection-plan.guardduty.amazonaws.com'
                    
              - Sid: 'AllowGuardDutyToMonitorEventBridgeManagedRule'
                Effect: Allow
                Action:
                  - 'events:DescribeRule'
                  - 'events:ListTargetsByRule'
                Resource:
                  - !Sub 'arn:aws:events:${Region}:${AWS::AccountId}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*'
                  
              - Sid: 'AllowPostScanTag'
                Effect: Allow
                Action:
                  - 's3:PutObjectTagging'
                  - 's3:GetObjectTagging'
                  - 's3:PutObjectVersionTagging'
                  - 's3:GetObjectVersionTagging'
                Resource:
                  - !Sub 'arn:aws:s3:::*/*'
                  
              - Sid: 'AllowEnableS3EventBridgeEvents'
                Effect: Allow
                Action:
                  - 's3:PutBucketNotification'
                  - 's3:GetBucketNotification'
                Resource:
                  - !Sub 'arn:aws:s3:::*'
                  
              - Sid: 'AllowPutValidationObject'
                Effect: Allow
                Action:
                  - 's3:PutObject'
                Resource:
                  - !Sub 'arn:aws:s3:::*/malware-protection-resource-validation-object'
                  
              - Sid: 'AllowCheckBucketOwnership'
                Effect: Allow
                Action:
                  - 's3:ListBucket'
                Resource:
                  - !Sub 'arn:aws:s3:::*'
                  
              - Sid: 'AllowMalwareScan'
                Effect: Allow
                Action:
                  - 's3:GetObject'
                  - 's3:GetObjectVersion'
                Resource:
                  - !Sub 'arn:aws:s3:::*/*'
                  
              - !If
                - HasKMSKey
                - Sid: 'AllowDecryptForMalwareScan'
                  Effect: Allow
                  Action:
                    - 'kms:GenerateDataKey'
                    - 'kms:Decrypt'
                  Resource: !Sub 'arn:aws:kms:${Region}:${AWS::AccountId}:key/${KMSKeyId}'
                  Condition:
                    StringLike:
                      'kms:ViaService': !Sub 's3.${Region}.amazonaws.com'
                - !Ref 'AWS::NoValue'

  MalwareProtectionPlan:
    Type: 'AWS::GuardDuty::MalwareProtectionPlan'
    Properties:
      Role: !GetAtt MalwareProtectionRole.Arn
      ProtectedResource:
        S3Bucket:
          BucketName: "[バケット名]"
      Actions:
        Tagging:
          Status: 'ENABLED'
      Tags:
        - Key: 'Purpose'
          Value: 'MalwareProtection'

Outputs:
  MalwareProtectionRoleArn:
    Description: 'ARN of the IAM Role for GuardDuty S3 Malware Protection'
    Value: !GetAtt MalwareProtectionRole.Arn
    
  MalwareProtectionPlanId:
    Description: 'ID of the Malware Protection Plan'
    Value: !GetAtt MalwareProtectionPlan.MalwareProtectionPlanId
    
  MalwareProtectionPlanArn:
    Description: 'ARN of the Malware Protection Plan'
    Value: !GetAtt MalwareProtectionPlan.Arn

S3マルウェアプロテクションは単一バケットしか指定できない

S3マルウェアプロテクションは AWS::GuardDuty::MalwareProtectionPlan というタイプのリソースで作成することができます。このリソースのプロパティの中に ProtectedResource というものがあり、ここで対象のS3バケットを指定します。

問題はこのプロパティで、必ず単独のS3バケットを指定する必要があるということです。例えば、以下のようにワイルドカードで全バケットを指定することはできません。

ProtectedResource:
  S3Bucket:
    BucketName: "*"

このような指定をすると、以下のようなエラーが発生します。

image.png

もちろん、以下のように null 値を指定することもできません。

ProtectedResource:
  S3Bucket:
    BucketName: null

こちらも以下のようなエラーになります。

bash-5.2$ aws cloudformation deploy --template-file s3malware.yaml --stack-name sample-stack --capabilities CAPABILITY_NAMED_IAM

An error occurred (ValidationError) when calling the CreateChangeSet operation: [/Resources/MalwareProtectionPlan/Type/ProtectedResource/S3Bucket/BucketName] 'null' values are not allowed in templates

マネージメントコンソール上でも必ず単独のバケットを指定するようになっているので、アカウント内のすべてのバケットを指定という書き方はできないということです。

image.png

S3バケットはアカウントごとに一意である必要があるため、S3バケット名にアカウントIDをつけているケース以外では、StackSetsで組織内のアカウントにまとめて展開することは難しいということになります。

単一アカウント内のすべてのS3バケットに対して有効化する方法

全アカウントでの一括有効化は難しいですが、単独のアカウント内のすべてのS3バケットに対してS3マルウェアプロテクションを有効化するということであれば、以下のスクリプトで可能です。

# 事前にS3マルウェアプロテクション用のIAMロールを作成しておく
MALWARE_PROTECTION_ROLE_ARN="arn:aws:iam::111122223333:role/GuardDutyMalwareProtectionRole"
REGIONS=$(aws ec2 describe-regions --query 'Regions[?RegionOptStatusEquals!=`disabled`].RegionName' --output text)
# 各リージョンですべてのバケットリストを取得し、S3マルウェアプロテクションを有効化
for region in ${REGIONS}; do
  echo "##### Enabling S3 Malware Protection for all buckets in ${region}"
  
  # リージョン内のすべてのバケットを取得
  BUCKETS=$(aws --region ${region} s3api list-buckets --query "Buckets[].Name" --output text)
  
  # 各バケットに対してマルウェアプロテクションを有効化
  for bucket in ${BUCKETS}; do
    # バケットのリージョンを確認(クロスリージョンのバケットはスキップ)
    BUCKET_REGION=$(aws s3api get-bucket-location --bucket ${bucket} --query "LocationConstraint" --output text)
    # null または空の場合は us-east-1
    if [ "$BUCKET_REGION" = "null" ] || [ -z "$BUCKET_REGION" ]; then
      BUCKET_REGION="us-east-1"
    fi
    
    # バケットが現在のリージョンにある場合のみ処理
    if [ "$BUCKET_REGION" = "$region" ]; then
      echo "Creating malware protection plan for bucket: ${bucket}"
      aws --region ${region} guardduty create-malware-protection-plan \
        --role "${MALWARE_PROTECTION_ROLE_ARN}" \
        --protected-resource "S3Bucket"={"BucketName"="${bucket}"} \
        --actions "Tagging"={"Status"="ENABLED"}
    fi
  done
done

この場合、S3マルウェアプロテクション用のIAMロールはすべてのS3バケットに対して許可する権限を持たせておく必要があります。実際のIAMロールはマネージメントコンソールからS3マルウェアプロテクションを作成した際に自動作成されるIAMロールや公式ドキュメントを参考にして頂ければと思います。

現実的な対応策

S3マルウェアプロテクションに付与するIAMポリシーが複雑であることや、検査対象オブジェクトが増えればその分コストも増えることも考えると、特定のバケットを対象にマネージメントコンソールでS3マルウェアプロテクションをポチポチ作っていくのもアリかなと思ったりしました。もちろんIaC化が進んでいる組織であればIaCで管理するに越したことはないです。

まとめ

S3マルウェアプロテクションを全アカウントで有効化するために検討した内容についての記事でした。この記事がどなたかの参考になれば幸いです。

以上、とーちでした。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.