From b108d3fb5350c1de55356749e1cbbc6c5ee3cd50 Mon Sep 17 00:00:00 2001 From: Andrei Ciobanu Date: Thu, 12 Feb 2026 13:46:18 +0200 Subject: [PATCH] Add `universe_domain` to the `gcs` backend (#3758) Signed-off-by: Andrei Ciobanu --- CHANGELOG.md | 1 + internal/backend/remote-state/gcs/backend.go | 12 ++++++++++++ .../backend/remote-state/gcs/backend_test.go | 16 ++++++++++++++++ website/docs/language/settings/backends/gcs.mdx | 1 + 4 files changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57a098db28..bf96868d1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ BUG FIXES: - No longer generate spurious error messages about incorrectly-detected provider reference problems when modules fail to load during the construction of a configuration tree. ([#3681](https://github.com/opentofu/opentofu/pull/3681)) - OpenTofu consistently sends "null" to external `key_provider` programs when only encryption key is requested ([#3672](https://github.com/opentofu/opentofu/pull/3672)) - The `azurerm` backend's MSI authentication method will now respect the provided client ID ([#3586](https://github.com/opentofu/opentofu/issues/3586)) +- Add `universe_domain` option in the `gcs` backend to support sovereign GCP services ([#3758](https://github.com/opentofu/opentofu/issues/3758)) ## Previous Releases diff --git a/internal/backend/remote-state/gcs/backend.go b/internal/backend/remote-state/gcs/backend.go index 2002f6a32b..e51f929a8b 100644 --- a/internal/backend/remote-state/gcs/backend.go +++ b/internal/backend/remote-state/gcs/backend.go @@ -119,6 +119,13 @@ func New(enc encryption.StateEncryption) backend.Backend { "GOOGLE_STORAGE_CUSTOM_ENDPOINT", }, nil), }, + "universe_domain": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_BACKEND_UNIVERSE_DOMAIN", + }, nil), + }, }, } @@ -212,6 +219,11 @@ func (b *Backend) configure(ctx context.Context) error { endpoint := option.WithEndpoint(storageEndpoint.(string)) opts = append(opts, endpoint) } + // Custom universe domain for sovereign cloud authentication + if universeDomain, ok := data.GetOk("universe_domain"); ok { + domain := option.WithUniverseDomain(universeDomain.(string)) + opts = append(opts, domain) + } client, err := storage.NewClient(ctx, opts...) if err != nil { return fmt.Errorf("storage.NewClient() failed: %w", err) diff --git a/internal/backend/remote-state/gcs/backend_test.go b/internal/backend/remote-state/gcs/backend_test.go index eaf1b26a80..229a148c29 100644 --- a/internal/backend/remote-state/gcs/backend_test.go +++ b/internal/backend/remote-state/gcs/backend_test.go @@ -47,6 +47,22 @@ const ( var keyRingLocation = os.Getenv("GOOGLE_REGION") +// TestBackendConfig checks that the fields provided in the config works well with the schema configured +// in the backend. +func TestBackendConfig(t *testing.T) { + expectedBucketName := bucketName(t) + config := map[string]interface{}{ + "bucket": expectedBucketName, + "access_token": "dummy token", + "storage_custom_endpoint": "https://storage.s3nsapis.fr/storage/v1/", + "universe_domain": "s3nsapis.fr", + } + + // This is meant only to parse the configuration and ensure that the configuration given matches the schema configured + // in the backend + _ = backend.TestBackendConfig(t, New(encryption.StateEncryptionDisabled()), backend.TestWrapConfig(config)).(*Backend) +} + func TestStateFile(t *testing.T) { t.Parallel() diff --git a/website/docs/language/settings/backends/gcs.mdx b/website/docs/language/settings/backends/gcs.mdx index 9c5ea334ee..5939c0aecc 100644 --- a/website/docs/language/settings/backends/gcs.mdx +++ b/website/docs/language/settings/backends/gcs.mdx @@ -137,3 +137,4 @@ The following configuration options are supported: For more information, including IAM requirements, see [Customer-managed Encryption Keys](https://cloud.google.com/storage/docs/encryption/customer-managed-keys). - `storage_custom_endpoint` / `GOOGLE_BACKEND_STORAGE_CUSTOM_ENDPOINT` / `GOOGLE_STORAGE_CUSTOM_ENDPOINT` - (Optional) A URL containing three parts: the protocol, the DNS name pointing to a Private Service Connect endpoint, and the path for the Cloud Storage API (`/storage/v1/b`, [see here](https://cloud.google.com/storage/docs/json_api/v1/buckets/get#http-request)). You can either use [a DNS name automatically made by the Service Directory](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis#configure-p-dns) or a [custom DNS name](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis#configure-dns-default) made by you. For example, if you create an endpoint called `xyz` and want to use the automatically-created DNS name, you should set the field value as `https://storage-xyz.p.googleapis.com/storage/v1/b`. For help creating a Private Service Connect endpoint using OpenTofu, [see this guide](https://cloud.google.com/vpc/docs/configure-private-service-connect-apis#terraform_1). +- `universe_domain` / `GOOGLE_BACKEND_UNIVERSE_DOMAIN` / `GOOGLE_CLOUD_UNIVERSE_DOMAIN` - (Optional) The domain of the Private Service Connect endpoint. Most of the times, the value provided here is the root domain of the value configured in `storage_custom_endpoint`. This instructs the GCP sdk to allow requests to a sovereign cloud with credentials that are already generated for that cloud universe.