diff --git a/README.rst b/README.rst index 6eff907a3..7ccbb76a7 100644 --- a/README.rst +++ b/README.rst @@ -325,6 +325,20 @@ switch to the REST client by setting the ``SOPS_GCP_KMS_CLIENT_TYPE`` environmen $ export SOPS_GCP_KMS_CLIENT_TYPE=rest # Use REST client $ export SOPS_GCP_KMS_CLIENT_TYPE=grpc # Use gRPC client (default) +For sovereign cloud environments that expose a GCP-compatible KMS API at a +non-standard endpoint (e.g. S3NS/Thales TPC: ``cloudkms.s3nsapis.fr``), +you can override the endpoint or the universe domain: + +.. code:: sh + + # Override the KMS endpoint directly + $ export SOPS_GCP_KMS_ENDPOINT=cloudkms.example.com:443 + + # Or derive the endpoint from the universe domain (cloudkms.:443) + $ export SOPS_GCP_KMS_UNIVERSE_DOMAIN=example.com + +.. note:: ``SOPS_GCP_KMS_ENDPOINT`` takes precedence over ``SOPS_GCP_KMS_UNIVERSE_DOMAIN`` if both are set. + Encrypting/decrypting with GCP KMS requires a KMS ResourceID. You can use the cloud console the get the ResourceID or you can create one using the gcloud sdk: diff --git a/gcpkms/keysource.go b/gcpkms/keysource.go index d9cc55089..2acbdd9c0 100644 --- a/gcpkms/keysource.go +++ b/gcpkms/keysource.go @@ -30,6 +30,14 @@ const ( // SopsGCPKMSClientTypeEnv is the environment variable used to specify the // GCP KMS client type. Valid values are "grpc" (default) and "rest". SopsGCPKMSClientTypeEnv = "SOPS_GCP_KMS_CLIENT_TYPE" + // SopsGCPKMSEndpointEnv overrides the GCP KMS endpoint URL. Useful for + // sovereign cloud environments that expose a GCP-compatible KMS API at a + // non-standard endpoint (e.g. S3NS/Thales TPC: cloudkms.s3nsapis.fr). + SopsGCPKMSEndpointEnv = "SOPS_GCP_KMS_ENDPOINT" + // SopsGCPKMSUniverseDomainEnv sets the universe domain for the GCP KMS + // client, which derives the endpoint as cloudkms.{UNIVERSE_DOMAIN}:443. + // Example: "s3nsapis.fr" for S3NS/Thales TPC. + SopsGCPKMSUniverseDomainEnv = "SOPS_GCP_KMS_UNIVERSE_DOMAIN" // KeyTypeIdentifier is the string used to identify a GCP KMS MasterKey. KeyTypeIdentifier = "gcp_kms" ) @@ -320,6 +328,12 @@ func (key *MasterKey) newKMSClient(ctx context.Context) (*kms.KeyManagementClien // Add extra options. opts = append(opts, key.clientOpts...) + if endpoint := os.Getenv(SopsGCPKMSEndpointEnv); endpoint != "" { + opts = append(opts, option.WithEndpoint(endpoint)) + } else if ud := os.Getenv(SopsGCPKMSUniverseDomainEnv); ud != "" { + opts = append(opts, option.WithUniverseDomain(ud)) + } + // Select client type based on inputs. clientType := strings.ToLower(os.Getenv(SopsGCPKMSClientTypeEnv)) var client *kms.KeyManagementClient diff --git a/gcpkms/keysource_test.go b/gcpkms/keysource_test.go index e0365de34..fa50b6bb8 100644 --- a/gcpkms/keysource_test.go +++ b/gcpkms/keysource_test.go @@ -189,6 +189,32 @@ func TestMasterKey_createCloudKMSService_withoutCredentials(t *testing.T) { assert.ErrorContains(t, err, "credentials: could not find default credentials") } +func TestMasterKey_createCloudKMSService_withEndpointEnv(t *testing.T) { + t.Setenv(SopsGCPKMSEndpointEnv, "cloudkms.example.com:443") + t.Setenv(SopsGoogleCredentialsOAuthTokenEnv, "token") + + masterKey := MasterKey{ + ResourceID: testResourceID, + } + + client, err := masterKey.newKMSClient(context.Background()) + assert.NoError(t, err) + assert.Contains(t, client.Connection().Target(), "cloudkms.example.com") +} + +func TestMasterKey_createCloudKMSService_withUniverseDomainEnv(t *testing.T) { + t.Setenv(SopsGCPKMSUniverseDomainEnv, "example.com") + t.Setenv(SopsGoogleCredentialsOAuthTokenEnv, "token") + + masterKey := MasterKey{ + ResourceID: testResourceID, + } + + client, err := masterKey.newKMSClient(context.Background()) + assert.NoError(t, err) + assert.Contains(t, client.Connection().Target(), "cloudkms.example.com") +} + func newGRPCServer(port string) *grpc.ClientConn { serv := grpc.NewServer() kmspb.RegisterKeyManagementServiceServer(serv, &mockKeyManagement)