From c338076e48a8274e8d7c8020b3a4647d41a23ced Mon Sep 17 00:00:00 2001 From: Mathieu Goulin Date: Sun, 23 Feb 2025 20:15:11 +0100 Subject: [PATCH] feat: Add support for universe_domain parameter (#242) This commit introduces a new `universe_domain` parameter to the plugin configuration, allowing users to specify the Google Cloud environment for client connections. This enables support for specialized offerings and sovereign controls beyond the default googleapis.com. Key changes: - Added `universe_domain` field to the plugin configuration. - Modified IAM client creation to use the configured `universe_domain`. - Updated credentials parsing to utilize `cloud.google.com/go/auth/credentials.DetectDefault` and support local-signing JWT generation. - Adjusted token generation to use `cloud.google.com/go/auth/credentials.DetectDefault` for credential handling. --- plugin/backend.go | 22 ++++++++++++++++++++-- plugin/path_config.go | 17 +++++++++++++++++ plugin/path_config_test.go | 1 + plugin/secrets_access_token.go | 12 +++++++++--- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/plugin/backend.go b/plugin/backend.go index 1830edd5..ce426737 100644 --- a/plugin/backend.go +++ b/plugin/backend.go @@ -11,6 +11,8 @@ import ( "sync" "time" + gauth "cloud.google.com/go/auth/credentials" + "cloud.google.com/go/auth/oauth2adapt" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-gcp-common/gcputil" @@ -142,8 +144,18 @@ func (b *backend) IAMAdminClient(s logical.Storage) (*iam.Service, error) { return nil, errwrap.Wrapf("failed to create IAM HTTP client: {{err}}", err) } + ctx := context.Background() + + cfg, err := getConfig(ctx, s) + if err != nil { + return nil, err + } + if cfg == nil { + cfg = &config{} + } + client, err := b.cache.Fetch("iam", cacheTime, func() (interface{}, error) { - client, err := iam.NewService(context.Background(), option.WithHTTPClient(httpClient)) + client, err := iam.NewService(context.Background(), option.WithHTTPClient(httpClient), option.WithUniverseDomain(cfg.UniverseDomain)) if err != nil { return nil, errwrap.Wrapf("failed to create IAM client: {{err}}", err) } @@ -203,10 +215,16 @@ func (b *backend) credentials(s logical.Storage) (*google.Credentials, error) { // default application credentials. var creds *google.Credentials if len(credBytes) > 0 { - creds, err = google.CredentialsFromJSON(ctx, credBytes, iam.CloudPlatformScope) + scopes := []string{"openid", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/appengine.admin", "https://www.googleapis.com/auth/sqlservice.login", "https://www.googleapis.com/auth/compute"} + gcred, err := gauth.DetectDefault(&gauth.DetectOptions{ + Scopes: scopes, + CredentialsJSON: credBytes, + UseSelfSignedJWT: true, + UniverseDomain: cfg.UniverseDomain}) if err != nil { return nil, errwrap.Wrapf("failed to parse credentials: {{err}}", err) } + creds = oauth2adapt.Oauth2CredentialsFromAuthCredentials(gcred) } else if cfg.IdentityTokenAudience != "" { ts := &PluginIdentityTokenSupplier{ sys: b.System(), diff --git a/plugin/path_config.go b/plugin/path_config.go index 37be470a..56d65ce5 100644 --- a/plugin/path_config.go +++ b/plugin/path_config.go @@ -18,6 +18,8 @@ import ( "github.com/hashicorp/vault/sdk/rotation" ) +const DefaultUniverseDomain = "googleapis.com" + func pathConfig(b *backend) *framework.Path { p := &framework.Path{ Pattern: "config", @@ -43,6 +45,12 @@ func pathConfig(b *backend) *framework.Path { Type: framework.TypeString, Description: `Email ID for the Service Account to impersonate for Workload Identity Federation.`, }, + "universe_domain": { + Type: framework.TypeString, + Required: false, + Default: "googleapis.com", + Description: `universe_domain specifies the Google Cloud environment a client connects to, enabling specialized offerings and sovereign controls beyond the default googleapis.com`, + }, }, Operations: map[logical.Operation]framework.OperationHandler{ @@ -84,6 +92,7 @@ func (b *backend) pathConfigRead(ctx context.Context, req *logical.Request, data "ttl": int64(cfg.TTL / time.Second), "max_ttl": int64(cfg.MaxTTL / time.Second), "service_account_email": cfg.ServiceAccountEmail, + "universe_domain": cfg.UniverseDomain, } cfg.PopulatePluginIdentityTokenData(configData) @@ -198,6 +207,13 @@ func (b *backend) pathConfigWrite(ctx context.Context, req *logical.Request, dat return logical.ErrorResponse("error registering rotation job: %s", err), nil } } + // Update Universe-Domain. + UniverseDomainRaw, ok := data.GetOk("universe_domain") + if ok { + cfg.UniverseDomain = UniverseDomainRaw.(string) + } else { + cfg.UniverseDomain = DefaultUniverseDomain + } entry, err := logical.StorageEntryJSON("config", cfg) if err != nil { @@ -232,6 +248,7 @@ type config struct { ServiceAccountEmail string pluginidentityutil.PluginIdentityTokenParams automatedrotationutil.AutomatedRotationParams + UniverseDomain string } func getConfig(ctx context.Context, s logical.Storage) (*config, error) { diff --git a/plugin/path_config_test.go b/plugin/path_config_test.go index 0e75c951..befad74e 100644 --- a/plugin/path_config_test.go +++ b/plugin/path_config_test.go @@ -44,6 +44,7 @@ func TestConfig(t *testing.T) { "rotation_period": float64(0), "rotation_schedule": "", "disable_automated_rotation": false, + "universe_domain": "googleapis.com", } testConfigRead(t, b, reqStorage, expected) diff --git a/plugin/secrets_access_token.go b/plugin/secrets_access_token.go index cbbf3dbc..080aa64b 100644 --- a/plugin/secrets_access_token.go +++ b/plugin/secrets_access_token.go @@ -8,11 +8,12 @@ import ( "encoding/base64" "time" + gauth "cloud.google.com/go/auth/credentials" + "cloud.google.com/go/auth/oauth2adapt" "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" "golang.org/x/oauth2" - "golang.org/x/oauth2/google" ) func (b *backend) secretAccessTokenResponse(ctx context.Context, s logical.Storage, tokenGen *TokenGenerator) (*logical.Response, error) { @@ -40,12 +41,17 @@ func (tg *TokenGenerator) getAccessToken(ctx context.Context) (*oauth2.Token, er return nil, errwrap.Wrapf("could not b64-decode key data: {{err}}", err) } - cfg, err := google.JWTConfigFromJSON(jsonBytes, tg.Scopes...) + gcred, err := gauth.DetectDefault(&gauth.DetectOptions{ + Scopes: tg.Scopes, + CredentialsJSON: jsonBytes, + UseSelfSignedJWT: true, + }) if err != nil { return nil, errwrap.Wrapf("could not generate token JWT config: {{err}}", err) } + cfg := oauth2adapt.Oauth2CredentialsFromAuthCredentials(gcred) - tkn, err := cfg.TokenSource(ctx).Token() + tkn, err := cfg.TokenSource.Token() if err != nil { return nil, errwrap.Wrapf("got error while creating OAuth2 token: {{err}}", err) }