diff --git a/README.md b/README.md index ea2c8dc0d..ceb930ee7 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre | Generate V4 Signed Policy | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/generateV4SignedPolicy.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/generateV4SignedPolicy.js,samples/README.md) | | Generate V4 Upload Signed Url | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/generateV4UploadSignedUrl.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/generateV4UploadSignedUrl.js,samples/README.md) | | Get Autoclass | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getAutoclass.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/getAutoclass.js,samples/README.md) | +| Get Bucket Encryption Enforcement | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getBucketEncryptionEnforcementConfig.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/getBucketEncryptionEnforcementConfig.js,samples/README.md) | | Get Default Event Based Hold | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getDefaultEventBasedHold.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/getDefaultEventBasedHold.js,samples/README.md) | | Get Metadata | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getMetadata.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/getMetadata.js,samples/README.md) | | Get Metadata Notifications | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getMetadataNotifications.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/getMetadataNotifications.js,samples/README.md) | @@ -193,6 +194,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre | Quickstart | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/quickstart.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) | | Release Event Based Hold | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/releaseEventBasedHold.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/releaseEventBasedHold.js,samples/README.md) | | Release Temporary Hold | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/releaseTemporaryHold.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/releaseTemporaryHold.js,samples/README.md) | +| Remove All Bucket Encryption Enforcement | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/removeAllBucketEncryptionEnforcementConfig.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/removeAllBucketEncryptionEnforcementConfig.js,samples/README.md) | | Remove Bucket Conditional Binding | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/removeBucketConditionalBinding.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/removeBucketConditionalBinding.js,samples/README.md) | | Storage Remove Bucket Cors Configuration. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/removeBucketCors.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/removeBucketCors.js,samples/README.md) | | Remove Bucket Default Owner | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/removeBucketDefaultOwner.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/removeBucketDefaultOwner.js,samples/README.md) | @@ -207,6 +209,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre | Restore Soft Deleted Object | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/restoreSoftDeletedObject.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/restoreSoftDeletedObject.js,samples/README.md) | | Rotate Encryption Key | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/rotateEncryptionKey.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/rotateEncryptionKey.js,samples/README.md) | | Set Autoclass | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setAutoclass.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/setAutoclass.js,samples/README.md) | +| Set Bucket Encryption Enforcement | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setBucketEncryptionEnforcementConfig.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/setBucketEncryptionEnforcementConfig.js,samples/README.md) | | Set Client Endpoint | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setClientEndpoint.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/setClientEndpoint.js,samples/README.md) | | Set Event Based Hold | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setEventBasedHold.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/setEventBasedHold.js,samples/README.md) | | Set the object retention policy of a File. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setObjectRetentionPolicy.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/setObjectRetentionPolicy.js,samples/README.md) | diff --git a/samples/README.md b/samples/README.md index 04f33426f..5b71d0c97 100644 --- a/samples/README.md +++ b/samples/README.md @@ -73,6 +73,7 @@ objects to users via direct download. * [Generate V4 Signed Policy](#generate-v4-signed-policy) * [Generate V4 Upload Signed Url](#generate-v4-upload-signed-url) * [Get Autoclass](#get-autoclass) + * [Get Bucket Encryption Enforcement](#get-bucket-encryption-enforcement) * [Get Default Event Based Hold](#get-default-event-based-hold) * [Get Metadata](#get-metadata) * [Get Metadata Notifications](#get-metadata-notifications) @@ -112,6 +113,7 @@ objects to users via direct download. * [Quickstart](#quickstart) * [Release Event Based Hold](#release-event-based-hold) * [Release Temporary Hold](#release-temporary-hold) + * [Remove All Bucket Encryption Enforcement](#remove-all-bucket-encryption-enforcement) * [Remove Bucket Conditional Binding](#remove-bucket-conditional-binding) * [Storage Remove Bucket Cors Configuration.](#storage-remove-bucket-cors-configuration.) * [Remove Bucket Default Owner](#remove-bucket-default-owner) @@ -126,6 +128,7 @@ objects to users via direct download. * [Restore Soft Deleted Object](#restore-soft-deleted-object) * [Rotate Encryption Key](#rotate-encryption-key) * [Set Autoclass](#set-autoclass) + * [Set Bucket Encryption Enforcement](#set-bucket-encryption-enforcement) * [Set Client Endpoint](#set-client-endpoint) * [Set Event Based Hold](#set-event-based-hold) * [Set the object retention policy of a File.](#set-the-object-retention-policy-of-a-file.) @@ -1142,6 +1145,25 @@ __Usage:__ +### Get Bucket Encryption Enforcement + +Retrieves the current encryption enforcement configurations for a bucket. + +View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getBucketEncryptionEnforcementConfig.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/getBucketEncryptionEnforcementConfig.js,samples/README.md) + +__Usage:__ + + +`node getBucketEncryptionEnforcementConfig.js ` + + +----- + + + + ### Get Default Event Based Hold View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/getDefaultEventBasedHold.js). @@ -1823,6 +1845,25 @@ __Usage:__ +### Remove All Bucket Encryption Enforcement + +Removes all encryption enforcement configurations and resets to default behavior. + +View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/removeAllBucketEncryptionEnforcementConfig.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/removeAllBucketEncryptionEnforcementConfig.js,samples/README.md) + +__Usage:__ + + +`node removeAllBucketEncryptionEnforcementConfig.js ` + + +----- + + + + ### Remove Bucket Conditional Binding View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/removeBucketConditionalBinding.js). @@ -2067,6 +2108,25 @@ __Usage:__ +### Set Bucket Encryption Enforcement + +Configures a bucket to enforce specific encryption types (e.g., CMEK-only). + +View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setBucketEncryptionEnforcementConfig.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/setBucketEncryptionEnforcementConfig.js,samples/README.md) + +__Usage:__ + + +`node setBucketEncryptionEnforcementConfig.js ` + + +----- + + + + ### Set Client Endpoint View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/setClientEndpoint.js). diff --git a/samples/getBucketEncryptionEnforcementConfig.js b/samples/getBucketEncryptionEnforcementConfig.js new file mode 100644 index 000000000..aa791ab1b --- /dev/null +++ b/samples/getBucketEncryptionEnforcementConfig.js @@ -0,0 +1,76 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Get Bucket Encryption Enforcement +// description: Retrieves the current encryption enforcement configurations for a bucket. +// usage: node getBucketEncryptionEnforcementConfig.js + +function main(bucketName = 'my-bucket') { + // [START storage_get_encryption_enforcement_config] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // The ID of your GCS bucket + // const bucketName = 'your-unique-bucket-name'; + + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + async function getBucketEncryptionEnforcementConfig() { + const [metadata] = await storage.bucket(bucketName).getMetadata(); + + console.log( + `Encryption enforcement configuration for bucket ${bucketName}.` + ); + const enc = metadata.encryption; + if (!enc) { + console.log( + 'No encryption configuration found (Default GMEK is active).' + ); + return; + } + console.log(`Default KMS Key: ${enc.defaultKmsKeyName || 'None'}`); + + const printConfig = (label, config) => { + if (config) { + console.log(`${label}:`); + console.log(` Mode: ${config.restrictionMode}`); + console.log(` Effective: ${config.effectiveTime}`); + } + }; + + printConfig( + 'Google Managed (GMEK) Enforcement', + enc.googleManagedEncryptionEnforcementConfig + ); + printConfig( + 'Customer Managed (CMEK) Enforcement', + enc.customerManagedEncryptionEnforcementConfig + ); + printConfig( + 'Customer Supplied (CSEK) Enforcement', + enc.customerSuppliedEncryptionEnforcementConfig + ); + } + + getBucketEncryptionEnforcementConfig().catch(console.error); + // [END storage_get_encryption_enforcement_config] +} +main(...process.argv.slice(2)); diff --git a/samples/removeAllBucketEncryptionEnforcementConfig.js b/samples/removeAllBucketEncryptionEnforcementConfig.js new file mode 100644 index 000000000..5cf508ae8 --- /dev/null +++ b/samples/removeAllBucketEncryptionEnforcementConfig.js @@ -0,0 +1,58 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Remove All Bucket Encryption Enforcement +// description: Removes all encryption enforcement configurations and resets to default behavior. +// usage: node removeAllBucketEncryptionEnforcementConfig.js + +function main(bucketName = 'my-bucket') { + // [START storage_remove_all_encryption_enforcement_config] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // The ID of your GCS bucket + // const bucketName = 'your-unique-bucket-name'; + + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + // Setting these to null explicitly removes the enforcement policy. + // We also include defaultKmsKeyName: null to fully reset the bucket encryption state. + async function removeAllBucketEncryptionEnforcementConfig() { + const options = { + encryption: { + defaultKmsKeyName: null, + googleManagedEncryptionEnforcementConfig: null, + customerSuppliedEncryptionEnforcementConfig: null, + customerManagedEncryptionEnforcementConfig: null, + }, + }; + + await storage.bucket(bucketName).setMetadata(options); + + console.log( + `Encryption enforcement configuration removed from bucket ${bucketName}.` + ); + } + + removeAllBucketEncryptionEnforcementConfig().catch(console.error); + // [END storage_remove_all_encryption_enforcement_config] +} +main(...process.argv.slice(2)); diff --git a/samples/setBucketEncryptionEnforcementConfig.js b/samples/setBucketEncryptionEnforcementConfig.js new file mode 100644 index 000000000..6aa9bb49f --- /dev/null +++ b/samples/setBucketEncryptionEnforcementConfig.js @@ -0,0 +1,93 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// sample-metadata: +// title: Set Bucket Encryption Enforcement +// description: Configures a bucket to enforce specific encryption types (e.g., CMEK-only). +// usage: node setBucketEncryptionEnforcementConfig.js + +function main( + bucketName = 'my-bucket', + defaultKmsKeyName = process.env.GOOGLE_CLOUD_KMS_KEY_ASIA +) { + // [START storage_set_encryption_enforcement_config] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // The ID of your GCS bucket + // const bucketName = 'your-unique-bucket-name'; + + // The name of the KMS key to be used as the default + // const defaultKmsKeyName = 'my-key'; + + // Imports the Google Cloud client library + const {Storage} = require('@google-cloud/storage'); + + // Creates a client + const storage = new Storage(); + + async function setBucketEncryptionEnforcementConfig() { + const options = { + encryption: { + defaultKmsKeyName: defaultKmsKeyName, + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + customerSuppliedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + customerManagedEncryptionEnforcementConfig: { + restrictionMode: 'NotRestricted', + }, + }, + }; + + const [metadata] = await storage.bucket(bucketName).setMetadata(options); + + console.log( + `Encryption enforcement configuration updated for bucket ${bucketName}.` + ); + const enc = metadata.encryption; + if (enc) { + console.log(`Default KMS Key: ${enc.defaultKmsKeyName}`); + + const logEnforcement = (label, config) => { + if (config) { + console.log(`${label}:`); + console.log(` Mode: ${config.restrictionMode}`); + console.log(` Effective: ${config.effectiveTime}`); + } + }; + + logEnforcement( + 'Google Managed (GMEK) Enforcement', + enc.googleManagedEncryptionEnforcementConfig + ); + logEnforcement( + 'Customer Managed (CMEK) Enforcement', + enc.customerManagedEncryptionEnforcementConfig + ); + logEnforcement( + 'Customer Supplied (CSEK) Enforcement', + enc.customerSuppliedEncryptionEnforcementConfig + ); + } + } + + setBucketEncryptionEnforcementConfig().catch(console.error); + // [END storage_set_encryption_enforcement_config] +} +main(...process.argv.slice(2)); diff --git a/samples/system-test/buckets.test.js b/samples/system-test/buckets.test.js index 4e2a03ebe..9b8975e6a 100644 --- a/samples/system-test/buckets.test.js +++ b/samples/system-test/buckets.test.js @@ -129,6 +129,65 @@ it('should remove a buckets default KMS key', async () => { assert.ok(!metadata.encryption); }); +it('should set bucket encryption enforcement configuration', async () => { + const output = execSync( + `node setBucketEncryptionEnforcementConfig.js ${bucketName} ${defaultKmsKeyName}` + ); + + assert.include( + output, + `Encryption enforcement configuration updated for bucket ${bucketName}.` + ); + + assert.include(output, `Default KMS Key: ${defaultKmsKeyName}`); + + assert.include(output, 'Google Managed (GMEK) Enforcement:'); + assert.include(output, 'Mode: FullyRestricted'); + + assert.include(output, 'Customer Managed (CMEK) Enforcement:'); + assert.include(output, 'Mode: NotRestricted'); + + assert.include(output, 'Customer Supplied (CSEK) Enforcement:'); + assert.include(output, 'Mode: FullyRestricted'); + + assert.match(output, new RegExp('Effective:')); + + const [metadata] = await bucket.getMetadata(); + assert.strictEqual( + metadata.encryption.googleManagedEncryptionEnforcementConfig + .restrictionMode, + 'FullyRestricted' + ); +}); + +it('should get bucket encryption enforcement configuration', async () => { + const output = execSync( + `node getBucketEncryptionEnforcementConfig.js ${bucketName}` + ); + + assert.include( + output, + `Encryption enforcement configuration for bucket ${bucketName}.` + ); + assert.include(output, `Default KMS Key: ${defaultKmsKeyName}`); + + assert.include(output, 'Google Managed (GMEK) Enforcement:'); + assert.include(output, 'Mode: FullyRestricted'); + assert.match(output, /Effective:/); +}); + +it('should remove all bucket encryption enforcement configuration', async () => { + const output = execSync( + `node removeAllBucketEncryptionEnforcementConfig.js ${bucketName}` + ); + assert.include( + output, + `Encryption enforcement configuration removed from bucket ${bucketName}` + ); + await bucket.getMetadata(); + assert.ok(!bucket.metadata.encryption); +}); + it("should enable a bucket's uniform bucket-level access", async () => { const output = execSync( `node enableUniformBucketLevelAccess.js ${bucketName}` diff --git a/src/bucket.ts b/src/bucket.ts index d35420905..c691d8b9f 100644 --- a/src/bucket.ts +++ b/src/bucket.ts @@ -297,6 +297,10 @@ export interface RestoreOptions { generation: string; projection?: 'full' | 'noAcl'; } +export interface EncryptionEnforcementConfig { + restrictionMode?: 'NotRestricted' | 'FullyRestricted'; + readonly effectiveTime?: string; +} export interface BucketMetadata extends BaseMetadata { acl?: AclMetadata[] | null; autoclass?: { @@ -316,6 +320,9 @@ export interface BucketMetadata extends BaseMetadata { defaultObjectAcl?: AclMetadata[]; encryption?: { defaultKmsKeyName?: string; + googleManagedEncryptionEnforcementConfig?: EncryptionEnforcementConfig; + customerManagedEncryptionEnforcementConfig?: EncryptionEnforcementConfig; + customerSuppliedEncryptionEnforcementConfig?: EncryptionEnforcementConfig; } | null; hierarchicalNamespace?: { enabled?: boolean; @@ -1193,6 +1200,25 @@ class Bucket extends ServiceObject { * }, function(err, apiResponse) {}); * * //- + * // Enforce CMEK-only encryption for new objects. + * // This blocks Google-Managed and Customer-Supplied keys. + * //- + * bucket.setMetadata({ + * encryption: { + * defaultKmsKeyName: 'projects/grape-spaceship-123/...', + * googleManagedEncryptionEnforcementConfig: { + * restrictionMode: 'FullyRestricted' + * }, + * customerSuppliedEncryptionEnforcementConfig: { + * restrictionMode: 'FullyRestricted' + * }, + * customerManagedEncryptionEnforcementConfig: { + * restrictionMode: 'NotRestricted' + * } + * } + * }, function(err, apiResponse) {}); + * + * //- * // Set the default event-based hold value for new objects in this * // bucket. * //- diff --git a/system-test/storage.ts b/system-test/storage.ts index 15257fb59..8c8939e0e 100644 --- a/system-test/storage.ts +++ b/system-test/storage.ts @@ -2992,6 +2992,89 @@ describe('storage', function () { `${metadata!.encryption!.defaultKmsKeyName}/cryptoKeyVersions/1` ); }); + + describe('encryption enforcement', () => { + it('should enforce FullyRestricted CSEK policy', async () => { + await bucket.setMetadata({ + encryption: { + defaultKmsKeyName: kmsKeyName, + customerSuppliedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + }, + }); + + await new Promise(res => + setTimeout(res, BUCKET_METADATA_UPDATE_WAIT_TIME) + ); + + const encryptionKey = crypto.randomBytes(32); + const file = bucket.file('csek-attempt', {encryptionKey}); + + await assert.rejects( + file.save(FILE_CONTENTS, {resumable: false}), + (err: ApiError) => { + const failureMessage = + "Requested encryption type for object is not compliant with the bucket's encryption enforcement configuration."; + assert.strictEqual(err.code, 412); + assert.ok(err.message.includes(failureMessage)); + return true; + } + ); + }); + + it('should allow uploads that comply with enforcement', async () => { + await bucket.setMetadata({ + encryption: { + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'NotRestricted', + }, + }, + }); + + const file = bucket.file('compliant-file'); + await file.save(FILE_CONTENTS); + + const [metadata] = await file.getMetadata(); + assert.ok(metadata.kmsKeyName || metadata.customerEncryption); + }); + + it('should retain defaultKmsKeyName when updating enforcement settings independently', async () => { + await bucket.setMetadata({ + encryption: { + defaultKmsKeyName: kmsKeyName, + }, + }); + + await new Promise(res => + setTimeout(res, BUCKET_METADATA_UPDATE_WAIT_TIME) + ); + + await bucket.setMetadata({ + encryption: { + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + }, + }); + + await new Promise(res => + setTimeout(res, BUCKET_METADATA_UPDATE_WAIT_TIME) + ); + + const [metadata] = await bucket.getMetadata(); + assert.strictEqual( + metadata.encryption?.defaultKmsKeyName, + kmsKeyName + ); + + assert.strictEqual( + metadata.encryption?.googleManagedEncryptionEnforcementConfig + ?.restrictionMode, + 'FullyRestricted' + ); + }); + }); }); }); diff --git a/test/bucket.ts b/test/bucket.ts index 5b49fa518..ec81a25a3 100644 --- a/test/bucket.ts +++ b/test/bucket.ts @@ -3300,4 +3300,145 @@ describe('Bucket', () => { done(); }); }); + + describe('setMetadata', () => { + describe('encryption enforcement', () => { + it('should correctly format restrictionMode for all enforcement types', async () => { + const effectiveTime = '2026-02-02T12:00:00Z'; + const encryptionMetadata = { + encryption: { + defaultKmsKeyName: 'kms-key-name', + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + effectiveTime: effectiveTime, + }, + customerManagedEncryptionEnforcementConfig: { + restrictionMode: 'NotRestricted', + effectiveTime: effectiveTime, + }, + customerSuppliedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + effectiveTime: effectiveTime, + }, + }, + }; + + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual( + metadata.encryption?.defaultKmsKeyName, + encryptionMetadata.encryption.defaultKmsKeyName + ); + + assert.deepStrictEqual( + metadata.encryption?.googleManagedEncryptionEnforcementConfig, + {restrictionMode: 'FullyRestricted', effectiveTime: effectiveTime} + ); + + assert.deepStrictEqual( + metadata.encryption?.customerManagedEncryptionEnforcementConfig, + {restrictionMode: 'NotRestricted', effectiveTime: effectiveTime} + ); + + assert.deepStrictEqual( + metadata.encryption?.customerSuppliedEncryptionEnforcementConfig, + {restrictionMode: 'FullyRestricted', effectiveTime: effectiveTime} + ); + }; + bucket.setMetadata(encryptionMetadata, assert.ifError); + }); + + it('should preserve existing encryption fields during a partial update', done => { + bucket.metadata = { + encryption: { + defaultKmsKeyName: 'kms-key-name', + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + }, + }; + + const patch = { + encryption: { + customerSuppliedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + }, + }; + + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual( + metadata.encryption?.customerSuppliedEncryptionEnforcementConfig + ?.restrictionMode, + 'FullyRestricted' + ); + done(); + }; + + bucket.setMetadata(patch, assert.ifError); + }); + + it('should reject or handle invalid restrictionMode values', done => { + const invalidMetadata = { + encryption: { + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'fully_restricted', + }, + }, + }; + + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual( + metadata.encryption?.googleManagedEncryptionEnforcementConfig + ?.restrictionMode, + 'fully_restricted' + ); + done(); + }; + + bucket.setMetadata(invalidMetadata, assert.ifError); + }); + + it('should not include enforcement configs that are not provided', done => { + const partialMetadata = { + encryption: { + defaultKmsKeyName: 'test-key', + googleManagedEncryptionEnforcementConfig: { + restrictionMode: 'FullyRestricted', + }, + }, + }; + + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.ok(metadata.encryption?.defaultKmsKeyName); + assert.ok( + metadata.encryption?.googleManagedEncryptionEnforcementConfig + ); + assert.strictEqual( + metadata.encryption?.customerManagedEncryptionEnforcementConfig, + undefined + ); + assert.strictEqual( + metadata.encryption?.customerSuppliedEncryptionEnforcementConfig, + undefined + ); + done(); + }; + + bucket.setMetadata(partialMetadata, assert.ifError); + }); + + it('should allow nullifying encryption enforcement', done => { + const clearMetadata = { + encryption: null, + }; + + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual(metadata.encryption, null); + done(); + }; + + bucket.setMetadata(clearMetadata, assert.ifError); + }); + }); + }); });