From 0aad9a42caf452efad5997f6ebd0319bd619b677 Mon Sep 17 00:00:00 2001 From: Cameron Thornton Date: Wed, 29 May 2024 16:27:44 -0500 Subject: [PATCH] Fix universe_domain for ADC and access token auth cases (#10517) Co-authored-by: Riley Karson --- .../terraform/provider/provider.go.erb | 44 +++++-------- .../terraform/transport/config.go.erb | 63 ++++++++++++++----- 2 files changed, 63 insertions(+), 44 deletions(-) diff --git a/mmv1/third_party/terraform/provider/provider.go.erb b/mmv1/third_party/terraform/provider/provider.go.erb index fd3b7927461b..2400c070bc7d 100644 --- a/mmv1/third_party/terraform/provider/provider.go.erb +++ b/mmv1/third_party/terraform/provider/provider.go.erb @@ -3,7 +3,6 @@ package provider import ( "context" - "encoding/json" "fmt" "os" "strings" @@ -279,35 +278,11 @@ func ProviderConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr }) } - // set universe_domain based on the service account key file. - if config.Credentials != "" { - contents, _, err := verify.PathOrContents(config.Credentials) - if err != nil { - return nil, diag.FromErr(fmt.Errorf("error loading service account credentials: %s", err)) - } - var content map[string]any - - if err := json.Unmarshal([]byte(contents), &content); err != nil { - return nil, diag.FromErr(err) - } - - if content["universe_domain"] != nil { - config.UniverseDomain = content["universe_domain"].(string) - } - } - - // Check if the user provided a value from the universe_domain field other than the default - if v, ok := d.GetOk("universe_domain"); ok && v.(string) != "googleapis.com" { - if config.UniverseDomain == "" { - return nil, diag.FromErr(fmt.Errorf("Universe domain mismatch: '%s' supplied directly to Terraform with no matching universe domain in credentials. Credentials with no 'universe_domain' set are assumed to be in the default universe.", v)) - } else if v.(string) != config.UniverseDomain { - if _, err := os.Stat(config.Credentials); err == nil { - return nil, diag.FromErr(fmt.Errorf("Universe domain mismatch: '%s' does not match the universe domain '%s' already set in the credential file '%s'. The 'universe_domain' provider configuration can not be used to override the universe domain that is defined in the active credential. Set the 'universe_domain' provider configuration when universe domain information is not already available in the credential, e.g. when authenticating with a JWT token.", v, config.UniverseDomain, config.Credentials)) - } else { - return nil, diag.FromErr(fmt.Errorf("Universe domain mismatch: '%s' does not match the universe domain '%s' supplied directly to Terraform. The 'universe_domain' provider configuration can not be used to override the universe domain that is defined in the active credential. Set the 'universe_domain' provider configuration when universe domain information is not already available in the credential, e.g. when authenticating with a JWT token.", v, config.UniverseDomain)) - } - } + // Set the universe domain to the configured value, if any + if v, ok := d.GetOk("universe_domain"); ok { + config.UniverseDomain = v.(string) } + // Configure DCL basePath transport_tpg.ProviderDCLConfigure(d, &config) @@ -406,6 +381,17 @@ func ProviderConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr return nil, diag.FromErr(err) } + // Verify that universe domains match between credentials and configuration + if v, ok := d.GetOk("universe_domain"); ok { + if config.UniverseDomain == "" && v.(string) != "googleapis.com" { // v can't be "", as it wouldn't pass `ok` above + return nil, diag.FromErr(fmt.Errorf("Universe domain mismatch: '%s' supplied directly to Terraform with no matching universe domain in credentials. Credentials with no 'universe_domain' set are assumed to be in the default universe.", v)) + } else if v.(string) != config.UniverseDomain && !(config.UniverseDomain == "" && v.(string) == "googleapis.com") { + return nil, diag.FromErr(fmt.Errorf("Universe domain mismatch: '%s' does not match the universe domain '%s' supplied directly to Terraform. The 'universe_domain' provider configuration must match the universe domain supplied by credentials.", config.UniverseDomain, v)) + } + } else if config.UniverseDomain != "" && config.UniverseDomain != "googleapis.com" { + return nil, diag.FromErr(fmt.Errorf("Universe domain mismatch: Universe domain '%s' was found in credentials without a corresponding 'universe_domain' provider configuration set. Please set 'universe_domain' to '%s' or use different credentials.", config.UniverseDomain, config.UniverseDomain)) + } + return &config, nil } diff --git a/mmv1/third_party/terraform/transport/config.go.erb b/mmv1/third_party/terraform/transport/config.go.erb index 3bace4f1d380..7c9667635655 100644 --- a/mmv1/third_party/terraform/transport/config.go.erb +++ b/mmv1/third_party/terraform/transport/config.go.erb @@ -1136,6 +1136,7 @@ type StaticTokenSource struct { // If initialCredentialsOnly is true, don't follow the impersonation settings and return the initial set of creds // instead. func (c *Config) GetCredentials(clientScopes []string, initialCredentialsOnly bool) (googleoauth.Credentials, error) { + // UniverseDomain is assumed to be the previously set provider-configured value for access tokens if c.AccessToken != "" { contents, _, err := verify.PathOrContents(c.AccessToken) if err != nil { @@ -1159,12 +1160,25 @@ func (c *Config) GetCredentials(clientScopes []string, initialCredentialsOnly bo }, nil } + // UniverseDomain is set by the credential file's "universe_domain" field if c.Credentials != "" { contents, _, err := verify.PathOrContents(c.Credentials) if err != nil { return googleoauth.Credentials{}, fmt.Errorf("error loading credentials: %s", err) } + var content map[string]any + if err := json.Unmarshal([]byte(contents), &content); err != nil { + return googleoauth.Credentials{}, fmt.Errorf("error unmarshaling credentials: %s", err) + } + + if content["universe_domain"] != nil { + c.UniverseDomain = content["universe_domain"].(string) + } else { + // Unset UniverseDomain if not found in credentials file + c.UniverseDomain = "" + } + if c.ImpersonateServiceAccount != "" && !initialCredentialsOnly { opts := []option.ClientOption{option.WithCredentialsJSON([]byte(contents)), option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...), option.WithScopes(clientScopes...)} creds, err := transport.Creds(context.TODO(), opts...) @@ -1194,33 +1208,52 @@ func (c *Config) GetCredentials(clientScopes []string, initialCredentialsOnly bo } } + var creds *googleoauth.Credentials + var err error if c.ImpersonateServiceAccount != "" && !initialCredentialsOnly { opts := option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...) - creds, err := transport.Creds(context.TODO(), opts, option.WithScopes(clientScopes...)) + creds, err = transport.Creds(context.TODO(), opts, option.WithScopes(clientScopes...)) if err != nil { return googleoauth.Credentials{}, err } + } else { + log.Printf("[INFO] Authenticating using DefaultClient...") + log.Printf("[INFO] -- Scopes: %s", clientScopes) - return *creds, nil + if c.UniverseDomain != "" && c.UniverseDomain != "googleapis.com" { + log.Printf("[INFO] -- Sending JwtWithScope option") + creds, err = transport.Creds(context.Background(), option.WithScopes(clientScopes...), internaloption.EnableJwtWithScope()) + if err != nil { + return googleoauth.Credentials{}, fmt.Errorf("Attempted to load application default credentials since neither `credentials` nor `access_token` was set in the provider block. No credentials loaded. To use your gcloud credentials, run 'gcloud auth application-default login'. Original error: %w", err) + } + } else { + creds, err = transport.Creds(context.Background(), option.WithScopes(clientScopes...)) + if err != nil { + return googleoauth.Credentials{}, fmt.Errorf("Attempted to load application default credentials since neither `credentials` nor `access_token` was set in the provider block. No credentials loaded. To use your gcloud credentials, run 'gcloud auth application-default login'. Original error: %w", err) + } + } } - log.Printf("[INFO] Authenticating using DefaultClient...") - log.Printf("[INFO] -- Scopes: %s", clientScopes) - - if c.UniverseDomain != "" && c.UniverseDomain != "googleapis.com" { - log.Printf("[INFO] -- Sending JwtWithScope option") - creds, err := transport.Creds(context.Background(), option.WithScopes(clientScopes...), internaloption.EnableJwtWithScope()) + if creds.JSON != nil { + var content map[string]any + if err := json.Unmarshal([]byte(creds.JSON), &content); err != nil { + log.Printf("[WARN] error unmarshaling credentials, skipping Universe Domain detection") + c.UniverseDomain = "" + } else if content["universe_domain"] != nil { + c.UniverseDomain = content["universe_domain"].(string) + } else { + // Unset UniverseDomain if not found in ADC credentials file + c.UniverseDomain = "" + } + } else { + // creds.GetUniverseDomain may retrieve a domain from the metadata server + ud, err := creds.GetUniverseDomain() if err != nil { - return googleoauth.Credentials{}, fmt.Errorf("Attempted to load application default credentials since neither `credentials` nor `access_token` was set in the provider block. No credentials loaded. To use your gcloud credentials, run 'gcloud auth application-default login'. Original error: %w", err) + log.Printf("[WARN] Error retrieving universe domain: %s", err) } - return *creds, nil + c.UniverseDomain = ud } - creds, err := transport.Creds(context.Background(), option.WithScopes(clientScopes...)) - if err != nil { - return googleoauth.Credentials{}, fmt.Errorf("Attempted to load application default credentials since neither `credentials` nor `access_token` was set in the provider block. No credentials loaded. To use your gcloud credentials, run 'gcloud auth application-default login'. Original error: %w", err) - } - return *creds, nil }