From 8626ec378173743109cfcfde71f86add272e0d45 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:13:27 +0000 Subject: [PATCH 1/4] feat: Implement Bucket Encryption Enforcement - Update storage-v1.json with enforcement configs. - Update docblocks in Bucket.php and StorageClient.php. - Add unit and system tests. Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com> --- Storage/src/Bucket.php | 12 ++++ .../ServiceDefinition/storage-v1.json | 69 +++++++++++++++++++ Storage/src/StorageClient.php | 12 ++++ Storage/tests/System/KmsTest.php | 35 ++++++++++ Storage/tests/Unit/BucketTest.php | 34 +++++++++ Storage/tests/Unit/StorageClientTest.php | 33 +++++++++ 6 files changed, 195 insertions(+) diff --git a/Storage/src/Bucket.php b/Storage/src/Bucket.php index 4f6b30024980..b406b9ce0c72 100644 --- a/Storage/src/Bucket.php +++ b/Storage/src/Bucket.php @@ -1029,6 +1029,18 @@ public function delete(array $options = []) * `projects/my-project/locations/kr-location/keyRings/my-kr/cryptoKeys/my-key`. * Please note the KMS key ring must use the same location as the * bucket. + * @type array $encryption.googleManagedEncryptionEnforcementConfig Google + * managed encryption enforcement configuration. + * @type string $encryption.googleManagedEncryptionEnforcementConfig.restrictionMode + * Restriction mode for Google managed encryption. + * @type array $encryption.customerManagedEncryptionEnforcementConfig Customer + * managed encryption enforcement configuration. + * @type string $encryption.customerManagedEncryptionEnforcementConfig.restrictionMode + * Restriction mode for customer managed encryption. + * @type array $encryption.customerSuppliedEncryptionEnforcementConfig Customer + * supplied encryption enforcement configuration. + * @type string $encryption.customerSuppliedEncryptionEnforcementConfig.restrictionMode + * Restriction mode for customer supplied encryption. * @type bool $defaultEventBasedHold When `true`, newly created objects * in this bucket will be retained indefinitely until an event * occurs, signified by the hold's release. diff --git a/Storage/src/Connection/ServiceDefinition/storage-v1.json b/Storage/src/Connection/ServiceDefinition/storage-v1.json index 52aa9050a729..52729ffecf3f 100644 --- a/Storage/src/Connection/ServiceDefinition/storage-v1.json +++ b/Storage/src/Connection/ServiceDefinition/storage-v1.json @@ -195,6 +195,75 @@ "defaultKmsKeyName": { "type": "string", "description": "A Cloud KMS key that will be used to encrypt objects inserted into this bucket, if no encryption method is specified." + }, + "googleManagedEncryptionEnforcementConfig": { + "type": "object", + "description": "If set, the new objects created in this bucket must comply with this enforcement config. Changing this has no effect on existing objects; it applies to new objects only. If omitted, the new objects are allowed to be encrypted with Google Managed Encryption type by default.", + "properties": { + "restrictionMode": { + "type": "string", + "description": "Restriction mode for Google-Managed Encryption Keys. Defaults to NotRestricted.", + "enum": [ + "NotRestricted", + "FullyRestricted" + ], + "enumDescriptions": [ + "Creation of new objects with Google Managed Encryption is not restricted.", + "Creation of new objects with Google Managed Encryption is fully restricted." + ] + }, + "effectiveTime": { + "type": "string", + "description": "Server-determined value that indicates the time from which configuration was enforced and effective. This value is in RFC 3339 format.", + "format": "date-time" + } + } + }, + "customerManagedEncryptionEnforcementConfig": { + "type": "object", + "description": "If set, the new objects created in this bucket must comply with this enforcement config. Changing this has no effect on existing objects; it applies to new objects only. If omitted, the new objects are allowed to be encrypted with Customer Managed Encryption type by default.", + "properties": { + "restrictionMode": { + "type": "string", + "description": "Restriction mode for Customer-Managed Encryption Keys. Defaults to NotRestricted.", + "enum": [ + "NotRestricted", + "FullyRestricted" + ], + "enumDescriptions": [ + "Creation of new objects with Customer-Managed Encryption is not restricted.", + "Creation of new objects with Customer-Managed Encryption is fully restricted." + ] + }, + "effectiveTime": { + "type": "string", + "description": "Server-determined value that indicates the time from which configuration was enforced and effective. This value is in RFC 3339 format.", + "format": "date-time" + } + } + }, + "customerSuppliedEncryptionEnforcementConfig": { + "type": "object", + "description": "If set, the new objects created in this bucket must comply with this enforcement config. Changing this has no effect on existing objects; it applies to new objects only. If omitted, the new objects are allowed to be encrypted with Customer Supplied Encryption type by default.", + "properties": { + "restrictionMode": { + "type": "string", + "description": "Restriction mode for Customer-Supplied Encryption Keys. Defaults to NotRestricted.", + "enum": [ + "NotRestricted", + "FullyRestricted" + ], + "enumDescriptions": [ + "Creation of new objects with Customer-Supplied Encryption is not restricted.", + "Creation of new objects with Customer-Supplied Encryption is fully restricted." + ] + }, + "effectiveTime": { + "type": "string", + "description": "Server-determined value that indicates the time from which configuration was enforced and effective. This value is in RFC 3339 format.", + "format": "date-time" + } + } } } }, diff --git a/Storage/src/StorageClient.php b/Storage/src/StorageClient.php index 9e9e1c0a32c3..9954c2bfd366 100644 --- a/Storage/src/StorageClient.php +++ b/Storage/src/StorageClient.php @@ -454,6 +454,18 @@ public function restore(string $name, string $generation, array $options = []) * `projects/my-project/locations/kr-location/keyRings/my-kr/cryptoKeys/my-key`. * Please note the KMS key ring must use the same location as the * bucket. + * @type array $encryption.googleManagedEncryptionEnforcementConfig Google + * managed encryption enforcement configuration. + * @type string $encryption.googleManagedEncryptionEnforcementConfig.restrictionMode + * Restriction mode for Google managed encryption. + * @type array $encryption.customerManagedEncryptionEnforcementConfig Customer + * managed encryption enforcement configuration. + * @type string $encryption.customerManagedEncryptionEnforcementConfig.restrictionMode + * Restriction mode for customer managed encryption. + * @type array $encryption.customerSuppliedEncryptionEnforcementConfig Customer + * supplied encryption enforcement configuration. + * @type string $encryption.customerSuppliedEncryptionEnforcementConfig.restrictionMode + * Restriction mode for customer supplied encryption. * @type bool $defaultEventBasedHold When `true`, newly created objects * in this bucket will be retained indefinitely until an event * occurs, signified by the hold's release. diff --git a/Storage/tests/System/KmsTest.php b/Storage/tests/System/KmsTest.php index 8ce0bd9a70a6..598a097f1802 100644 --- a/Storage/tests/System/KmsTest.php +++ b/Storage/tests/System/KmsTest.php @@ -155,6 +155,41 @@ public function testRotatesKmsToCustomerSuppliedEncrpytion() $this->assertEquals(self::DATA, $rewrittenObject->downloadAsString()); } + public function testEncryptionEnforcementConfig() + { + self::$bucket->update([ + 'encryption' => [ + 'googleManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'NotRestricted' + ], + 'customerManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'NotRestricted' + ], + 'customerSuppliedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'NotRestricted' + ], + ] + ]); + + $info = self::$bucket->info(); + $this->assertArrayHasKey('encryption', $info); + $this->assertArrayHasKey('googleManagedEncryptionEnforcementConfig', $info['encryption']); + $this->assertArrayHasKey('customerManagedEncryptionEnforcementConfig', $info['encryption']); + $this->assertArrayHasKey('customerSuppliedEncryptionEnforcementConfig', $info['encryption']); + $this->assertEquals('NotRestricted', $info['encryption']['googleManagedEncryptionEnforcementConfig']['restrictionMode']); + $this->assertEquals('NotRestricted', $info['encryption']['customerManagedEncryptionEnforcementConfig']['restrictionMode']); + $this->assertEquals('NotRestricted', $info['encryption']['customerSuppliedEncryptionEnforcementConfig']['restrictionMode']); + + // Reset + self::$bucket->update([ + 'encryption' => [ + 'googleManagedEncryptionEnforcementConfig' => null, + 'customerManagedEncryptionEnforcementConfig' => null, + 'customerSuppliedEncryptionEnforcementConfig' => null, + ] + ]); + } + /** * @param array $options * @return StorageObject diff --git a/Storage/tests/Unit/BucketTest.php b/Storage/tests/Unit/BucketTest.php index 5ac38e70763f..5368c0ff6c24 100644 --- a/Storage/tests/Unit/BucketTest.php +++ b/Storage/tests/Unit/BucketTest.php @@ -438,6 +438,40 @@ public function testUpdateAutoclassConfig($terminalStorageClass) $this->assertArrayHasKey('terminalStorageClassUpdateTime', $autoclassInfo); } + public function testUpdateEncryptionEnforcementConfig() + { + $encryptionConfig = [ + 'encryption' => [ + 'defaultKmsKeyName' => 'key', + 'googleManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + 'customerManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + 'customerSuppliedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + ], + ]; + $this->connection->patchBucket(Argument::any())->willReturn( + ['name' => 'bucket'] + + $encryptionConfig + ); + $bucket = $this->getBucket([ + 'name' => 'bucket', + ]); + + $bucket->update($encryptionConfig); + + $this->assertArrayHasKey('encryption', $bucket->info()); + $encryptionInfo = $bucket->info()['encryption']; + $this->assertEquals('key', $encryptionInfo['defaultKmsKeyName']); + $this->assertEquals('FullyRestricted', $encryptionInfo['googleManagedEncryptionEnforcementConfig']['restrictionMode']); + $this->assertEquals('FullyRestricted', $encryptionInfo['customerManagedEncryptionEnforcementConfig']['restrictionMode']); + $this->assertEquals('FullyRestricted', $encryptionInfo['customerSuppliedEncryptionEnforcementConfig']['restrictionMode']); + } + public function testUpdatesDataWithLifecycleBuilder() { $lifecycleArr = ['test' => 'test']; diff --git a/Storage/tests/Unit/StorageClientTest.php b/Storage/tests/Unit/StorageClientTest.php index 9f8b5ea117c7..d2d574871859 100644 --- a/Storage/tests/Unit/StorageClientTest.php +++ b/Storage/tests/Unit/StorageClientTest.php @@ -208,6 +208,39 @@ public function testCreatesDualRegionBucket() $this->assertInstanceOf(Bucket::class, $createdBucket); } + public function testCreateBucketWithEncryptionEnforcementConfig() + { + $bucket = 'bucket'; + $encryptionConfig = [ + 'encryption' => [ + 'defaultKmsKeyName' => 'key', + 'googleManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + 'customerManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + 'customerSuppliedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + ], + ]; + $this->connection->projectId() + ->willReturn(self::PROJECT); + $this->connection + ->insertBucket([ + 'project' => self::PROJECT, + 'encryption' => $encryptionConfig['encryption'], + 'name' => $bucket + ]) + ->willReturn(['name' => $bucket] + $encryptionConfig); + $this->client->___setProperty('connection', $this->connection->reveal()); + + $createdBucket = $this->client->createBucket($bucket, $encryptionConfig); + $this->assertInstanceOf(Bucket::class, $createdBucket); + $this->assertEquals($encryptionConfig['encryption'], $createdBucket->info()['encryption']); + } + public function testCreatesBucketWithLifecycleBuilder() { $bucket = 'bucket'; From cce9320e6bf3f4da64ef5e1e999a0a33944a39d2 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 05:26:49 +0000 Subject: [PATCH 2/4] chore: fix checkstyle errors Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com> --- Storage/tests/System/KmsTest.php | 15 ++++++++++++--- Storage/tests/Unit/BucketTest.php | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Storage/tests/System/KmsTest.php b/Storage/tests/System/KmsTest.php index 598a097f1802..af22c5975ffb 100644 --- a/Storage/tests/System/KmsTest.php +++ b/Storage/tests/System/KmsTest.php @@ -176,9 +176,18 @@ public function testEncryptionEnforcementConfig() $this->assertArrayHasKey('googleManagedEncryptionEnforcementConfig', $info['encryption']); $this->assertArrayHasKey('customerManagedEncryptionEnforcementConfig', $info['encryption']); $this->assertArrayHasKey('customerSuppliedEncryptionEnforcementConfig', $info['encryption']); - $this->assertEquals('NotRestricted', $info['encryption']['googleManagedEncryptionEnforcementConfig']['restrictionMode']); - $this->assertEquals('NotRestricted', $info['encryption']['customerManagedEncryptionEnforcementConfig']['restrictionMode']); - $this->assertEquals('NotRestricted', $info['encryption']['customerSuppliedEncryptionEnforcementConfig']['restrictionMode']); + $this->assertEquals( + 'NotRestricted', + $info['encryption']['googleManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + $this->assertEquals( + 'NotRestricted', + $info['encryption']['customerManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + $this->assertEquals( + 'NotRestricted', + $info['encryption']['customerSuppliedEncryptionEnforcementConfig']['restrictionMode'] + ); // Reset self::$bucket->update([ diff --git a/Storage/tests/Unit/BucketTest.php b/Storage/tests/Unit/BucketTest.php index 5368c0ff6c24..19ab4a6929d4 100644 --- a/Storage/tests/Unit/BucketTest.php +++ b/Storage/tests/Unit/BucketTest.php @@ -467,9 +467,18 @@ public function testUpdateEncryptionEnforcementConfig() $this->assertArrayHasKey('encryption', $bucket->info()); $encryptionInfo = $bucket->info()['encryption']; $this->assertEquals('key', $encryptionInfo['defaultKmsKeyName']); - $this->assertEquals('FullyRestricted', $encryptionInfo['googleManagedEncryptionEnforcementConfig']['restrictionMode']); - $this->assertEquals('FullyRestricted', $encryptionInfo['customerManagedEncryptionEnforcementConfig']['restrictionMode']); - $this->assertEquals('FullyRestricted', $encryptionInfo['customerSuppliedEncryptionEnforcementConfig']['restrictionMode']); + $this->assertEquals( + 'FullyRestricted', + $encryptionInfo['googleManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + $this->assertEquals( + 'FullyRestricted', + $encryptionInfo['customerManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + $this->assertEquals( + 'FullyRestricted', + $encryptionInfo['customerSuppliedEncryptionEnforcementConfig']['restrictionMode'] + ); } public function testUpdatesDataWithLifecycleBuilder() From 375858d5ae98307568310c0788fef7baba20b719 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 08:28:58 +0000 Subject: [PATCH 3/4] chore: add more tests Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com> --- Storage/tests/Unit/BucketTest.php | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Storage/tests/Unit/BucketTest.php b/Storage/tests/Unit/BucketTest.php index 19ab4a6929d4..025c0015e828 100644 --- a/Storage/tests/Unit/BucketTest.php +++ b/Storage/tests/Unit/BucketTest.php @@ -481,6 +481,46 @@ public function testUpdateEncryptionEnforcementConfig() ); } + public function testReloadWithEncryptionEnforcementConfig() + { + $encryptionConfig = [ + 'encryption' => [ + 'defaultKmsKeyName' => 'key', + 'googleManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted', + 'effectiveTime' => '2025-12-18T18:13:15Z' + ], + 'customerManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'NotRestricted', + 'effectiveTime' => '2025-12-18T18:13:15Z' + ], + 'customerSuppliedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'NotRestricted', + 'effectiveTime' => '2025-12-18T18:13:15Z' + ], + ], + ]; + $this->connection->getBucket(Argument::any())->willReturn( + ['name' => self::BUCKET_NAME] + + $encryptionConfig + ); + $bucket = $this->getBucket(); + + $info = $bucket->reload(); + + $this->assertArrayHasKey('encryption', $info); + $encryptionInfo = $info['encryption']; + $this->assertEquals('key', $encryptionInfo['defaultKmsKeyName']); + $this->assertEquals( + 'FullyRestricted', + $encryptionInfo['googleManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + $this->assertEquals( + '2025-12-18T18:13:15Z', + $encryptionInfo['googleManagedEncryptionEnforcementConfig']['effectiveTime'] + ); + } + public function testUpdatesDataWithLifecycleBuilder() { $lifecycleArr = ['test' => 'test']; From edb3d7811feae0fa96fa5ed8bd75e0f6f8bf6a66 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:05:28 +0000 Subject: [PATCH 4/4] chore: add more tests - Add test for partial encryption enforcement configuration update. - Add test for unknown enum value handling in reload for forward compatibility. - Ensure all tests follow checkstyle rules. Co-authored-by: nidhiii-27 <224584462+nidhiii-27@users.noreply.github.com> --- Storage/tests/Unit/BucketTest.php | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Storage/tests/Unit/BucketTest.php b/Storage/tests/Unit/BucketTest.php index 025c0015e828..d24181608459 100644 --- a/Storage/tests/Unit/BucketTest.php +++ b/Storage/tests/Unit/BucketTest.php @@ -481,6 +481,35 @@ public function testUpdateEncryptionEnforcementConfig() ); } + public function testUpdatePartialEncryptionEnforcementConfig() + { + $encryptionConfig = [ + 'encryption' => [ + 'googleManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'FullyRestricted' + ], + ], + ]; + $this->connection->patchBucket(Argument::any())->willReturn( + ['name' => 'bucket'] + + $encryptionConfig + ); + $bucket = $this->getBucket([ + 'name' => 'bucket', + ]); + + $bucket->update($encryptionConfig); + + $this->assertArrayHasKey('encryption', $bucket->info()); + $encryptionInfo = $bucket->info()['encryption']; + $this->assertEquals( + 'FullyRestricted', + $encryptionInfo['googleManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + $this->assertArrayNotHasKey('customerManagedEncryptionEnforcementConfig', $encryptionInfo); + $this->assertArrayNotHasKey('customerSuppliedEncryptionEnforcementConfig', $encryptionInfo); + } + public function testReloadWithEncryptionEnforcementConfig() { $encryptionConfig = [ @@ -521,6 +550,31 @@ public function testReloadWithEncryptionEnforcementConfig() ); } + public function testReloadWithUnknownEncryptionEnforcementRestrictionMode() + { + $encryptionConfig = [ + 'encryption' => [ + 'googleManagedEncryptionEnforcementConfig' => [ + 'restrictionMode' => 'NOT_YET_DEFINED' + ], + ], + ]; + $this->connection->getBucket(Argument::any())->willReturn( + ['name' => self::BUCKET_NAME] + + $encryptionConfig + ); + $bucket = $this->getBucket(); + + $info = $bucket->reload(); + + $this->assertArrayHasKey('encryption', $info); + $encryptionInfo = $info['encryption']; + $this->assertEquals( + 'NOT_YET_DEFINED', + $encryptionInfo['googleManagedEncryptionEnforcementConfig']['restrictionMode'] + ); + } + public function testUpdatesDataWithLifecycleBuilder() { $lifecycleArr = ['test' => 'test'];