AWS CDKを使ってS3バケットをGuardDutyで保護する

Amazon S3バケットは、AWS環境でのデータストレージの基本となるリソースです。しかし、S3バケットが適切に保護されていないと、データ漏洩や不正アクセスのリスクが高まります。そこで、AWSの脅威検出サービスであるGuardDutyを活用し、S3バケットを保護できる。

### AWS CDKプロジェクトのセットアップ

AWS CDKプロジェクトを初期化します。プロジェクト名は guardduty-s3 とします。

```sh
mkdir guardduty-s3
cd guardduty-s3
cdk init app --language typescript
```

### S3バケットの作成とGuardDutyの有効化

バケット作成

```typescript
    const bucket = new s3.Bucket(scope, `${id}-s3`, {
      versioned: true, // バージョニングの有効化
      removalPolicy: cdk.RemovalPolicy.DESTROY, // スタック削除時にバケットも削除される
    });
```  

role作成
```typescript
// GuardDutyRole
    const guardDutyRole = new iam.Role(scope, detectorRole, {
      assumedBy: new iam.ServicePrincipal(
        "malware-protection-plan.guardduty.amazonaws.com"
      ),
    });

    // AllowManagedRuleToSendS3EventsToGuardDuty
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          "events:PutRule",
          "events:DeleteRule",
          "events:PutTargets",
          "events:RemoveTargets",
        ],
        resources: [
          `arn:aws:events:${this.region}:${this.account}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*`,
        ],
        conditions: {
          StringLike: {
            "events:ManagedBy":
              "malware-protection-plan.guardduty.amazonaws.com",
          },
        },
      })
    );

    // AllowGuardDutyToMonitorEventBridgeManagedRule
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["events:DescribeRule", "events:ListTargetsByRule"],
        resources: [
          `arn:aws:events:${this.region}:${this.account}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*`,
        ],
      })
    );

    // AllowPostScanTagging
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          "s3:PutObjectTagging",
          "s3:GetObjectTagging",
          "s3:PutObjectVersionTagging",
          "s3:GetObjectVersionTagging",
        ],
        resources: [`arn:aws:s3:::${bucketName}/*`],
      })
    );

    // AllowEnableS3EventBridgeEvents
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:PutBucketNotification", "s3:GetBucketNotification"],
        resources: [`arn:aws:s3:::${bucketName}`],
      })
    );

    // AllowPutValidationObject
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:PutObject"],
        resources: [
          `arn:aws:s3:::${bucketName}/malware-protection-resource-validation-object`,
        ],
      })
    );

    // allow list objects
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:ListBucket"],
        resources: [`arn:aws:s3:::${bucketName}`],
      })
    );

    // AllowMalwareScan
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:GetObject", "s3:GetObjectVersion"],
        resources: [`arn:aws:s3:::${bucketName}/*`],
      })
    );

    // AllowDecryptForMalwareScan
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["kms:GenerateDataKey", "kms:Decrypt"],
        resources: [`arn:aws:kms:${this.region}:${this.account}:key/<key_id>`],
        conditions: {
          StringLike: { "kms:ViaService": "s3.*.amazonaws.com" },
        },
      })
    );
```  

malware protection plan 作成

```typescript
    const guardDutyDetector = new guardduty.CfnMalwareProtectionPlan(
      scope,
      detectorId,
      {
        protectedResource: {
          s3Bucket: {
            bucketName: bucketName, // bucketの名前
            objectPrefixes: ["your_prefix/"], // {bucket_name}:/your_prefix/.....
          },
        },
        role: guardDutyRole.roleArn, // roleのARN
        actions: {
          tagging: {
            status: "ENABLED", // enable tagging
          },
        },
      }
    );
```

stackコード(guardduty-s3-stack.ts)

```typescript
import * as cdk from "aws-cdk-lib";
import * as guardduty from "aws-cdk-lib/aws-guardduty";
import * as iam from "aws-cdk-lib/aws-iam";
import * as s3 from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";

export class GuarddutyS3Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const bucket = this.createS3Bucket(this, `${id}-s3`);
    new cdk.CfnOutput(this, `BucketName`, {
      value: bucket.bucketName,
    });

    // roleの作成
    const guardDutyRole = this.createGuardDutyRole(
      this,
      `${id}-role`,
      bucket.bucketName
    );

    // GuardDuty Detectorの作成
    this.createMalwareProtectionPlan(
      this,
      `${id}-detector`,
      bucket.bucketName,
      guardDutyRole
    );
  }

  protected createMalwareProtectionPlan(
    scope: Construct,
    detectorId: string,
    bucketName: string,
    guardDutyRole: iam.Role
  ): guardduty.CfnMalwareProtectionPlan {
    // GuardDuty Detectorの作成
    const guardDutyDetector = new guardduty.CfnMalwareProtectionPlan(
      scope,
      detectorId,
      {
        protectedResource: {
          s3Bucket: {
            bucketName: bucketName, // bucketの名前
            objectPrefixes: ["your_prefix/"], // {bucket_name}:/your_prefix/.....
          },
        },
        role: guardDutyRole.roleArn, // roleのARN
        actions: {
          tagging: {
            status: "ENABLED", // enable tagging
          },
        },
      }
    );
    return guardDutyDetector;
  }

  protected createS3Bucket(scope: Construct, id: string): s3.Bucket {
    // S3バケットの作成
    const bucket = new s3.Bucket(scope, `${id}-s3`, {
      versioned: true, // バージョニングの有効化
      removalPolicy: cdk.RemovalPolicy.DESTROY, // スタック削除時にバケットも削除される
    });

    return bucket;
  }

  protected createGuardDutyRole(
    scope: Construct,
    detectorRole: string,
    bucketName: string
  ): iam.Role {
    // GuardDutyRole
    const guardDutyRole = new iam.Role(scope, detectorRole, {
      assumedBy: new iam.ServicePrincipal(
        "malware-protection-plan.guardduty.amazonaws.com"
      ),
    });

    // AllowManagedRuleToSendS3EventsToGuardDuty
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          "events:PutRule",
          "events:DeleteRule",
          "events:PutTargets",
          "events:RemoveTargets",
        ],
        resources: [
          `arn:aws:events:${this.region}:${this.account}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*`,
        ],
        conditions: {
          StringLike: {
            "events:ManagedBy":
              "malware-protection-plan.guardduty.amazonaws.com",
          },
        },
      })
    );

    // AllowGuardDutyToMonitorEventBridgeManagedRule
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["events:DescribeRule", "events:ListTargetsByRule"],
        resources: [
          `arn:aws:events:${this.region}:${this.account}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*`,
        ],
      })
    );

    // AllowPostScanTagging
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: [
          "s3:PutObjectTagging",
          "s3:GetObjectTagging",
          "s3:PutObjectVersionTagging",
          "s3:GetObjectVersionTagging",
        ],
        resources: [`arn:aws:s3:::${bucketName}/*`],
      })
    );

    // AllowEnableS3EventBridgeEvents
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:PutBucketNotification", "s3:GetBucketNotification"],
        resources: [`arn:aws:s3:::${bucketName}`],
      })
    );

    // AllowPutValidationObject
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:PutObject"],
        resources: [
          `arn:aws:s3:::${bucketName}/malware-protection-resource-validation-object`,
        ],
      })
    );

    // allow list objects
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:ListBucket"],
        resources: [`arn:aws:s3:::${bucketName}`],
      })
    );

    // AllowMalwareScan
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["s3:GetObject", "s3:GetObjectVersion"],
        resources: [`arn:aws:s3:::${bucketName}/*`],
      })
    );

    // AllowDecryptForMalwareScan
    guardDutyRole.addToPolicy(
      new iam.PolicyStatement({
        actions: ["kms:GenerateDataKey", "kms:Decrypt"],
        resources: [`arn:aws:kms:${this.region}:${this.account}:key/<key_id>`],
        conditions: {
          StringLike: { "kms:ViaService": "s3.*.amazonaws.com" },
        },
      })
    );

    return guardDutyRole;
  }
}


```

### スタックのデプロイ

スタックをデプロイするには、以下のコマンドを実行します。
```shell

cdk bootstrap
cdk deploy
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155256/4c9c1f0c-0be5-6c45-c90a-4b7c0b9cf038.png)


AWS環境内のS3バケットをGuardDutyで保護し、不正アクセスや脅威を検出する準備が整いました。