diff --git a/ci/cloudbuild/builds/lib/integration.sh b/ci/cloudbuild/builds/lib/integration.sh index d6fba90073441..3506c661c860c 100644 --- a/ci/cloudbuild/builds/lib/integration.sh +++ b/ci/cloudbuild/builds/lib/integration.sh @@ -31,7 +31,7 @@ source module ci/lib/io.sh export PATH="${HOME}/.local/bin:${PATH}" python3 -m pip uninstall -y --quiet googleapis-storage-testbench python3 -m pip install --upgrade --user --quiet --disable-pip-version-check \ - "git+https://github.com/googleapis/storage-testbench@v0.59.0" + "git+https://github.com/googleapis/storage-testbench@v0.60.0" # Some of the tests will need a valid roots.pem file. rm -f /dev/shm/roots.pem diff --git a/google/cloud/storage/bucket_encryption.h b/google/cloud/storage/bucket_encryption.h index 2e26815bd963c..90bdf89945eaa 100644 --- a/google/cloud/storage/bucket_encryption.h +++ b/google/cloud/storage/bucket_encryption.h @@ -16,6 +16,12 @@ #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_BUCKET_ENCRYPTION_H #include "google/cloud/storage/version.h" +#include "google/cloud/internal/format_time_point.h" +#include +#include +#include +#include +#include #include namespace google { @@ -23,6 +29,96 @@ namespace cloud { namespace storage { GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +template +struct EncryptionEnforcementConfigName; + +template +struct EncryptionEnforcementConfig { + std::string restriction_mode; + std::chrono::system_clock::time_point effective_time; +}; + +template +inline bool operator==(EncryptionEnforcementConfig const& lhs, + EncryptionEnforcementConfig const& rhs) { + return std::tie(lhs.restriction_mode, lhs.effective_time) == + std::tie(rhs.restriction_mode, rhs.effective_time); +} + +template +inline bool operator<(EncryptionEnforcementConfig const& lhs, + EncryptionEnforcementConfig const& rhs) { + return std::tie(lhs.restriction_mode, lhs.effective_time) < + std::tie(rhs.restriction_mode, rhs.effective_time); +} + +template +inline bool operator!=(EncryptionEnforcementConfig const& lhs, + EncryptionEnforcementConfig const& rhs) { + return std::rel_ops::operator!=(lhs, rhs); +} + +template +inline bool operator>(EncryptionEnforcementConfig const& lhs, + EncryptionEnforcementConfig const& rhs) { + return std::rel_ops::operator>(lhs, rhs); +} + +template +inline bool operator<=(EncryptionEnforcementConfig const& lhs, + EncryptionEnforcementConfig const& rhs) { + return std::rel_ops::operator<=(lhs, rhs); +} + +template +inline bool operator>=(EncryptionEnforcementConfig const& lhs, + EncryptionEnforcementConfig const& rhs) { + return std::rel_ops::operator>=(lhs, rhs); +} + +template +inline std::ostream& operator<<(std::ostream& os, + EncryptionEnforcementConfig const& rhs) { + return os << EncryptionEnforcementConfigName::value + << "={restriction_mode=" << rhs.restriction_mode + << ", effective_time=" + << google::cloud::internal::FormatRfc3339(rhs.effective_time) + << "}"; +} + +struct GoogleManagedEncryptionEnforcementConfigTag {}; +using GoogleManagedEncryptionEnforcementConfig = + EncryptionEnforcementConfig; + +template <> +struct EncryptionEnforcementConfigName< + GoogleManagedEncryptionEnforcementConfigTag> { + static constexpr char const* value = + "GoogleManagedEncryptionEnforcementConfig"; +}; + +struct CustomerManagedEncryptionEnforcementConfigTag {}; +using CustomerManagedEncryptionEnforcementConfig = + EncryptionEnforcementConfig; + +template <> +struct EncryptionEnforcementConfigName< + CustomerManagedEncryptionEnforcementConfigTag> { + static constexpr char const* value = + "CustomerManagedEncryptionEnforcementConfig"; +}; + +struct CustomerSuppliedEncryptionEnforcementConfigTag {}; +using CustomerSuppliedEncryptionEnforcementConfig = + EncryptionEnforcementConfig; + +template <> +struct EncryptionEnforcementConfigName< + CustomerSuppliedEncryptionEnforcementConfigTag> { + static constexpr char const* value = + "CustomerSuppliedEncryptionEnforcementConfig"; +}; + /** * Describes the default customer managed encryption key for a bucket. * @@ -37,16 +133,36 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN */ struct BucketEncryption { std::string default_kms_key_name; + GoogleManagedEncryptionEnforcementConfig + google_managed_encryption_enforcement_config; + CustomerManagedEncryptionEnforcementConfig + customer_managed_encryption_enforcement_config; + CustomerSuppliedEncryptionEnforcementConfig + customer_supplied_encryption_enforcement_config; }; inline bool operator==(BucketEncryption const& lhs, BucketEncryption const& rhs) { - return lhs.default_kms_key_name == rhs.default_kms_key_name; + return std::tie(lhs.default_kms_key_name, + lhs.google_managed_encryption_enforcement_config, + lhs.customer_managed_encryption_enforcement_config, + lhs.customer_supplied_encryption_enforcement_config) == + std::tie(rhs.default_kms_key_name, + rhs.google_managed_encryption_enforcement_config, + rhs.customer_managed_encryption_enforcement_config, + rhs.customer_supplied_encryption_enforcement_config); } inline bool operator<(BucketEncryption const& lhs, BucketEncryption const& rhs) { - return lhs.default_kms_key_name < rhs.default_kms_key_name; + return std::tie(lhs.default_kms_key_name, + lhs.google_managed_encryption_enforcement_config, + lhs.customer_managed_encryption_enforcement_config, + lhs.customer_supplied_encryption_enforcement_config) < + std::tie(rhs.default_kms_key_name, + rhs.google_managed_encryption_enforcement_config, + rhs.customer_managed_encryption_enforcement_config, + rhs.customer_supplied_encryption_enforcement_config); } inline bool operator!=(BucketEncryption const& lhs, @@ -69,6 +185,17 @@ inline bool operator>=(BucketEncryption const& lhs, return std::rel_ops::operator>=(lhs, rhs); } +inline std::ostream& operator<<(std::ostream& os, BucketEncryption const& rhs) { + os << "BucketEncryption={default_kms_key_name=" << rhs.default_kms_key_name; + os << ", google_managed_encryption_enforcement_config=" + << rhs.google_managed_encryption_enforcement_config; + os << ", customer_managed_encryption_enforcement_config=" + << rhs.customer_managed_encryption_enforcement_config; + os << ", customer_supplied_encryption_enforcement_config=" + << rhs.customer_supplied_encryption_enforcement_config; + return os << "}"; +} + GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END } // namespace storage } // namespace cloud diff --git a/google/cloud/storage/bucket_encryption_feature_test.cc b/google/cloud/storage/bucket_encryption_feature_test.cc new file mode 100644 index 0000000000000..be56b2c9289e5 --- /dev/null +++ b/google/cloud/storage/bucket_encryption_feature_test.cc @@ -0,0 +1,141 @@ +// Copyright 2025 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 +// +// https://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. + +#include "google/cloud/storage/bucket_metadata.h" +#include "google/cloud/storage/internal/bucket_metadata_parser.h" +#include "google/cloud/internal/format_time_point.h" +#include "google/cloud/testing_util/status_matchers.h" +#include +#include + +namespace google { +namespace cloud { +namespace storage { +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN +namespace { + +TEST(BucketEncryptionTest, Parse) { + std::string text = R"""({ + "encryption": { + "defaultKmsKeyName": "projects/test-project-name/locations/us-central1/keyRings/test-keyring-name/cryptoKeys/test-key-name", + "googleManagedEncryptionEnforcementConfig": { + "restrictionMode": "FULLY_RESTRICTED", + "effectiveTime": "2025-12-18T18:13:15Z" + }, + "customerManagedEncryptionEnforcementConfig": { + "restrictionMode": "NOT_RESTRICTED", + "effectiveTime": "2025-12-18T18:13:15Z" + }, + "customerSuppliedEncryptionEnforcementConfig": { + "restrictionMode": "NOT_RESTRICTED", + "effectiveTime": "2025-12-18T18:13:15Z" + } + } +})"""; + + auto actual = internal::BucketMetadataParser::FromString(text); + ASSERT_STATUS_OK(actual); + + ASSERT_TRUE(actual->has_encryption()); + auto const& encryption = actual->encryption(); + EXPECT_EQ( + "projects/test-project-name/locations/us-central1/keyRings/" + "test-keyring-name/cryptoKeys/test-key-name", + encryption.default_kms_key_name); + + EXPECT_EQ( + "FULLY_RESTRICTED", + encryption.google_managed_encryption_enforcement_config.restriction_mode); + EXPECT_EQ("2025-12-18T18:13:15Z", + google::cloud::internal::FormatRfc3339( + encryption.google_managed_encryption_enforcement_config + .effective_time)); + + EXPECT_EQ("NOT_RESTRICTED", + encryption.customer_managed_encryption_enforcement_config + .restriction_mode); + EXPECT_EQ("2025-12-18T18:13:15Z", + google::cloud::internal::FormatRfc3339( + encryption.customer_managed_encryption_enforcement_config + .effective_time)); + + EXPECT_EQ("NOT_RESTRICTED", + encryption.customer_supplied_encryption_enforcement_config + .restriction_mode); + EXPECT_EQ("2025-12-18T18:13:15Z", + google::cloud::internal::FormatRfc3339( + encryption.customer_supplied_encryption_enforcement_config + .effective_time)); +} + +TEST(BucketEncryptionTest, ToJson) { + BucketMetadata meta; + BucketEncryption encryption; + encryption.default_kms_key_name = "test-key"; + encryption.google_managed_encryption_enforcement_config.restriction_mode = + "FULLY_RESTRICTED"; + encryption.google_managed_encryption_enforcement_config.effective_time = + google::cloud::internal::ParseRfc3339("2025-12-18T18:13:15Z").value(); + encryption.customer_managed_encryption_enforcement_config.restriction_mode = + "NOT_RESTRICTED"; + encryption.customer_managed_encryption_enforcement_config.effective_time = + google::cloud::internal::ParseRfc3339("2025-12-18T18:13:15Z").value(); + + meta.set_encryption(encryption); + + auto json_string = internal::BucketMetadataToJsonString(meta); + auto json = nlohmann::json::parse(json_string); + + ASSERT_TRUE(json.contains("encryption")); + auto e = json["encryption"]; + EXPECT_EQ("test-key", e["defaultKmsKeyName"]); + EXPECT_EQ("FULLY_RESTRICTED", + e["googleManagedEncryptionEnforcementConfig"]["restrictionMode"]); + EXPECT_EQ("2025-12-18T18:13:15Z", + e["googleManagedEncryptionEnforcementConfig"]["effectiveTime"]); + EXPECT_EQ("NOT_RESTRICTED", + e["customerManagedEncryptionEnforcementConfig"]["restrictionMode"]); + EXPECT_EQ("2025-12-18T18:13:15Z", + e["customerManagedEncryptionEnforcementConfig"]["effectiveTime"]); + EXPECT_FALSE(e.contains("customerSuppliedEncryptionEnforcementConfig")); +} + +TEST(BucketEncryptionTest, Patch) { + BucketMetadataPatchBuilder builder; + BucketEncryption encryption; + encryption.default_kms_key_name = "test-key"; + encryption.google_managed_encryption_enforcement_config.restriction_mode = + "FULLY_RESTRICTED"; + encryption.google_managed_encryption_enforcement_config.effective_time = + google::cloud::internal::ParseRfc3339("2025-12-18T18:13:15Z").value(); + + builder.SetEncryption(encryption); + + auto patch_string = builder.BuildPatch(); + auto patch = nlohmann::json::parse(patch_string); + + ASSERT_TRUE(patch.contains("encryption")); + auto e = patch["encryption"]; + EXPECT_EQ("test-key", e["defaultKmsKeyName"]); + EXPECT_EQ("FULLY_RESTRICTED", + e["googleManagedEncryptionEnforcementConfig"]["restrictionMode"]); + EXPECT_EQ("2025-12-18T18:13:15Z", + e["googleManagedEncryptionEnforcementConfig"]["effectiveTime"]); +} + +} // namespace +GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END +} // namespace storage +} // namespace cloud +} // namespace google diff --git a/google/cloud/storage/bucket_metadata.cc b/google/cloud/storage/bucket_metadata.cc index f10c02faa01f2..0d4a3ea42d4fa 100644 --- a/google/cloud/storage/bucket_metadata.cc +++ b/google/cloud/storage/bucket_metadata.cc @@ -150,8 +150,7 @@ std::ostream& operator<<(std::ostream& os, BucketMetadata const& rhs) { os << "]"; if (rhs.has_encryption()) { - os << ", encryption.default_kms_key_name=" - << rhs.encryption().default_kms_key_name; + os << ", encryption=" << rhs.encryption(); } os << ", etag=" << rhs.etag(); @@ -362,9 +361,26 @@ BucketMetadataPatchBuilder& BucketMetadataPatchBuilder::ResetDefaultAcl() { BucketMetadataPatchBuilder& BucketMetadataPatchBuilder::SetEncryption( BucketEncryption const& v) { - impl_.AddSubPatch("encryption", - internal::PatchBuilder().SetStringField( - "defaultKmsKeyName", v.default_kms_key_name)); + internal::PatchBuilder builder; + builder.SetStringField("defaultKmsKeyName", v.default_kms_key_name); + + auto add_config_patch = [&](char const* name, auto const& config) { + if (config.restriction_mode.empty()) return; + builder.AddSubPatch( + name, internal::PatchBuilder() + .SetStringField("restrictionMode", config.restriction_mode) + .SetStringField("effectiveTime", + google::cloud::internal::FormatRfc3339( + config.effective_time))); + }; + add_config_patch("googleManagedEncryptionEnforcementConfig", + v.google_managed_encryption_enforcement_config); + add_config_patch("customerManagedEncryptionEnforcementConfig", + v.customer_managed_encryption_enforcement_config); + add_config_patch("customerSuppliedEncryptionEnforcementConfig", + v.customer_supplied_encryption_enforcement_config); + + impl_.AddSubPatch("encryption", std::move(builder)); return *this; } diff --git a/google/cloud/storage/bucket_metadata_test.cc b/google/cloud/storage/bucket_metadata_test.cc index 23f307f3a988c..c2595c7e7e934 100644 --- a/google/cloud/storage/bucket_metadata_test.cc +++ b/google/cloud/storage/bucket_metadata_test.cc @@ -107,7 +107,19 @@ BucketMetadata CreateBucketMetadataForTest() { "etag": "AYX=" }], "encryption": { - "defaultKmsKeyName": "projects/test-project-name/locations/us-central1/keyRings/test-keyring-name/cryptoKeys/test-key-name" + "defaultKmsKeyName": "projects/test-project-name/locations/us-central1/keyRings/test-keyring-name/cryptoKeys/test-key-name", + "googleManagedEncryptionEnforcementConfig": { + "restrictionMode": "FULLY_RESTRICTED", + "effectiveTime": "2025-12-18T18:13:15Z" + }, + "customerManagedEncryptionEnforcementConfig": { + "restrictionMode": "NOT_RESTRICTED", + "effectiveTime": "2025-12-18T18:13:15Z" + }, + "customerSuppliedEncryptionEnforcementConfig": { + "restrictionMode": "NOT_RESTRICTED", + "effectiveTime": "2025-12-18T18:13:15Z" + } }, "etag": "XYZ=", "hierarchicalNamespace": { @@ -224,6 +236,22 @@ TEST(BucketMetadataTest, Parse) { "projects/test-project-name/locations/us-central1/keyRings/" "test-keyring-name/cryptoKeys/test-key-name", actual.encryption().default_kms_key_name); + EXPECT_EQ("FULLY_RESTRICTED", + actual.encryption() + .google_managed_encryption_enforcement_config.restriction_mode); + EXPECT_EQ( + "NOT_RESTRICTED", + actual.encryption() + .customer_managed_encryption_enforcement_config.restriction_mode); + EXPECT_EQ( + "NOT_RESTRICTED", + actual.encryption() + .customer_supplied_encryption_enforcement_config.restriction_mode); + EXPECT_EQ( + "2025-12-18T18:13:15Z", + google::cloud::internal::FormatRfc3339( + actual.encryption() + .customer_supplied_encryption_enforcement_config.effective_time)); EXPECT_EQ("XYZ=", actual.etag()); // hierarchicalNamespace ASSERT_TRUE(actual.has_hierarchical_namespace()); @@ -489,10 +517,32 @@ TEST(BucketMetadataTest, ToJsonString) { // encryption() ASSERT_EQ(1U, actual.count("encryption")); + auto const& encryption = actual["encryption"]; EXPECT_EQ( "projects/test-project-name/locations/us-central1/keyRings/" "test-keyring-name/cryptoKeys/test-key-name", - actual["encryption"].value("defaultKmsKeyName", "")); + encryption.value("defaultKmsKeyName", "")); + + EXPECT_EQ("FULLY_RESTRICTED", + encryption["googleManagedEncryptionEnforcementConfig"].value( + "restrictionMode", "")); + EXPECT_EQ("2025-12-18T18:13:15Z", + encryption["googleManagedEncryptionEnforcementConfig"].value( + "effectiveTime", "")); + + EXPECT_EQ("NOT_RESTRICTED", + encryption["customerManagedEncryptionEnforcementConfig"].value( + "restrictionMode", "")); + EXPECT_EQ("2025-12-18T18:13:15Z", + encryption["customerManagedEncryptionEnforcementConfig"].value( + "effectiveTime", "")); + + EXPECT_EQ("NOT_RESTRICTED", + encryption["customerSuppliedEncryptionEnforcementConfig"].value( + "restrictionMode", "")); + EXPECT_EQ("2025-12-18T18:13:15Z", + encryption["customerSuppliedEncryptionEnforcementConfig"].value( + "effectiveTime", "")); // hierarchical_namespace() ASSERT_EQ(1, actual.count("hierarchicalNamespace")); @@ -849,8 +899,24 @@ TEST(BucketMetadataTest, SetEncryption) { std::string fake_key_name = "projects/test-project-name/locations/us-central1/keyRings/" "test-keyring-name/cryptoKeys/another-test-key-name"; - copy.set_encryption(BucketEncryption{fake_key_name}); + std::string fake_restriction_mode = "FULLY_RESTRICTED"; + + copy.set_encryption(BucketEncryption{fake_key_name, + {fake_restriction_mode}, + {fake_restriction_mode}, + {fake_restriction_mode}}); EXPECT_EQ(fake_key_name, copy.encryption().default_kms_key_name); + EXPECT_EQ(fake_restriction_mode, + copy.encryption() + .google_managed_encryption_enforcement_config.restriction_mode); + EXPECT_EQ( + fake_restriction_mode, + copy.encryption() + .customer_managed_encryption_enforcement_config.restriction_mode); + EXPECT_EQ( + fake_restriction_mode, + copy.encryption() + .customer_supplied_encryption_enforcement_config.restriction_mode); EXPECT_NE(expected, copy); } diff --git a/google/cloud/storage/internal/bucket_metadata_parser.cc b/google/cloud/storage/internal/bucket_metadata_parser.cc index 18924bd133a27..1c138bb0c3d45 100644 --- a/google/cloud/storage/internal/bucket_metadata_parser.cc +++ b/google/cloud/storage/internal/bucket_metadata_parser.cc @@ -17,6 +17,7 @@ #include "google/cloud/storage/internal/lifecycle_rule_parser.h" #include "google/cloud/storage/internal/metadata_parser.h" #include "google/cloud/storage/internal/object_access_control_parser.h" +#include "google/cloud/internal/format_time_point.h" #include "google/cloud/internal/make_status.h" #include "absl/strings/str_format.h" #include @@ -156,10 +157,41 @@ Status ParseDefaultObjectAcl(BucketMetadata& meta, nlohmann::json const& json) { return Status{}; } +template +StatusOr ParseEncryptionEnforcementConfig( + nlohmann::json const& json) { + auto restriction_mode = json.value("restrictionMode", ""); + auto effective_time = internal::ParseTimestampField(json, "effectiveTime"); + if (!effective_time) return std::move(effective_time).status(); + return ConfigType{std::move(restriction_mode), *effective_time}; +} + Status ParseEncryption(BucketMetadata& meta, nlohmann::json const& json) { if (json.contains("encryption")) { BucketEncryption e; - e.default_kms_key_name = json["encryption"].value("defaultKmsKeyName", ""); + auto const& encryption = json["encryption"]; + e.default_kms_key_name = encryption.value("defaultKmsKeyName", ""); + if (encryption.contains("googleManagedEncryptionEnforcementConfig")) { + auto config = ParseEncryptionEnforcementConfig< + GoogleManagedEncryptionEnforcementConfig>( + encryption["googleManagedEncryptionEnforcementConfig"]); + if (!config) return std::move(config).status(); + e.google_managed_encryption_enforcement_config = *std::move(config); + } + if (encryption.contains("customerManagedEncryptionEnforcementConfig")) { + auto config = ParseEncryptionEnforcementConfig< + CustomerManagedEncryptionEnforcementConfig>( + encryption["customerManagedEncryptionEnforcementConfig"]); + if (!config) return std::move(config).status(); + e.customer_managed_encryption_enforcement_config = *std::move(config); + } + if (encryption.contains("customerSuppliedEncryptionEnforcementConfig")) { + auto config = ParseEncryptionEnforcementConfig< + CustomerSuppliedEncryptionEnforcementConfig>( + encryption["customerSuppliedEncryptionEnforcementConfig"]); + if (!config) return std::move(config).status(); + e.customer_supplied_encryption_enforcement_config = *std::move(config); + } meta.set_encryption(std::move(e)); } return Status{}; @@ -375,6 +407,25 @@ void ToJsonEncryption(nlohmann::json& json, BucketMetadata const& meta) { if (!meta.has_encryption()) return; nlohmann::json e; SetIfNotEmpty(e, "defaultKmsKeyName", meta.encryption().default_kms_key_name); + + auto to_json_config = [&](char const* name, auto const& config_source) { + if (config_source.restriction_mode.empty()) return; + nlohmann::json config; + config["restrictionMode"] = config_source.restriction_mode; + config["effectiveTime"] = + google::cloud::internal::FormatRfc3339(config_source.effective_time); + e[name] = std::move(config); + }; + to_json_config( + "googleManagedEncryptionEnforcementConfig", + meta.encryption().google_managed_encryption_enforcement_config); + to_json_config( + "customerManagedEncryptionEnforcementConfig", + meta.encryption().customer_managed_encryption_enforcement_config); + to_json_config( + "customerSuppliedEncryptionEnforcementConfig", + meta.encryption().customer_supplied_encryption_enforcement_config); + json["encryption"] = std::move(e); } diff --git a/google/cloud/storage/internal/grpc/bucket_request_parser.cc b/google/cloud/storage/internal/grpc/bucket_request_parser.cc index 0b4d19c886791..703125edd9c02 100644 --- a/google/cloud/storage/internal/grpc/bucket_request_parser.cc +++ b/google/cloud/storage/internal/grpc/bucket_request_parser.cc @@ -21,6 +21,7 @@ #include "google/cloud/storage/internal/lifecycle_rule_parser.h" #include "google/cloud/storage/internal/object_access_control_parser.h" #include "google/cloud/storage/internal/patch_builder_details.h" +#include "google/cloud/internal/format_time_point.h" #include "google/cloud/internal/time_utils.h" #include @@ -162,13 +163,53 @@ Status PatchLogging(Bucket& b, nlohmann::json const& l) { return Status{}; } +google::protobuf::Timestamp ToProtoTimestamp( + std::chrono::system_clock::time_point tp) { + auto duration = tp.time_since_epoch(); + auto seconds = std::chrono::duration_cast(duration); + auto nanos = + std::chrono::duration_cast(duration - seconds); + google::protobuf::Timestamp ts; + ts.set_seconds(seconds.count()); + ts.set_nanos(static_cast(nanos.count())); + return ts; +} + Status PatchEncryption(Bucket& b, nlohmann::json const& e) { if (e.is_null()) { b.clear_encryption(); - } else { - b.mutable_encryption()->set_default_kms_key( - e.value("defaultKmsKeyName", "")); + return Status{}; } + auto& encryption = *b.mutable_encryption(); + if (e.contains("defaultKmsKeyName")) { + encryption.set_default_kms_key(e.value("defaultKmsKeyName", "")); + } + auto patch_config = [&](char const* json_key, auto* mutable_config) { + if (e.contains(json_key)) { + auto const& c = e[json_key]; + if (c.contains("restrictionMode")) { + mutable_config->set_restriction_mode(c.value("restrictionMode", "")); + } + if (c.contains("effectiveTime")) { + auto ts = + google::cloud::internal::ParseRfc3339(c.value("effectiveTime", "")); + if (ts) { + *mutable_config->mutable_effective_time() = ToProtoTimestamp(*ts); + } + } + } + }; + + patch_config( + "googleManagedEncryptionEnforcementConfig", + encryption.mutable_google_managed_encryption_enforcement_config()); + patch_config( + "customerManagedEncryptionEnforcementConfig", + encryption.mutable_customer_managed_encryption_enforcement_config()); + patch_config( + "customerSuppliedEncryptionEnforcementConfig", + encryption.mutable_customer_supplied_encryption_enforcement_config()); + return Status{}; } @@ -289,8 +330,24 @@ void UpdateLogging(Bucket& bucket, storage::BucketMetadata const& metadata) { void UpdateEncryption(Bucket& bucket, storage::BucketMetadata const& metadata) { if (!metadata.has_encryption()) return; - bucket.mutable_encryption()->set_default_kms_key( - metadata.encryption().default_kms_key_name); + auto& encryption = *bucket.mutable_encryption(); + encryption.set_default_kms_key(metadata.encryption().default_kms_key_name); + + auto update_config = [&](auto const& source, auto* dest) { + if (source.restriction_mode.empty()) return; + dest->set_restriction_mode(source.restriction_mode); + *dest->mutable_effective_time() = ToProtoTimestamp(source.effective_time); + }; + + update_config( + metadata.encryption().google_managed_encryption_enforcement_config, + encryption.mutable_google_managed_encryption_enforcement_config()); + update_config( + metadata.encryption().customer_managed_encryption_enforcement_config, + encryption.mutable_customer_managed_encryption_enforcement_config()); + update_config( + metadata.encryption().customer_supplied_encryption_enforcement_config, + encryption.mutable_customer_supplied_encryption_enforcement_config()); } void UpdateBilling(Bucket& bucket, storage::BucketMetadata const& metadata) { diff --git a/google/cloud/storage/internal/grpc/bucket_request_parser_test.cc b/google/cloud/storage/internal/grpc/bucket_request_parser_test.cc index 4a1769efad220..0612e8f647b22 100644 --- a/google/cloud/storage/internal/grpc/bucket_request_parser_test.cc +++ b/google/cloud/storage/internal/grpc/bucket_request_parser_test.cc @@ -458,7 +458,21 @@ TEST(GrpcBucketRequestParser, PatchBucketRequestAllOptions) { log_bucket: "projects/_/buckets/test-log-bucket" log_object_prefix: "test-log-prefix" } - encryption { default_kms_key: "test-only-kms-key" } + encryption { + default_kms_key: "test-only-kms-key" + google_managed_encryption_enforcement_config { + restriction_mode: "FULLY_RESTRICTED" + effective_time { seconds: 1766175572 } + } + customer_managed_encryption_enforcement_config { + restriction_mode: "NOT_RESTRICTED" + effective_time { seconds: 1766175695 } + } + customer_supplied_encryption_enforcement_config { + restriction_mode: "FULLY_RESTRICTED" + effective_time { seconds: 1766175739 } + } + } autoclass { enabled: true } billing { requester_pays: true } retention_policy { retention_duration { seconds: 123000 } } @@ -534,7 +548,22 @@ TEST(GrpcBucketRequestParser, PatchBucketRequestAllOptions) { .SetLogging( storage::BucketLogging{"test-log-bucket", "test-log-prefix"}) .SetEncryption(storage::BucketEncryption{ - /*.default_kms_key=*/"test-only-kms-key"}) + /*.default_kms_key=*/"test-only-kms-key", + /*.google_managed_encryption_enforcement_config=*/ + storage::GoogleManagedEncryptionEnforcementConfig{ + "FULLY_RESTRICTED", + google::cloud::internal::ParseRfc3339("2025-12-19T20:19:32Z") + .value()}, + /*.customer_managed_encryption_enforcement_config=*/ + storage::CustomerManagedEncryptionEnforcementConfig{ + "NOT_RESTRICTED", + google::cloud::internal::ParseRfc3339("2025-12-19T20:21:35Z") + .value()}, + /*.customer_supplied_encryption_enforcement_config=*/ + storage::CustomerSuppliedEncryptionEnforcementConfig{ + "FULLY_RESTRICTED", + google::cloud::internal::ParseRfc3339("2025-12-19T20:22:19Z") + .value()}}) .SetAutoclass(storage::BucketAutoclass{true}) .SetBilling(storage::BucketBilling{/*.requester_pays=*/true}) .SetRetentionPolicy(std::chrono::seconds(123000)) @@ -713,7 +742,21 @@ TEST(GrpcBucketRequestParser, UpdateBucketRequestAllOptions) { log_bucket: "test-log-bucket" log_object_prefix: "test-log-prefix" } - encryption { default_kms_key: "test-only-kms-key" } + encryption { + default_kms_key: "test-only-kms-key" + google_managed_encryption_enforcement_config { + restriction_mode: "FULLY_RESTRICTED" + effective_time { seconds: 1766176065 } + } + customer_managed_encryption_enforcement_config { + restriction_mode: "NOT_RESTRICTED" + effective_time { seconds: 1766176105 } + } + customer_supplied_encryption_enforcement_config { + restriction_mode: "FULLY_RESTRICTED" + effective_time { seconds: 1766176151 } + } + } autoclass { enabled: true } billing { requester_pays: true } retention_policy { retention_duration { seconds: 123000 } } @@ -787,7 +830,22 @@ TEST(GrpcBucketRequestParser, UpdateBucketRequestAllOptions) { .set_logging( storage::BucketLogging{"test-log-bucket", "test-log-prefix"}) .set_encryption(storage::BucketEncryption{ - /*.default_kms_key=*/"test-only-kms-key"}) + /*.default_kms_key=*/"test-only-kms-key", + /*.google_managed_encryption_enforcement_config=*/ + storage::GoogleManagedEncryptionEnforcementConfig{ + "FULLY_RESTRICTED", + google::cloud::internal::ParseRfc3339("2025-12-19T20:27:45Z") + .value()}, + /*.customer_managed_encryption_enforcement_config=*/ + storage::CustomerManagedEncryptionEnforcementConfig{ + "NOT_RESTRICTED", + google::cloud::internal::ParseRfc3339("2025-12-19T20:28:25Z") + .value()}, + /*.customer_supplied_encryption_enforcement_config=*/ + storage::CustomerSuppliedEncryptionEnforcementConfig{ + "FULLY_RESTRICTED", + google::cloud::internal::ParseRfc3339("2025-12-19T20:29:11Z") + .value()}}) .set_autoclass(storage::BucketAutoclass{true}) .set_billing(storage::BucketBilling{/*.requester_pays=*/true}) .set_retention_policy(std::chrono::seconds(123000))