Repository: aws/aws-cdk
Pull request #34229: feat(s3tables): server-side encryption by customer managed KMS key
Author: login: badmintoncryer
Labels: name: p2
name: distinguished-contributor
description: [Pilot] contributed 50+ PRs to the CDK
name: pr/needs-maintainer-review
description: This PR needs a review from a Core Team Member
Created: 2025-04-23T14:17:08Z | Merged: 2025-05-09T22:55:07Z

### Issue # (if applicable)

None

### Reason for this change

AWS S3 Tables supports for server side encryption by customer managed KMS keys.

https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-encryption.html

And cloudformation have supported for this feature.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3tables-tablebucket-encryptionconfiguration.html

### Description of changes

- Add `kmsKey` prop to `TableBucketProps`
- Add kms key resource policy which enables S3 Tables to execute automatic table maintenance
  - https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html

### Describe any new or updated permissions being added

Add resource policy to the kms key.

```ts
// add resource policy to the encryption key
    // see https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html#tables-kms-maintenance-permissions
    props?.kmsKey?.addToResourcePolicy(
      new iam.PolicyStatement({
        actions: ['kms:Decrypt', 'kms:GenerateDataKey'],
        resources: ['*'],
        effect: iam.Effect.ALLOW,
        principals: [new iam.ServicePrincipal('maintenance.s3tables.amazonaws.com')],
        conditions: {
          StringLike: {
            'kms:EncryptionContext:aws:s3:arn': `${Stack.of(this).formatArn({
              service: 's3tables',
              resource: 'bucket',
              resourceName: props.tableBucketName,
            })}/*`,
          },
        },
      }),
    );
```

### Description of how you validated changes

Add both unit and integ tests.

### Checklist
- [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*



Diff:
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/README.md b/packages/@aws-cdk/aws-s3tables-alpha/README.md
index 2fb4986e9b3cf..42eb4a45323f9 100644
--- a/packages/@aws-cdk/aws-s3tables-alpha/README.md
+++ b/packages/@aws-cdk/aws-s3tables-alpha/README.md
@@ -41,6 +41,22 @@ const sampleTableBucket = new TableBucket(scope, 'ExampleTableBucket', {
 
 Learn more about table buckets maintenance operations and default behavior from the [S3 Tables User Guide](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-table-buckets-maintenance.html)
 
+### Server-side Encryption
+
+By default, S3 Tables buckets are encrypted using Amazon S3-managed keys (SSE-S3). You can also use AWS Key Management Service (AWS KMS) keys to encrypt your data.
+To do this, you can specify the `kmsKey` property when creating the bucket:
+
+```ts
+declare const kmsKey: kms.IKey;
+
+new TableBucket(this, 'TableBucket', {
+  tableBucketName: 'kms-key-s3tables-bucket',
+  kmsKey,
+});
+```
+
+**Note**: AWS CDK automatically add a resource policy to the KMS key to allow the S3 Tables service to use it for automatic table maintenance. Detail information can be found in the [security for S3 tables](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html) documentation.
+
 ### Controlling Table Bucket Permissions
 
 ```ts
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/lib/table-bucket.ts b/packages/@aws-cdk/aws-s3tables-alpha/lib/table-bucket.ts
index 5d35e263a9fa3..6221415aa1bcc 100644
--- a/packages/@aws-cdk/aws-s3tables-alpha/lib/table-bucket.ts
+++ b/packages/@aws-cdk/aws-s3tables-alpha/lib/table-bucket.ts
@@ -5,7 +5,8 @@ import { TableBucketPolicy } from './table-bucket-policy';
 import * as perms from './permissions';
 import { validateTableBucketAttributes } from './util';
 import * as iam from 'aws-cdk-lib/aws-iam';
-import { Resource, IResource, UnscopedValidationError, RemovalPolicy, Token } from 'aws-cdk-lib/core';
+import * as kms from 'aws-cdk-lib/aws-kms';
+import { Resource, IResource, UnscopedValidationError, RemovalPolicy, Token, Stack } from 'aws-cdk-lib/core';
 import { addConstructMetadata } from 'aws-cdk-lib/core/lib/metadata-resource';
 
 /**
@@ -258,6 +259,13 @@ export interface TableBucketProps {
    * @default RETAIN
    */
   readonly removalPolicy?: RemovalPolicy;
+
+  /**
+   * The KMS key to use for server-side encryption.
+   *
+   * @default - Server-side encryption with Amazon S3-managed keys (SSE-S3)
+   */
+  readonly kmsKey?: kms.IKey;
 }
 
 /**
@@ -498,8 +506,34 @@ export class TableBucket extends TableBucketBase {
         noncurrentDays: props.unreferencedFileRemoval?.noncurrentDays,
         unreferencedDays: props.unreferencedFileRemoval?.unreferencedDays,
       },
+      encryptionConfiguration: props?.kmsKey
+        ? {
+          sseAlgorithm: 'aws:kms',
+          kmsKeyArn: props.kmsKey.keyArn,
+        }
+        : undefined,
     });
 
+    // add resource policy to the encryption key
+    // see https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html#tables-kms-maintenance-permissions
+    props?.kmsKey?.addToResourcePolicy(
+      new iam.PolicyStatement({
+        actions: ['kms:Decrypt', 'kms:GenerateDataKey'],
+        resources: ['*'],
+        effect: iam.Effect.ALLOW,
+        principals: [new iam.ServicePrincipal('maintenance.s3tables.amazonaws.com')],
+        conditions: {
+          StringLike: {
+            'kms:EncryptionContext:aws:s3:arn': `${Stack.of(this).formatArn({
+              service: 's3tables',
+              resource: 'bucket',
+              resourceName: props.tableBucketName,
+            })}/*`,
+          },
+        },
+      }),
+    );
+
     this.tableBucketName = this.getResourceNameAttribute(this._resource.ref);
     this.tableBucketArn = this._resource.attrTableBucketArn;
     this._resource.applyRemovalPolicy(props.removalPolicy);
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/rosetta/default.ts-fixture b/packages/@aws-cdk/aws-s3tables-alpha/rosetta/default.ts-fixture
index c6d93a0493b30..7f368d6acafe1 100644
--- a/packages/@aws-cdk/aws-s3tables-alpha/rosetta/default.ts-fixture
+++ b/packages/@aws-cdk/aws-s3tables-alpha/rosetta/default.ts-fixture
@@ -2,6 +2,7 @@ import { Construct } from 'constructs';
 import { Stack } from 'aws-cdk-lib';
 import { TableBucket, UnreferencedFileRemovalStatus } from '@aws-cdk/aws-s3tables-alpha';
 import * as iam from 'aws-cdk-lib/aws-iam';
+import * as kms from 'aws-cdk-lib/aws-kms';
 
 class Fixture extends Stack {
   constructor(scope: Construct, id: string) {
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/cdk.out b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/cdk.out
new file mode 100644
index 0000000000000..188478b55560e
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/cdk.out
@@ -0,0 +1 @@
+{"version":"41.0.0"}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/integ.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/integ.json
new file mode 100644
index 0000000000000..2c9687e9d84f7
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/integ.json
@@ -0,0 +1,12 @@
+{
+  "version": "41.0.0",
+  "testCases": {
+    "kms-key-s3tables-integ/DefaultTest": {
+      "stacks": [
+        "kms-key-s3tables-stack"
+      ],
+      "assertionStack": "kms-key-s3tables-integ/DefaultTest/DeployAssert",
+      "assertionStackName": "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E"
+    }
+  }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.assets.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.assets.json
new file mode 100644
index 0000000000000..560278a3536cb
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.assets.json
@@ -0,0 +1,20 @@
+{
+  "version": "41.0.0",
+  "files": {
+    "468631fb6c24efd89d7b1b6061454e0f86de36d40d52239419e858a45064d1a6": {
+      "displayName": "kms-key-s3tables-stack Template",
+      "source": {
+        "path": "kms-key-s3tables-stack.template.json",
+        "packaging": "file"
+      },
+      "destinations": {
+        "current_account-current_region": {
+          "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+          "objectKey": "468631fb6c24efd89d7b1b6061454e0f86de36d40d52239419e858a45064d1a6.json",
+          "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
+        }
+      }
+    }
+  },
+  "dockerImages": {}
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.template.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.template.json
new file mode 100644
index 0000000000000..ed36584592b59
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.template.json
@@ -0,0 +1,126 @@
+{
+ "Resources": {
+  "KmsKey46693ADD": {
+   "Type": "AWS::KMS::Key",
+   "Properties": {
+    "KeyPolicy": {
+     "Statement": [
+      {
+       "Action": "kms:*",
+       "Effect": "Allow",
+       "Principal": {
+        "AWS": {
+         "Fn::Join": [
+          "",
+          [
+           "arn:",
+           {
+            "Ref": "AWS::Partition"
+           },
+           ":iam::",
+           {
+            "Ref": "AWS::AccountId"
+           },
+           ":root"
+          ]
+         ]
+        }
+       },
+       "Resource": "*"
+      },
+      {
+       "Action": [
+        "kms:Decrypt",
+        "kms:GenerateDataKey"
+       ],
+       "Condition": {
+        "StringLike": {
+         "kms:EncryptionContext:aws:s3:arn": {
+          "Fn::Join": [
+           "",
+           [
+            "arn:",
+            {
+             "Ref": "AWS::Partition"
+            },
+            ":s3tables:",
+            {
+             "Ref": "AWS::Region"
+            },
+            ":",
+            {
+             "Ref": "AWS::AccountId"
+            },
+            ":bucket/kms-key-s3tables-bucket/*"
+           ]
+          ]
+         }
+        }
+       },
+       "Effect": "Allow",
+       "Principal": {
+        "Service": "maintenance.s3tables.amazonaws.com"
+       },
+       "Resource": "*"
+      }
+     ],
+     "Version": "2012-10-17"
+    }
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete"
+  },
+  "TableBucket0BF18B0D": {
+   "Type": "AWS::S3Tables::TableBucket",
+   "Properties": {
+    "EncryptionConfiguration": {
+     "KMSKeyArn": {
+      "Fn::GetAtt": [
+       "KmsKey46693ADD",
+       "Arn"
+      ]
+     },
+     "SSEAlgorithm": "aws:kms"
+    },
+    "TableBucketName": "kms-key-s3tables-bucket",
+    "UnreferencedFileRemoval": {}
+   },
+   "UpdateReplacePolicy": "Delete",
+   "DeletionPolicy": "Delete"
+  }
+ },
+ "Parameters": {
+  "BootstrapVersion": {
+   "Type": "AWS::SSM::Parameter::Value<String>",
+   "Default": "/cdk-bootstrap/hnb659fds/version",
+   "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
+  }
+ },
+ "Rules": {
+  "CheckBootstrapVersion": {
+   "Assertions": [
+    {
+     "Assert": {
+      "Fn::Not": [
+       {
+        "Fn::Contains": [
+         [
+          "1",
+          "2",
+          "3",
+          "4",
+          "5"
+         ],
+         {
+          "Ref": "BootstrapVersion"
+         }
+        ]
+       }
+      ]
+     },
+     "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
+    }
+   ]
+  }
+ }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets.json
new file mode 100644
index 0000000000000..3d342ddabcc26
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets.json
@@ -0,0 +1,20 @@
+{
+  "version": "41.0.0",
+  "files": {
+    "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
+      "displayName": "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E Template",
+      "source": {
+        "path": "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json",
+        "packaging": "file"
+      },
+      "destinations": {
+        "current_account-current_region": {
+          "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
+          "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
+          "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
+        }
+      }
+    }
+  },
+  "dockerImages": {}
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json
new file mode 100644
index 0000000000000..ad9d0fb73d1dd
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json
@@ -0,0 +1,36 @@
+{
+ "Parameters": {
+  "BootstrapVersion": {
+   "Type": "AWS::SSM::Parameter::Value<String>",
+   "Default": "/cdk-bootstrap/hnb659fds/version",
+   "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
+  }
+ },
+ "Rules": {
+  "CheckBootstrapVersion": {
+   "Assertions": [
+    {
+     "Assert": {
+      "Fn::Not": [
+       {
+        "Fn::Contains": [
+         [
+          "1",
+          "2",
+          "3",
+          "4",
+          "5"
+         ],
+         {
+          "Ref": "BootstrapVersion"
+         }
+        ]
+       }
+      ]
+     },
+     "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
+    }
+   ]
+  }
+ }
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/manifest.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/manifest.json
new file mode 100644
index 0000000000000..2c22e4084cf12
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/manifest.json
@@ -0,0 +1,132 @@
+{
+  "version": "42.0.0",
+  "artifacts": {
+    "kms-key-s3tables-stack.assets": {
+      "type": "cdk:asset-manifest",
+      "properties": {
+        "file": "kms-key-s3tables-stack.assets.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+      }
+    },
+    "kms-key-s3tables-stack": {
+      "type": "aws:cloudformation:stack",
+      "environment": "aws://unknown-account/unknown-region",
+      "properties": {
+        "templateFile": "kms-key-s3tables-stack.template.json",
+        "terminationProtection": false,
+        "validateOnSynth": false,
+        "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+        "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+        "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/468631fb6c24efd89d7b1b6061454e0f86de36d40d52239419e858a45064d1a6.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
+        "additionalDependencies": [
+          "kms-key-s3tables-stack.assets"
+        ],
+        "lookupRole": {
+          "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+          "requiresBootstrapStackVersion": 8,
+          "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+        }
+      },
+      "dependencies": [
+        "kms-key-s3tables-stack.assets"
+      ],
+      "metadata": {
+        "/kms-key-s3tables-stack/KmsKey": [
+          {
+            "type": "aws:cdk:analytics:construct",
+            "data": "*"
+          }
+        ],
+        "/kms-key-s3tables-stack/KmsKey/Resource": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "KmsKey46693ADD"
+          }
+        ],
+        "/kms-key-s3tables-stack/TableBucket": [
+          {
+            "type": "aws:cdk:analytics:construct",
+            "data": "*"
+          }
+        ],
+        "/kms-key-s3tables-stack/TableBucket/TableBucket": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "TableBucket0BF18B0D"
+          }
+        ],
+        "/kms-key-s3tables-stack/BootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "BootstrapVersion"
+          }
+        ],
+        "/kms-key-s3tables-stack/CheckBootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "CheckBootstrapVersion"
+          }
+        ]
+      },
+      "displayName": "kms-key-s3tables-stack"
+    },
+    "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets": {
+      "type": "cdk:asset-manifest",
+      "properties": {
+        "file": "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+      }
+    },
+    "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E": {
+      "type": "aws:cloudformation:stack",
+      "environment": "aws://unknown-account/unknown-region",
+      "properties": {
+        "templateFile": "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json",
+        "terminationProtection": false,
+        "validateOnSynth": false,
+        "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
+        "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
+        "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
+        "requiresBootstrapStackVersion": 6,
+        "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
+        "additionalDependencies": [
+          "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets"
+        ],
+        "lookupRole": {
+          "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}",
+          "requiresBootstrapStackVersion": 8,
+          "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version"
+        }
+      },
+      "dependencies": [
+        "kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets"
+      ],
+      "metadata": {
+        "/kms-key-s3tables-integ/DefaultTest/DeployAssert/BootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "BootstrapVersion"
+          }
+        ],
+        "/kms-key-s3tables-integ/DefaultTest/DeployAssert/CheckBootstrapVersion": [
+          {
+            "type": "aws:cdk:logicalId",
+            "data": "CheckBootstrapVersion"
+          }
+        ]
+      },
+      "displayName": "kms-key-s3tables-integ/DefaultTest/DeployAssert"
+    },
+    "Tree": {
+      "type": "cdk:tree",
+      "properties": {
+        "file": "tree.json"
+      }
+    }
+  },
+  "minimumCliVersion": "2.1006.0"
+}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/tree.json b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/tree.json
new file mode 100644
index 0000000000000..34ddaecda7c52
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/tree.json
@@ -0,0 +1 @@
+{"version":"tree-0.1","tree":{"id":"App","path":"","children":{"kms-key-s3tables-stack":{"id":"kms-key-s3tables-stack","path":"kms-key-s3tables-stack","children":{"KmsKey":{"id":"KmsKey","path":"kms-key-s3tables-stack/KmsKey","children":{"Resource":{"id":"Resource","path":"kms-key-s3tables-stack/KmsKey/Resource","attributes":{"aws:cdk:cloudformation:type":"AWS::KMS::Key","aws:cdk:cloudformation:props":{"keyPolicy":{"Statement":[{"Action":"kms:*","Effect":"Allow","Principal":{"AWS":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":iam::",{"Ref":"AWS::AccountId"},":root"]]}},"Resource":"*"},{"Action":["kms:Decrypt","kms:GenerateDataKey"],"Condition":{"StringLike":{"kms:EncryptionContext:aws:s3:arn":{"Fn::Join":["",["arn:",{"Ref":"AWS::Partition"},":s3tables:",{"Ref":"AWS::Region"},":",{"Ref":"AWS::AccountId"},":bucket/kms-key-s3tables-bucket/*"]]}}},"Effect":"Allow","Principal":{"Service":"maintenance.s3tables.amazonaws.com"},"Resource":"*"}],"Version":"2012-10-17"}}},"constructInfo":{"fqn":"aws-cdk-lib.aws_kms.CfnKey","version":"0.0.0"}}},"constructInfo":{"fqn":"aws-cdk-lib.aws_kms.Key","version":"0.0.0","metadata":["*"]}},"TableBucket":{"id":"TableBucket","path":"kms-key-s3tables-stack/TableBucket","children":{"TableBucket":{"id":"TableBucket","path":"kms-key-s3tables-stack/TableBucket/TableBucket","attributes":{"aws:cdk:cloudformation:type":"AWS::S3Tables::TableBucket","aws:cdk:cloudformation:props":{"encryptionConfiguration":{"sseAlgorithm":"aws:kms","kmsKeyArn":{"Fn::GetAtt":["KmsKey46693ADD","Arn"]}},"tableBucketName":"kms-key-s3tables-bucket","unreferencedFileRemoval":{}}},"constructInfo":{"fqn":"aws-cdk-lib.aws_s3tables.CfnTableBucket","version":"0.0.0"}}},"constructInfo":{"fqn":"@aws-cdk/aws-s3tables-alpha.TableBucket","version":"0.0.0","metadata":["*"]}},"BootstrapVersion":{"id":"BootstrapVersion","path":"kms-key-s3tables-stack/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"kms-key-s3tables-stack/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}},"constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"}},"kms-key-s3tables-integ":{"id":"kms-key-s3tables-integ","path":"kms-key-s3tables-integ","children":{"DefaultTest":{"id":"DefaultTest","path":"kms-key-s3tables-integ/DefaultTest","children":{"Default":{"id":"Default","path":"kms-key-s3tables-integ/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"kms-key-s3tables-integ/DefaultTest/DeployAssert","children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"kms-key-s3tables-integ/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"kms-key-s3tables-integ/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}},"constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"}}},"constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"}}},"constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}},"constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"}}}
\ No newline at end of file
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.ts b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.ts
new file mode 100644
index 0000000000000..53fd8ce9367a4
--- /dev/null
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.ts
@@ -0,0 +1,20 @@
+import * as cdk from 'aws-cdk-lib';
+import * as integ from '@aws-cdk/integ-tests-alpha';
+import * as kms from 'aws-cdk-lib/aws-kms';
+import * as s3tables from '../../lib';
+
+const app = new cdk.App();
+const stack = new cdk.Stack(app, 'kms-key-s3tables-stack');
+
+const kmsKey = new kms.Key(stack, 'KmsKey');
+
+new s3tables.TableBucket(stack, 'TableBucket', {
+  tableBucketName: 'kms-key-s3tables-bucket',
+  kmsKey: kmsKey,
+});
+
+cdk.RemovalPolicies.of(stack).destroy();
+
+new integ.IntegTest(app, 'kms-key-s3tables-integ', {
+  testCases: [stack],
+});
diff --git a/packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts b/packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
index 9863accabc4d5..224c7c3f54f6f 100644
--- a/packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
+++ b/packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
@@ -1,5 +1,6 @@
 import { Template } from 'aws-cdk-lib/assertions';
 import * as iam from 'aws-cdk-lib/aws-iam';
+import * as kms from 'aws-cdk-lib/aws-kms';
 import * as core from 'aws-cdk-lib/core';
 import * as s3tables from '../lib';
 
@@ -146,6 +147,94 @@ describe('TableBucket', () => {
     });
   });
 
+  describe('server-side encryption', () => {
+    test('configure customer managed key', () => {
+      const kmsKey = new kms.Key(stack, 'Key');
+
+      new s3tables.TableBucket(stack, 'ExampleTableBucket', {
+        tableBucketName: 'example-table-bucket',
+        kmsKey,
+      });
+
+      Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
+        KeyPolicy: {
+          Statement: [
+            {
+              Action: 'kms:*',
+              Effect: 'Allow',
+              Principal: {
+                AWS: {
+                  'Fn::Join': [
+                    '',
+                    [
+                      'arn:',
+                      {
+                        'Ref': 'AWS::Partition',
+                      },
+                      ':iam::',
+                      {
+                        'Ref': 'AWS::AccountId',
+                      },
+                      ':root',
+                    ],
+                  ],
+                },
+              },
+              Resource: '*',
+            },
+            {
+              Action: [
+                'kms:Decrypt',
+                'kms:GenerateDataKey',
+              ],
+              Condition: {
+                StringLike: {
+                  'kms:EncryptionContext:aws:s3:arn': {
+                    'Fn::Join': [
+                      '',
+                      [
+                        'arn:',
+                        {
+                          'Ref': 'AWS::Partition',
+                        },
+                        ':s3tables:',
+                        {
+                          'Ref': 'AWS::Region',
+                        },
+                        ':',
+                        {
+                          'Ref': 'AWS::AccountId',
+                        },
+                        ':bucket/example-table-bucket/*',
+                      ],
+                    ],
+                  },
+                },
+              },
+              Effect: 'Allow',
+              Principal: {
+                Service: 'maintenance.s3tables.amazonaws.com',
+              },
+              Resource: '*',
+            },
+          ],
+        },
+      });
+
+      Template.fromStack(stack).hasResourceProperties(TABLE_BUCKET_CFN_RESOURCE, {
+        'EncryptionConfiguration': {
+          'KMSKeyArn': {
+            'Fn::GetAtt': [
+              'Key961B73FD',
+              'Arn',
+            ],
+          },
+          'SSEAlgorithm': 'aws:kms',
+        },
+      });
+    });
+  });
+
   describe('import existing table bucket with name', () => {
     const BUCKET_PROPS = {
       tableBucketName: 'example-table-bucket',


Reviews:
user: login: aws-cdk-automation
body: (This review is outdated)
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-04-23T14:18:57Z
user: login: badmintoncryer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T14:25:50Z
user: login: mazyu36
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T01:04:34Z
user: login: QuantumNeuralCoder
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:19:53Z
user: login: badmintoncryer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T14:05:51Z
user: login: QuantumNeuralCoder
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T22:06:03Z
user: login: QuantumNeuralCoder
body: Noting the kms* pattern also caught by security guardian. Approving this. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T22:23:39Z

Review comments:
diff_hunk: @@ -498,8 +506,34 @@ export class TableBucket extends TableBucketBase {
         noncurrentDays: props.unreferencedFileRemoval?.noncurrentDays,
         unreferencedDays: props.unreferencedFileRemoval?.unreferencedDays,
       },
+      encryptionConfiguration: props?.kmsKey
+        ? {
+          sseAlgorithm: 'aws:kms',
+          kmsKeyArn: props.kmsKey.keyArn,
+        }
+        : undefined,
     });
 
+    // add resource policy to the encryption key
+    // see https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html#tables-kms-maintenance-permissions
+    props?.kmsKey?.addToResourcePolicy(
+      new iam.PolicyStatement({
+        actions: ['kms:Decrypt', 'kms:GenerateDataKey'],
+        resources: ['*'],
path: packages/@aws-cdk/aws-s3tables-alpha/lib/table-bucket.ts
user: login: badmintoncryer
body: While [the documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-tables-kms-permissions.html#tables-kms-maintenance-permissions) specifies using the KMS Key ARN as `resource` section, I am using a wildcard (*) for the following reasons:

- Specifying `kmsKey.keyArn` as the `resource` would create a circular reference
- In resource policies, using a wildcard (*) or specifying the key ARN has the same effect
  - ` In a key policy, the value of the Resource element is "*", which means "this KMS key." The asterisk ("*") identifies the KMS key to which the key policy is attached.`
  - https://docs.aws.amazon.com/kms/latest/developerguide/key-policy-overview.html#key-policy-elements
created_at: 2025-04-23T14:25:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,6 +147,94 @@ describe('TableBucket', () => {
     });
   });
 
+  describe('server-side encryption', () => {
+    test('configure customer managed key', () => {
+      const kmsKey = new kms.Key(stack, 'Key');
+
+      new s3tables.TableBucket(stack, 'ExampleTableBucket', {
+        tableBucketName: 'example-table-bucket',
+        kmsKey,
+      });
+
+      Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
path: packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
user: login: QuantumNeuralCoder
body: Can you run the security-guardian tool locally? I am sure it will complain :). Refer [README](https://github.com/aws/aws-cdk/tree/main/tools/%40aws-cdk/security-guardian).
created_at: 2025-05-05T17:19:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,6 +147,94 @@ describe('TableBucket', () => {
     });
   });
 
+  describe('server-side encryption', () => {
+    test('configure customer managed key', () => {
+      const kmsKey = new kms.Key(stack, 'Key');
+
+      new s3tables.TableBucket(stack, 'ExampleTableBucket', {
+        tableBucketName: 'example-table-bucket',
+        kmsKey,
+      });
+
+      Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
path: packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
user: login: badmintoncryer
body: This is the execution result. Is it OK?

```sh
❯ yarn security-guardian
yarn run v1.22.21
$ ts-node src/index.ts --rule_set_path=./rules
Created working directory: ./changed_templates
Detecting changed .template.json files from origin/main to HEAD
[command]/opt/homebrew/bin/git diff --name-status origin/main HEAD
M       packages/@aws-cdk/aws-s3tables-alpha/README.md
M       packages/@aws-cdk/aws-s3tables-alpha/lib/table-bucket.ts
M       packages/@aws-cdk/aws-s3tables-alpha/rosetta/default.ts-fixture
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/cdk.out
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/integ.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.assets.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.template.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.assets.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/manifest.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/tree.json
A       packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.ts
M       packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
[command]/opt/homebrew/bin/git rev-parse --show-toplevel
/Users/kazuhoshinozuka/git/aws-cdk
fullpath: /Users/kazuhoshinozuka/git/aws-cdk/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.template.json
Copied: packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kms-key-s3tables-stack.template.json
[command]/opt/homebrew/bin/git rev-parse --show-toplevel
/Users/kazuhoshinozuka/git/aws-cdk
fullpath: /Users/kazuhoshinozuka/git/aws-cdk/packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json
Copied: packages/@aws-cdk/aws-s3tables-alpha/test/integration/integ.table-bucket-kms-key.js.snapshot/kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json
Running cfn-guard with rule set: ./rules
[command]/opt/homebrew/bin/cfn-guard validate --data ./changed_templates --rules ./rules --show-summary fail --output-format json
{
  "name": "/Users/kazuhoshinozuka/git/aws-cdk/tools/@aws-cdk/security-guardian/changed_templates/packages_@aws-cdk_aws-s3tables-alpha_test_integration_integ.table-bucket-kms-key.js.snapshot_kms-key-s3tables-stack.template.json",
  "metadata": {},
  "status": "SKIP",
  "not_compliant": [],
  "not_applicable": [
    "IAM_ROLE_NO_BROAD_PRINCIPALS"
  ],
  "compliant": []
}{
  "name": "/Users/kazuhoshinozuka/git/aws-cdk/tools/@aws-cdk/security-guardian/changed_templates/packages_@aws-cdk_aws-s3tables-alpha_test_integration_integ.table-bucket-kms-key.js.snapshot_kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json",
  "metadata": {},
  "status": "SKIP",
  "not_compliant": [],
  "not_applicable": [
    "IAM_ROLE_NO_BROAD_PRINCIPALS"
  ],
  "compliant": []
}Running scanner for intrinsics
Scanning JSON files in: ./changed_templates
Processing: changed_templates/packages_@aws-cdk_aws-s3tables-alpha_test_integration_integ.table-bucket-kms-key.js.snapshot_kms-key-s3tables-stack.template.json
Match found in: changed_templates/packages_@aws-cdk_aws-s3tables-alpha_test_integration_integ.table-bucket-kms-key.js.snapshot_kms-key-s3tables-stack.template.json (statements: 1)
Processing: changed_templates/packages_@aws-cdk_aws-s3tables-alpha_test_integration_integ.table-bucket-kms-key.js.snapshot_kmskeys3tablesintegDefaultTestDeployAssertC8AB8C4E.template.json
detailed_output File: changed_templates/packages_@aws-cdk_aws-s3tables-alpha_test_integration_integ.table-bucket-kms-key.js.snapshot_kms-key-s3tables-stack.template.json
{
  "Action": "kms:*",
  "Effect": "Allow",
  "Principal": {
    "AWS": {
      "Fn::Join": [
        "",
        [
          "arn:",
          {
            "Ref": "AWS::Partition"
          },
          ":iam::",
          {
            "Ref": "AWS::AccountId"
          },
          ":root"
        ]
      ]
    }
  },
  "Resource": "*"
} |n| ============================================================

 Scan complete!
 Files scanned : 2
 Matches found : 1
::warning::Intrinsic scan found 1 issue(s).

::set-output name=issues_found::1
::error::Action completed with issues: Intrinsic scan found 1 issue(s).
Removed working directory: ./changed_templates
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
```
created_at: 2025-05-06T14:05:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -146,6 +147,94 @@ describe('TableBucket', () => {
     });
   });
 
+  describe('server-side encryption', () => {
+    test('configure customer managed key', () => {
+      const kmsKey = new kms.Key(stack, 'Key');
+
+      new s3tables.TableBucket(stack, 'ExampleTableBucket', {
+        tableBucketName: 'example-table-bucket',
+        kmsKey,
+      });
+
+      Template.fromStack(stack).hasResourceProperties('AWS::KMS::Key', {
path: packages/@aws-cdk/aws-s3tables-alpha/test/table-bucket.test.ts
user: login: QuantumNeuralCoder
body: Yes. The results are fine. 
created_at: 2025-05-09T22:06:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: mergify[bot]
created_at: 2025-05-09T22:24:15Z
body: Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)).
author_association: CONTRIBUTOR
user: login: aws-cdk-automation
created_at: 2025-05-09T22:54:56Z
body: 
### AWS CodeBuild CI Report

* CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
* Commit ID: 882f81ba75677fe9aaae922b58b08dc9b7c96d48
* Result: SUCCEEDED
* [Build Logs](https://2cl995f30a.execute-api.us-east-1.amazonaws.com/Prod/buildlogs?key=2168c12c-9420-47bc-a429-1fe9e4ca9350%2Fbuild.log) (available for 30 days)

*Powered by [github-codebuild-logs](https://serverlessrepo.aws.amazon.com/applications/arn:aws:serverlessrepo:us-east-1:277187709615:applications~github-codebuild-logs), available on the [AWS Serverless Application Repository](https://aws.amazon.com/serverless/serverlessrepo/)*


<!--
CREATED BY GITHUB-CODEBUILD-LOGS
-->


author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-05-09T22:55:09Z
body: Thank you for contributing! Your pull request will be updated from main and then merged automatically (do not update manually, and be sure to [allow changes to be pushed to your fork](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork)).
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-05-09T22:55:19Z
body: Comments on closed issues and PRs are hard for our team to see. 
If you need help, please open a new issue that references this one.
author_association: CONTRIBUTOR
user: login: godwingrs22
created_at: 2025-05-14T20:23:48Z
body: Hi @badmintoncryer, 

First of all, thank you for taking the time to contribute to this feature. We truly value community contributions and the effort you put into implementing this feature.

I need to revert this PR as there was a parallel implementation #34281 being developed by the service team that includes some additional internal considerations. I apologize for not catching this overlap earlier in the review process.

Thanks for your understanding!

author_association: CONTRIBUTOR