From fa9675659bfd93e2b078a3968badc2e903c56fac Mon Sep 17 00:00:00 2001 From: upodroid Date: Mon, 21 Sep 2020 11:12:49 +0100 Subject: [PATCH 01/14] add service account impersonation --- ..._source_google_service_account_id_token.go | 4 +- .../data_source_storage_object_signed_url.go | 14 +-- ...ce_google_service_account_id_token_test.go | 15 +--- .../resource_compute_instance_migrate_test.go | 7 +- .../terraform/utils/bootstrap_utils_test.go | 7 +- third_party/terraform/utils/config.go.erb | 50 ++++------- third_party/terraform/utils/config_test.go | 88 +++---------------- .../terraform/utils/gcp_sweeper_test.go | 5 +- third_party/terraform/utils/provider.go.erb | 32 +++---- 9 files changed, 52 insertions(+), 170 deletions(-) diff --git a/third_party/terraform/data_sources/data_source_google_service_account_id_token.go b/third_party/terraform/data_sources/data_source_google_service_account_id_token.go index 7c7b32a9488c..bafb2d46b6f0 100644 --- a/third_party/terraform/data_sources/data_source_google_service_account_id_token.go +++ b/third_party/terraform/data_sources/data_source_google_service_account_id_token.go @@ -74,8 +74,8 @@ func dataSourceGoogleServiceAccountIdTokenRead(d *schema.ResourceData, meta inte ts := creds.TokenSource - // If the source token is just an access_token, all we can do is use the iamcredentials api to get an id_token - if _, ok := ts.(staticTokenSource); ok { + // The idToken doesn't support impersonatedcredentials method yet :( https://github.com/googleapis/google-api-go-client/blob/master/idtoken/idtoken.go#L67 + if config.ImpersonateServiceAccount != "" { // Use // https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken service := config.clientIamCredentials diff --git a/third_party/terraform/data_sources/data_source_storage_object_signed_url.go b/third_party/terraform/data_sources/data_source_storage_object_signed_url.go index a14a0ec5dd14..9041794fcc90 100644 --- a/third_party/terraform/data_sources/data_source_storage_object_signed_url.go +++ b/third_party/terraform/data_sources/data_source_storage_object_signed_url.go @@ -54,6 +54,7 @@ func dataSourceGoogleSignedUrl() *schema.Resource { Type: schema.TypeString, Sensitive: true, Optional: true, + Removed: "The provider has deprecated specifying Service Accounts keys through the credentials file. Set the key using the GOOGLE_APPLICATION_CREDENTIALS environment value as mentioned in the docs", }, "duration": { Type: schema.TypeString, @@ -174,21 +175,10 @@ func dataSourceGoogleSignedUrlRead(d *schema.ResourceData, meta interface{}) err // 3. A JSON file whose path is specified by the // GOOGLE_APPLICATION_CREDENTIALS environment variable. func loadJwtConfig(d *schema.ResourceData, meta interface{}) (*jwt.Config, error) { - config := meta.(*Config) - credentials := "" - if v, ok := d.GetOk("credentials"); ok { - log.Println("[DEBUG] using data source credentials to sign URL") - credentials = v.(string) - - } else if config.Credentials != "" { - log.Println("[DEBUG] using provider credentials to sign URL") - credentials = config.Credentials - - } else if filename := os.Getenv(googleCredentialsEnvVar); filename != "" { + if filename := os.Getenv(googleCredentialsEnvVar); filename != "" { log.Println("[DEBUG] using env GOOGLE_APPLICATION_CREDENTIALS credentials to sign URL") credentials = filename - } if strings.TrimSpace(credentials) != "" { diff --git a/third_party/terraform/tests/data_source_google_service_account_id_token_test.go b/third_party/terraform/tests/data_source_google_service_account_id_token_test.go index 9ef4b2599358..b2992e116adf 100644 --- a/third_party/terraform/tests/data_source_google_service_account_id_token_test.go +++ b/third_party/terraform/tests/data_source_google_service_account_id_token_test.go @@ -91,21 +91,10 @@ func TestAccDataSourceGoogleServiceAccountIdToken_impersonation(t *testing.T) { func testAccCheckGoogleServiceAccountIdToken_impersonation_datasource(targetAudience string, targetServiceAccount string) string { return fmt.Sprintf(` -data "google_service_account_access_token" "default" { - target_service_account = "%s" - scopes = ["userinfo-email", "https://www.googleapis.com/auth/cloud-platform"] - lifetime = "30s" -} - -provider google { - alias = "impersonated" - access_token = data.google_service_account_access_token.default.access_token -} - data "google_service_account_id_token" "default" { - provider = google.impersonated target_service_account = "%s" target_audience = "%s" + include_email = true } -`, targetServiceAccount, targetServiceAccount, targetAudience) +`, targetServiceAccount, targetAudience) } diff --git a/third_party/terraform/tests/resource_compute_instance_migrate_test.go b/third_party/terraform/tests/resource_compute_instance_migrate_test.go index 3790cc9e8b74..45cf71c099ad 100644 --- a/third_party/terraform/tests/resource_compute_instance_migrate_test.go +++ b/third_party/terraform/tests/resource_compute_instance_migrate_test.go @@ -936,10 +936,9 @@ func getInitializedConfig(t *testing.T) *Config { testAccPreCheck(t) config := &Config{ - Project: getTestProjectFromEnv(), - Credentials: getTestCredsFromEnv(), - Region: getTestRegionFromEnv(), - Zone: getTestZoneFromEnv(), + Project: getTestProjectFromEnv(), + Region: getTestRegionFromEnv(), + Zone: getTestZoneFromEnv(), } ConfigureBasePaths(config) diff --git a/third_party/terraform/utils/bootstrap_utils_test.go b/third_party/terraform/utils/bootstrap_utils_test.go index 267c8a1f3bce..1b3184469900 100644 --- a/third_party/terraform/utils/bootstrap_utils_test.go +++ b/third_party/terraform/utils/bootstrap_utils_test.go @@ -322,10 +322,9 @@ func BootstrapConfig(t *testing.T) *Config { } config := &Config{ - Credentials: getTestCredsFromEnv(), - Project: getTestProjectFromEnv(), - Region: getTestRegionFromEnv(), - Zone: getTestZoneFromEnv(), + Project: getTestProjectFromEnv(), + Region: getTestRegionFromEnv(), + Zone: getTestZoneFromEnv(), } ConfigureBasePaths(config) diff --git a/third_party/terraform/utils/config.go.erb b/third_party/terraform/utils/config.go.erb index a1d3a2816173..1e261a10bec1 100644 --- a/third_party/terraform/utils/config.go.erb +++ b/third_party/terraform/utils/config.go.erb @@ -12,7 +12,6 @@ import ( "google.golang.org/api/option" "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/terraform-plugin-sdk/helper/logging" - "github.com/hashicorp/terraform-plugin-sdk/helper/pathorcontents" "github.com/hashicorp/terraform-plugin-sdk/httpclient" "golang.org/x/oauth2" @@ -59,20 +58,21 @@ import ( sqladmin "google.golang.org/api/sqladmin/v1beta4" "google.golang.org/api/storage/v1" "google.golang.org/api/storagetransfer/v1" + "google.golang.org/api/transport" ) // Config is the configuration structure used to instantiate the Google // provider. type Config struct { - Credentials string - AccessToken string - Project string - Region string - Zone string - Scopes []string - BatchingConfig *batchingConfig - UserProjectOverride bool - RequestTimeout time.Duration + ImpersonateServiceAccount string + ImpersonateServiceAccountDelegates []string + Project string + Region string + Zone string + Scopes []string + BatchingConfig *batchingConfig + UserProjectOverride bool + RequestTimeout time.Duration // PollInterval is passed to resource.StateChangeConf in common_operation.go // It controls the interval at which we poll for successful operations PollInterval time.Duration @@ -652,35 +652,15 @@ type staticTokenSource struct { } func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, error) { - if c.AccessToken != "" { - contents, _, err := pathorcontents.Read(c.AccessToken) - if err != nil { - return googleoauth.Credentials{}, fmt.Errorf("Error loading access token: %s", err) - } - - log.Printf("[INFO] Authenticating using configured Google JSON 'access_token'...") - log.Printf("[INFO] -- Scopes: %s", clientScopes) - token := &oauth2.Token{AccessToken: contents} - - return googleoauth.Credentials{ - TokenSource: staticTokenSource{oauth2.StaticTokenSource(token)}, - }, nil - } - if c.Credentials != "" { - contents, _, err := pathorcontents.Read(c.Credentials) + if c.ImpersonateServiceAccount != "" { + opts := option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...) + creds, err := transport.Creds(context.TODO(), opts, option.WithScopes(clientScopes...)) if err != nil { - return googleoauth.Credentials{}, fmt.Errorf("error loading credentials: %s", err) + return googleoauth.Credentials{}, err } - - creds, err := googleoauth.CredentialsFromJSON(c.context, []byte(contents), clientScopes...) - if err != nil { - return googleoauth.Credentials{}, fmt.Errorf("unable to parse credentials from '%s': %s", contents, err) - } - - log.Printf("[INFO] Authenticating using configured Google JSON 'credentials'...") - log.Printf("[INFO] -- Scopes: %s", clientScopes) return *creds, nil + } log.Printf("[INFO] Authenticating using DefaultClient...") diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index 9ce39abb5957..ed95db494ed8 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -3,65 +3,14 @@ package google import ( "context" "fmt" - "io/ioutil" "os" "testing" "time" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" - "golang.org/x/oauth2/google" ) const testFakeCredentialsPath = "./test-fixtures/fake_account.json" -const testOauthScope = "https://www.googleapis.com/auth/compute" - -func TestConfigLoadAndValidate_accountFilePath(t *testing.T) { - config := &Config{ - Credentials: testFakeCredentialsPath, - Project: "my-gce-project", - Region: "us-central1", - } - - ConfigureBasePaths(config) - - err := config.LoadAndValidate(context.Background()) - if err != nil { - t.Fatalf("error: %v", err) - } -} - -func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) { - contents, err := ioutil.ReadFile(testFakeCredentialsPath) - if err != nil { - t.Fatalf("error: %v", err) - } - config := &Config{ - Credentials: string(contents), - Project: "my-gce-project", - Region: "us-central1", - } - - ConfigureBasePaths(config) - - err = config.LoadAndValidate(context.Background()) - if err != nil { - t.Fatalf("error: %v", err) - } -} - -func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) { - config := &Config{ - Credentials: "{this is not json}", - Project: "my-gce-project", - Region: "us-central1", - } - - ConfigureBasePaths(config) - - if config.LoadAndValidate(context.Background()) == nil { - t.Fatalf("expected error, but got nil") - } -} func TestAccConfigLoadValidate_credentials(t *testing.T) { if os.Getenv(resource.TestEnvVar) == "" { @@ -69,13 +18,11 @@ func TestAccConfigLoadValidate_credentials(t *testing.T) { } testAccPreCheck(t) - creds := getTestCredsFromEnv() proj := getTestProjectFromEnv() config := &Config{ - Credentials: creds, - Project: proj, - Region: "us-central1", + Project: proj, + Region: "us-central1", } ConfigureBasePaths(config) @@ -91,34 +38,24 @@ func TestAccConfigLoadValidate_credentials(t *testing.T) { } } -func TestAccConfigLoadValidate_accessToken(t *testing.T) { +func TestAccConfigLoadValidate_impersonated(t *testing.T) { if os.Getenv(resource.TestEnvVar) == "" { t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", resource.TestEnvVar)) } testAccPreCheck(t) - creds := getTestCredsFromEnv() + serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATED_SERVICE_ACCOUNT"}) proj := getTestProjectFromEnv() - c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), testOauthScope) - if err != nil { - t.Fatalf("invalid test credentials: %s", err) - } - - token, err := c.TokenSource.Token() - if err != nil { - t.Fatalf("Unable to generate test access token: %s", err) - } - config := &Config{ - AccessToken: token.AccessToken, - Project: proj, - Region: "us-central1", + ImpersonateServiceAccount: serviceaccount, + Project: proj, + Region: "us-central1", } ConfigureBasePaths(config) - err = config.LoadAndValidate(context.Background()) + err := config.LoadAndValidate(context.Background()) if err != nil { t.Fatalf("error: %v", err) } @@ -131,10 +68,9 @@ func TestAccConfigLoadValidate_accessToken(t *testing.T) { func TestConfigLoadAndValidate_customScopes(t *testing.T) { config := &Config{ - Credentials: testFakeCredentialsPath, - Project: "my-gce-project", - Region: "us-central1", - Scopes: []string{"https://www.googleapis.com/auth/compute"}, + Project: "my-gce-project", + Region: "us-central1", + Scopes: []string{"https://www.googleapis.com/auth/compute"}, } ConfigureBasePaths(config) @@ -159,7 +95,6 @@ func TestConfigLoadAndValidate_defaultBatchingConfig(t *testing.T) { t.Fatalf("unexpected error: %v", err) } config := &Config{ - Credentials: testFakeCredentialsPath, Project: "my-gce-project", Region: "us-central1", BatchingConfig: batchCfg, @@ -196,7 +131,6 @@ func TestConfigLoadAndValidate_customBatchingConfig(t *testing.T) { } config := &Config{ - Credentials: testFakeCredentialsPath, Project: "my-gce-project", Region: "us-central1", BatchingConfig: batchCfg, diff --git a/third_party/terraform/utils/gcp_sweeper_test.go b/third_party/terraform/utils/gcp_sweeper_test.go index 757821e9744e..7fc8aad63859 100644 --- a/third_party/terraform/utils/gcp_sweeper_test.go +++ b/third_party/terraform/utils/gcp_sweeper_test.go @@ -32,9 +32,8 @@ func sharedConfigForRegion(region string) (*Config, error) { } conf := &Config{ - Credentials: creds, - Region: region, - Project: project, + Region: region, + Project: project, } ConfigureBasePaths(conf) diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index 3587bd93e384..6ea5ac908561 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -20,24 +20,18 @@ var mutexKV = mutexkv.NewMutexKV() func Provider() terraform.ResourceProvider { provider := &schema.Provider{ Schema: map[string]*schema.Schema{ - "credentials": &schema.Schema{ + "impersonate_service_account": { Type: schema.TypeString, Optional: true, DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "GOOGLE_CREDENTIALS", - "GOOGLE_CLOUD_KEYFILE_JSON", - "GCLOUD_KEYFILE_JSON", + "GOOGLE_IMPERSONATE_SERVICE_ACCOUNT", }, nil), - ValidateFunc: validateCredentials, }, - "access_token": { - Type: schema.TypeString, + "impersonate_service_account_delegates": { + Type: schema.TypeList, Optional: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "GOOGLE_OAUTH_ACCESS_TOKEN", - }, nil), - ConflictsWith: []string{"credentials"}, + Elem: &schema.Schema{Type: schema.TypeString}, }, "project": &schema.Schema{ @@ -437,18 +431,16 @@ func providerConfigure(d *schema.ResourceData, p *schema.Provider, terraformVers } } // Add credential source - if v, ok := d.GetOk("access_token"); ok { - config.AccessToken = v.(string) - } else if v, ok := d.GetOk("credentials"); ok { - config.Credentials = v.(string) + if v, ok := d.GetOk("impersonate_service_account"); ok { + config.ImpersonateServiceAccount = v.(string) } - scopes := d.Get("scopes").([]interface{}) - if len(scopes) > 0 { - config.Scopes = make([]string, len(scopes)) + delegates := d.Get("impersonate_service_account_delegates").([]interface{}) + if len(delegates) > 0 { + config.ImpersonateServiceAccountDelegates = make([]string, len(delegates)) } - for i, scope := range scopes { - config.Scopes[i] = scope.(string) + for i, delegate := range delegates { + config.ImpersonateServiceAccountDelegates[i] = delegate.(string) } batchCfg, err := expandProviderBatchingConfig(d.Get("batching")) From a1d06ed349ade8eacd3b1eec56e993b454f28d18 Mon Sep 17 00:00:00 2001 From: upodroid Date: Mon, 21 Sep 2020 14:00:07 +0100 Subject: [PATCH 02/14] fix conflicts --- .../data_source_storage_object_signed_url.go | 6 --- third_party/terraform/utils/config.go.erb | 44 +------------------ third_party/terraform/utils/config_test.go | 2 +- 3 files changed, 2 insertions(+), 50 deletions(-) diff --git a/third_party/terraform/data_sources/data_source_storage_object_signed_url.go b/third_party/terraform/data_sources/data_source_storage_object_signed_url.go index 04e9ecc64e9a..c15cfa61c99d 100644 --- a/third_party/terraform/data_sources/data_source_storage_object_signed_url.go +++ b/third_party/terraform/data_sources/data_source_storage_object_signed_url.go @@ -49,12 +49,6 @@ func dataSourceGoogleSignedUrl() *schema.Resource { Optional: true, Default: "", }, - "credentials": { - Type: schema.TypeString, - Sensitive: true, - Optional: true, - Removed: "The provider has deprecated specifying Service Accounts keys through the credentials file. Set the key using the GOOGLE_APPLICATION_CREDENTIALS environment value as mentioned in the docs", - }, "duration": { Type: schema.TypeString, Optional: true, diff --git a/third_party/terraform/utils/config.go.erb b/third_party/terraform/utils/config.go.erb index 1e376bfa636c..815f4336c585 100644 --- a/third_party/terraform/utils/config.go.erb +++ b/third_party/terraform/utils/config.go.erb @@ -12,12 +12,7 @@ import ( "google.golang.org/api/option" "github.com/hashicorp/go-cleanhttp" -<<<<<<< HEAD - "github.com/hashicorp/terraform-plugin-sdk/helper/logging" - "github.com/hashicorp/terraform-plugin-sdk/httpclient" -======= "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" ->>>>>>> master "golang.org/x/oauth2" googleoauth "golang.org/x/oauth2/google" @@ -69,28 +64,16 @@ import ( // Config is the configuration structure used to instantiate the Google // provider. type Config struct { -<<<<<<< HEAD ImpersonateServiceAccount string ImpersonateServiceAccountDelegates []string Project string Region string + BillingProject string Zone string Scopes []string BatchingConfig *batchingConfig UserProjectOverride bool RequestTimeout time.Duration -======= - Credentials string - AccessToken string - Project string - BillingProject string - Region string - Zone string - Scopes []string - BatchingConfig *batchingConfig - UserProjectOverride bool - RequestTimeout time.Duration ->>>>>>> master // PollInterval is passed to resource.StateChangeConf in common_operation.go // It controls the interval at which we poll for successful operations PollInterval time.Duration @@ -657,36 +640,11 @@ func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, erro return creds.TokenSource, nil } -// staticTokenSource is used to be able to identify static token sources without reflection. -type staticTokenSource struct { - oauth2.TokenSource -} - func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, error) { -<<<<<<< HEAD if c.ImpersonateServiceAccount != "" { opts := option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...) creds, err := transport.Creds(context.TODO(), opts, option.WithScopes(clientScopes...)) -======= - if c.AccessToken != "" { - contents, _, err := pathOrContents(c.AccessToken) - if err != nil { - return googleoauth.Credentials{}, fmt.Errorf("Error loading access token: %s", err) - } - - log.Printf("[INFO] Authenticating using configured Google JSON 'access_token'...") - log.Printf("[INFO] -- Scopes: %s", clientScopes) - token := &oauth2.Token{AccessToken: contents} - - return googleoauth.Credentials{ - TokenSource: staticTokenSource{oauth2.StaticTokenSource(token)}, - }, nil - } - - if c.Credentials != "" { - contents, _, err := pathOrContents(c.Credentials) ->>>>>>> master if err != nil { return googleoauth.Credentials{}, err } diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index f9d76d38e033..f7d9c18fa7d6 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -37,7 +37,7 @@ func TestAccConfigLoadValidate_credentials(t *testing.T) { } func TestAccConfigLoadValidate_impersonated(t *testing.T) { - if os.Getenv(resource.TestEnvVar) == "" { + if os.Getenv(TestEnvVar) == "" { t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", TestEnvVar)) } testAccPreCheck(t) From 567fc551ce0fc6a8c2332e386f093911e007378a Mon Sep 17 00:00:00 2001 From: upodroid Date: Mon, 21 Sep 2020 14:26:51 +0100 Subject: [PATCH 03/14] add the scopes block removed by error --- third_party/terraform/utils/provider.go.erb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index 01764d405706..273dc5a75318 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -451,6 +451,14 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr config.ImpersonateServiceAccount = v.(string) } + scopes := d.Get("scopes").([]interface{}) + if len(scopes) > 0 { + config.Scopes = make([]string, len(scopes)) + } + for i, scope := range scopes { + config.Scopes[i] = scope.(string) + } + delegates := d.Get("impersonate_service_account_delegates").([]interface{}) if len(delegates) > 0 { config.ImpersonateServiceAccountDelegates = make([]string, len(delegates)) From 2010e7896bda2f3d382e13ef6b7c2178c5479bcc Mon Sep 17 00:00:00 2001 From: upodroid Date: Fri, 25 Sep 2020 17:30:26 +0100 Subject: [PATCH 04/14] add service account impersonation --- ..._source_google_service_account_id_token.go | 4 +- .../data_source_storage_object_signed_url.go | 18 ++- ...ce_google_service_account_id_token_test.go | 15 ++- .../resource_compute_instance_migrate_test.go | 7 +- .../terraform/utils/bootstrap_utils_test.go | 7 +- third_party/terraform/utils/config.go.erb | 50 ++++++++- third_party/terraform/utils/config_test.go | 105 +++++++++++++++++- .../terraform/utils/gcp_sweeper_test.go | 5 +- third_party/terraform/utils/provider.go.erb | 24 ++++ .../guides/provider_reference.html.markdown | 47 +++++--- 10 files changed, 248 insertions(+), 34 deletions(-) diff --git a/third_party/terraform/data_sources/data_source_google_service_account_id_token.go b/third_party/terraform/data_sources/data_source_google_service_account_id_token.go index f76905018269..3efafd2c8643 100644 --- a/third_party/terraform/data_sources/data_source_google_service_account_id_token.go +++ b/third_party/terraform/data_sources/data_source_google_service_account_id_token.go @@ -74,8 +74,8 @@ func dataSourceGoogleServiceAccountIdTokenRead(d *schema.ResourceData, meta inte ts := creds.TokenSource - // The idToken doesn't support impersonatedcredentials method yet :( https://github.com/googleapis/google-api-go-client/blob/master/idtoken/idtoken.go#L67 - if config.ImpersonateServiceAccount != "" { + // If the source token is just an access_token, all we can do is use the iamcredentials api to get an id_token + if _, ok := ts.(staticTokenSource); ok { // Use // https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken service := config.clientIamCredentials diff --git a/third_party/terraform/data_sources/data_source_storage_object_signed_url.go b/third_party/terraform/data_sources/data_source_storage_object_signed_url.go index c15cfa61c99d..938c72f8f345 100644 --- a/third_party/terraform/data_sources/data_source_storage_object_signed_url.go +++ b/third_party/terraform/data_sources/data_source_storage_object_signed_url.go @@ -49,6 +49,11 @@ func dataSourceGoogleSignedUrl() *schema.Resource { Optional: true, Default: "", }, + "credentials": { + Type: schema.TypeString, + Sensitive: true, + Optional: true, + }, "duration": { Type: schema.TypeString, Optional: true, @@ -170,10 +175,21 @@ func dataSourceGoogleSignedUrlRead(d *schema.ResourceData, meta interface{}) err // 3. A JSON file whose path is specified by the // GOOGLE_APPLICATION_CREDENTIALS environment variable. func loadJwtConfig(d *schema.ResourceData, meta interface{}) (*jwt.Config, error) { + config := meta.(*Config) + credentials := "" - if filename := os.Getenv(googleCredentialsEnvVar); filename != "" { + if v, ok := d.GetOk("credentials"); ok { + log.Println("[DEBUG] using data source credentials to sign URL") + credentials = v.(string) + + } else if config.Credentials != "" { + log.Println("[DEBUG] using provider credentials to sign URL") + credentials = config.Credentials + + } else if filename := os.Getenv(googleCredentialsEnvVar); filename != "" { log.Println("[DEBUG] using env GOOGLE_APPLICATION_CREDENTIALS credentials to sign URL") credentials = filename + } if strings.TrimSpace(credentials) != "" { diff --git a/third_party/terraform/tests/data_source_google_service_account_id_token_test.go b/third_party/terraform/tests/data_source_google_service_account_id_token_test.go index 9f0a79e7d2b3..1e7d9a2255e8 100644 --- a/third_party/terraform/tests/data_source_google_service_account_id_token_test.go +++ b/third_party/terraform/tests/data_source_google_service_account_id_token_test.go @@ -91,10 +91,21 @@ func TestAccDataSourceGoogleServiceAccountIdToken_impersonation(t *testing.T) { func testAccCheckGoogleServiceAccountIdToken_impersonation_datasource(targetAudience string, targetServiceAccount string) string { return fmt.Sprintf(` +data "google_service_account_access_token" "default" { + target_service_account = "%s" + scopes = ["userinfo-email", "https://www.googleapis.com/auth/cloud-platform"] + lifetime = "30s" +} + +provider google { + alias = "impersonated" + access_token = data.google_service_account_access_token.default.access_token +} + data "google_service_account_id_token" "default" { + provider = google.impersonated target_service_account = "%s" target_audience = "%s" - include_email = true } -`, targetServiceAccount, targetAudience) +`, targetServiceAccount, targetServiceAccount, targetAudience) } diff --git a/third_party/terraform/tests/resource_compute_instance_migrate_test.go b/third_party/terraform/tests/resource_compute_instance_migrate_test.go index ba26d3c635cc..a37c78785099 100644 --- a/third_party/terraform/tests/resource_compute_instance_migrate_test.go +++ b/third_party/terraform/tests/resource_compute_instance_migrate_test.go @@ -935,9 +935,10 @@ func getInitializedConfig(t *testing.T) *Config { testAccPreCheck(t) config := &Config{ - Project: getTestProjectFromEnv(), - Region: getTestRegionFromEnv(), - Zone: getTestZoneFromEnv(), + Project: getTestProjectFromEnv(), + Credentials: getTestCredsFromEnv(), + Region: getTestRegionFromEnv(), + Zone: getTestZoneFromEnv(), } ConfigureBasePaths(config) diff --git a/third_party/terraform/utils/bootstrap_utils_test.go b/third_party/terraform/utils/bootstrap_utils_test.go index 1b3184469900..267c8a1f3bce 100644 --- a/third_party/terraform/utils/bootstrap_utils_test.go +++ b/third_party/terraform/utils/bootstrap_utils_test.go @@ -322,9 +322,10 @@ func BootstrapConfig(t *testing.T) *Config { } config := &Config{ - Project: getTestProjectFromEnv(), - Region: getTestRegionFromEnv(), - Zone: getTestZoneFromEnv(), + Credentials: getTestCredsFromEnv(), + Project: getTestProjectFromEnv(), + Region: getTestRegionFromEnv(), + Zone: getTestZoneFromEnv(), } ConfigureBasePaths(config) diff --git a/third_party/terraform/utils/config.go.erb b/third_party/terraform/utils/config.go.erb index 815f4336c585..60e812d72349 100644 --- a/third_party/terraform/utils/config.go.erb +++ b/third_party/terraform/utils/config.go.erb @@ -64,8 +64,10 @@ import ( // Config is the configuration structure used to instantiate the Google // provider. type Config struct { - ImpersonateServiceAccount string - ImpersonateServiceAccountDelegates []string + AccessToken string + Credentials string + ImpersonateServiceAccount string + ImpersonateServiceAccountDelegates []string Project string Region string BillingProject string @@ -640,8 +642,52 @@ func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, erro return creds.TokenSource, nil } +// staticTokenSource is used to be able to identify static token sources without reflection. +type staticTokenSource struct { + oauth2.TokenSource +} + func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, error) { + if c.AccessToken != "" { + contents, _, err := pathOrContents(c.AccessToken) + if err != nil { + return googleoauth.Credentials{}, fmt.Errorf("Error loading access token: %s", err) + } + + log.Printf("[INFO] Authenticating using configured Google JSON 'access_token'...") + log.Printf("[INFO] -- Scopes: %s", clientScopes) + token := &oauth2.Token{AccessToken: contents} + + return googleoauth.Credentials{ + TokenSource: staticTokenSource{oauth2.StaticTokenSource(token)}, + }, nil + } + + if c.Credentials != "" { + contents, _, err := pathOrContents(c.Credentials) + if err != nil { + return googleoauth.Credentials{}, fmt.Errorf("error loading credentials: %s", err) + } + if c.ImpersonateServiceAccount != "" { + opts := []option.ClientOption{option.WithCredentialsJSON([]byte(contents)), option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...), option.WithScopes(clientScopes...)} + creds, err := transport.Creds(context.TODO(), opts...) + if err != nil { + return googleoauth.Credentials{}, err + } + return *creds, nil + + } + creds, err := googleoauth.CredentialsFromJSON(c.context, []byte(contents), clientScopes...) + if err != nil { + return googleoauth.Credentials{}, fmt.Errorf("unable to parse credentials from '%s': %s", contents, err) + } + + log.Printf("[INFO] Authenticating using configured Google JSON 'credentials'...") + log.Printf("[INFO] -- Scopes: %s", clientScopes) + return *creds, nil + } + if c.ImpersonateServiceAccount != "" { opts := option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...) creds, err := transport.Creds(context.TODO(), opts, option.WithScopes(clientScopes...)) diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index f7d9c18fa7d6..b1e154a2c05f 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -3,12 +3,64 @@ package google import ( "context" "fmt" + "io/ioutil" "os" "testing" "time" + + "golang.org/x/oauth2/google" ) const testFakeCredentialsPath = "./test-fixtures/fake_account.json" +const testOauthScope = "https://www.googleapis.com/auth/compute" + +func TestConfigLoadAndValidate_accountFilePath(t *testing.T) { + config := &Config{ + Credentials: testFakeCredentialsPath, + Project: "my-gce-project", + Region: "us-central1", + } + + ConfigureBasePaths(config) + + err := config.LoadAndValidate(context.Background()) + if err != nil { + t.Fatalf("error: %v", err) + } +} + +func TestConfigLoadAndValidate_accountFileJSON(t *testing.T) { + contents, err := ioutil.ReadFile(testFakeCredentialsPath) + if err != nil { + t.Fatalf("error: %v", err) + } + config := &Config{ + Credentials: string(contents), + Project: "my-gce-project", + Region: "us-central1", + } + + ConfigureBasePaths(config) + + err = config.LoadAndValidate(context.Background()) + if err != nil { + t.Fatalf("error: %v", err) + } +} + +func TestConfigLoadAndValidate_accountFileJSONInvalid(t *testing.T) { + config := &Config{ + Credentials: "{this is not json}", + Project: "my-gce-project", + Region: "us-central1", + } + + ConfigureBasePaths(config) + + if config.LoadAndValidate(context.Background()) == nil { + t.Fatalf("expected error, but got nil") + } +} func TestAccConfigLoadValidate_credentials(t *testing.T) { if os.Getenv(TestEnvVar) == "" { @@ -16,11 +68,13 @@ func TestAccConfigLoadValidate_credentials(t *testing.T) { } testAccPreCheck(t) + creds := getTestCredsFromEnv() proj := getTestProjectFromEnv() config := &Config{ - Project: proj, - Region: "us-central1", + Credentials: creds, + Project: proj, + Region: "us-central1", } ConfigureBasePaths(config) @@ -64,11 +118,50 @@ func TestAccConfigLoadValidate_impersonated(t *testing.T) { } } +func TestAccConfigLoadValidate_accessToken(t *testing.T) { + if os.Getenv(TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", TestEnvVar)) + } + testAccPreCheck(t) + + creds := getTestCredsFromEnv() + proj := getTestProjectFromEnv() + + c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), testOauthScope) + if err != nil { + t.Fatalf("invalid test credentials: %s", err) + } + + token, err := c.TokenSource.Token() + if err != nil { + t.Fatalf("Unable to generate test access token: %s", err) + } + + config := &Config{ + AccessToken: token.AccessToken, + Project: proj, + Region: "us-central1", + } + + ConfigureBasePaths(config) + + err = config.LoadAndValidate(context.Background()) + if err != nil { + t.Fatalf("error: %v", err) + } + + _, err = config.clientCompute.Zones.Get(proj, "us-central1-a").Do() + if err != nil { + t.Fatalf("expected API call with loaded config to work, got error: %s", err) + } +} + func TestConfigLoadAndValidate_customScopes(t *testing.T) { config := &Config{ - Project: "my-gce-project", - Region: "us-central1", - Scopes: []string{"https://www.googleapis.com/auth/compute"}, + Credentials: testFakeCredentialsPath, + Project: "my-gce-project", + Region: "us-central1", + Scopes: []string{"https://www.googleapis.com/auth/compute"}, } ConfigureBasePaths(config) @@ -93,6 +186,7 @@ func TestConfigLoadAndValidate_defaultBatchingConfig(t *testing.T) { t.Fatalf("unexpected error: %v", err) } config := &Config{ + Credentials: testFakeCredentialsPath, Project: "my-gce-project", Region: "us-central1", BatchingConfig: batchCfg, @@ -129,6 +223,7 @@ func TestConfigLoadAndValidate_customBatchingConfig(t *testing.T) { } config := &Config{ + Credentials: testFakeCredentialsPath, Project: "my-gce-project", Region: "us-central1", BatchingConfig: batchCfg, diff --git a/third_party/terraform/utils/gcp_sweeper_test.go b/third_party/terraform/utils/gcp_sweeper_test.go index 6b3dc212c75d..3c21d695bb01 100644 --- a/third_party/terraform/utils/gcp_sweeper_test.go +++ b/third_party/terraform/utils/gcp_sweeper_test.go @@ -32,8 +32,9 @@ func sharedConfigForRegion(region string) (*Config, error) { } conf := &Config{ - Region: region, - Project: project, + Credentials: creds, + Region: region, + Project: project, } ConfigureBasePaths(conf) diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index 273dc5a75318..67912c839b45 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -22,6 +22,25 @@ var mutexKV = NewMutexKV() func Provider() *schema.Provider { provider := &schema.Provider{ Schema: map[string]*schema.Schema{ + "credentials": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_CREDENTIALS", + "GOOGLE_CLOUD_KEYFILE_JSON", + "GCLOUD_KEYFILE_JSON", + }, nil), + ValidateFunc: validateCredentials, + }, + + "access_token": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_OAUTH_ACCESS_TOKEN", + }, nil), + ConflictsWith: []string{"credentials"}, + }, "impersonate_service_account": { Type: schema.TypeString, Optional: true, @@ -447,6 +466,11 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, p *schema.Pr } } // Add credential source + if v, ok := d.GetOk("access_token"); ok { + config.AccessToken = v.(string) + } else if v, ok := d.GetOk("credentials"); ok { + config.Credentials = v.(string) + } if v, ok := d.GetOk("impersonate_service_account"); ok { config.ImpersonateServiceAccount = v.(string) } diff --git a/third_party/terraform/website/docs/guides/provider_reference.html.markdown b/third_party/terraform/website/docs/guides/provider_reference.html.markdown index 1e45adfe1525..2ae64a89715a 100644 --- a/third_party/terraform/website/docs/guides/provider_reference.html.markdown +++ b/third_party/terraform/website/docs/guides/provider_reference.html.markdown @@ -18,7 +18,6 @@ location (`zone` and/or `region`) for your resources. ```hcl provider "google" { - credentials = file("account.json") project = "my-project-id" region = "us-central1" zone = "us-central1-c" @@ -27,7 +26,6 @@ provider "google" { ```hcl provider "google-beta" { - credentials = file("account.json") project = "my-project-id" region = "us-central1" zone = "us-central1-c" @@ -62,6 +60,28 @@ resource "google_compute_instance" "beta-instance" { provider "google-beta" {} ``` +## Authentication + +### Running Terraform on your workstation. + +If you are using terraform on your workstation, you will need to install the Google Cloud SDK and authenticate using [User Application Default +Credentials](https://cloud.google.com/sdk/gcloud/reference/auth/application-default). + +### Running Terraform on Google Cloud + +If you are running terraform on Google Cloud, you can configure that instance or cluster to use a [Google Service +Account](https://cloud.google.com/compute/docs/authentication). This will allow Terraform to authenticate to Google Cloud without having to bake in a separate +credential/authentication file. Make sure that the scope of the VM/Cluster is set to cloud-platform. + +### Running Terraform outside of Google Cloud + +If you are using running terraform outside of Google Cloud, generate a service account key and set the `GOOGLE_APPPLICATION_CREDENTIALS` environment variable to +the path of the service account key. Terraform will use that key for authentication. + +### Impersonating Service Accounts + +Terraform can impersonate a Google Service Account as described [here](https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials). A valid +credential must be provided as mentioned in the earlier section and that identity must have the `roles/iam.serviceAccountTokenCreator` role on the service account you are impersonating. ## Configuration Reference @@ -72,15 +92,6 @@ same configuration. ### Quick Reference -* `credentials` - (Optional) Either the path to or the contents of a -[service account key file] in JSON format. You can -[manage key files using the Cloud Console]. If not provided, the -application default credentials will be used. You can configure -Application Default Credentials on your personal machine by -running `gcloud auth application-default login`. If -terraform is running on a GCP machine, and this value is unset, -it will automatically use that machine's configured service account. - * `project` - (Optional) The default project to manage resources in. If another project is specified on a resource, it will take precedence. @@ -91,6 +102,9 @@ region is specified on a regional resource, it will take precedence. zone should be within the default region you specified. If another zone is specified on a zonal resource, it will take precedence. +* `impersonate_service_account` - (Optional) The service account to impersonate for all Google API Calls. +You must have `roles/iam.serviceAccountTokenCreator` role on that account for the impersonation to succeed. + --- * `scopes` - (Optional) The list of OAuth 2.0 [scopes] requested when generating @@ -166,9 +180,14 @@ are automatically available. See for more details. * On your computer, you can make your Google identity available by -running [`gcloud auth application-default login`][gcloud adc]. This -approach isn't recommended- some APIs are not compatible with -credentials obtained through `gcloud`. +running [`gcloud auth application-default login`][gcloud adc]. + +--- +* `impersonate_service_account` - (Optional) The service account to impersonate for all Google API Calls. +You must have `roles/iam.serviceAccountTokenCreator` role on that account for the impersonation to succeed. +If you are using a delegation chain, you can specify that using the `impersonate_service_account_delegates` field. + +* `impersonate_service_account_delegates` - (Optional) The delegation chain for an impersonating a service account as described [here](https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-delegated). --- From 7bb84871be5dd49d107b4c708c0dd16ae45c06c7 Mon Sep 17 00:00:00 2001 From: upodroid Date: Fri, 25 Sep 2020 17:37:13 +0100 Subject: [PATCH 05/14] undo spacing --- third_party/terraform/utils/config.go.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/terraform/utils/config.go.erb b/third_party/terraform/utils/config.go.erb index 60e812d72349..aabdc7c8d745 100644 --- a/third_party/terraform/utils/config.go.erb +++ b/third_party/terraform/utils/config.go.erb @@ -644,7 +644,7 @@ func (c *Config) getTokenSource(clientScopes []string) (oauth2.TokenSource, erro // staticTokenSource is used to be able to identify static token sources without reflection. type staticTokenSource struct { - oauth2.TokenSource + oauth2.TokenSource } func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, error) { From 89dbaec3282d3f7513b7cae38d00e9e029cc6f99 Mon Sep 17 00:00:00 2001 From: upodroid Date: Mon, 28 Sep 2020 19:51:31 +0100 Subject: [PATCH 06/14] update docs for quota project --- .../website/docs/guides/provider_reference.html.markdown | 2 ++ 1 file changed, 2 insertions(+) diff --git a/third_party/terraform/website/docs/guides/provider_reference.html.markdown b/third_party/terraform/website/docs/guides/provider_reference.html.markdown index 2ae64a89715a..261e07678b28 100644 --- a/third_party/terraform/website/docs/guides/provider_reference.html.markdown +++ b/third_party/terraform/website/docs/guides/provider_reference.html.markdown @@ -67,6 +67,8 @@ provider "google-beta" {} If you are using terraform on your workstation, you will need to install the Google Cloud SDK and authenticate using [User Application Default Credentials](https://cloud.google.com/sdk/gcloud/reference/auth/application-default). +A quota project must be set which gcloud automatically reads from the `core/project` value. You can override this project by specifying `--project` flag when running `gcloud auth application-default login`. The SDK should return this message if you have set the correct billing project. `Quota project "your-project" was added to ADC which can be used by Google client libraries for billing and quota.` + ### Running Terraform on Google Cloud If you are running terraform on Google Cloud, you can configure that instance or cluster to use a [Google Service From 4a2ffe753bf4b3aff22c6ce50c6a425c4eb79670 Mon Sep 17 00:00:00 2001 From: upodroid Date: Wed, 30 Sep 2020 23:23:23 +0100 Subject: [PATCH 07/14] fix typos in docs --- .../website/docs/guides/provider_reference.html.markdown | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/third_party/terraform/website/docs/guides/provider_reference.html.markdown b/third_party/terraform/website/docs/guides/provider_reference.html.markdown index 261e07678b28..4631a7128838 100644 --- a/third_party/terraform/website/docs/guides/provider_reference.html.markdown +++ b/third_party/terraform/website/docs/guides/provider_reference.html.markdown @@ -77,13 +77,12 @@ credential/authentication file. Make sure that the scope of the VM/Cluster is se ### Running Terraform outside of Google Cloud -If you are using running terraform outside of Google Cloud, generate a service account key and set the `GOOGLE_APPPLICATION_CREDENTIALS` environment variable to +If you are running terraform outside of Google Cloud, generate a service account key and set the `GOOGLE_APPPLICATION_CREDENTIALS` environment variable to the path of the service account key. Terraform will use that key for authentication. ### Impersonating Service Accounts -Terraform can impersonate a Google Service Account as described [here](https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials). A valid -credential must be provided as mentioned in the earlier section and that identity must have the `roles/iam.serviceAccountTokenCreator` role on the service account you are impersonating. +Terraform can impersonate a Google Service Account as described [here](https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials). A valid credential must be provided as mentioned in the earlier section and that identity must have the `roles/iam.serviceAccountTokenCreator` role on the service account you are impersonating. ## Configuration Reference @@ -107,6 +106,10 @@ specified on a zonal resource, it will take precedence. * `impersonate_service_account` - (Optional) The service account to impersonate for all Google API Calls. You must have `roles/iam.serviceAccountTokenCreator` role on that account for the impersonation to succeed. +* `credentials` - (Optional) Either the path to or the contents of a +[service account key file] in JSON format. You can +[manage key files using the Cloud Console]. If not provided, the +application default credentials will be used. --- * `scopes` - (Optional) The list of OAuth 2.0 [scopes] requested when generating From e90c0c4fe007812022d50c87247f6fbd202dfb30 Mon Sep 17 00:00:00 2001 From: upodroid Date: Mon, 5 Oct 2020 22:56:55 +0100 Subject: [PATCH 08/14] fix typo in test and the docs --- third_party/terraform/utils/config_test.go | 2 +- .../website/docs/guides/provider_reference.html.markdown | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index b1e154a2c05f..d8b99e1f96c9 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -96,7 +96,7 @@ func TestAccConfigLoadValidate_impersonated(t *testing.T) { } testAccPreCheck(t) - serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATED_SERVICE_ACCOUNT"}) + serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATE_SERVICE_ACCOUNT"}) proj := getTestProjectFromEnv() config := &Config{ diff --git a/third_party/terraform/website/docs/guides/provider_reference.html.markdown b/third_party/terraform/website/docs/guides/provider_reference.html.markdown index 4631a7128838..63cfe899dfa0 100644 --- a/third_party/terraform/website/docs/guides/provider_reference.html.markdown +++ b/third_party/terraform/website/docs/guides/provider_reference.html.markdown @@ -191,6 +191,8 @@ running [`gcloud auth application-default login`][gcloud adc]. * `impersonate_service_account` - (Optional) The service account to impersonate for all Google API Calls. You must have `roles/iam.serviceAccountTokenCreator` role on that account for the impersonation to succeed. If you are using a delegation chain, you can specify that using the `impersonate_service_account_delegates` field. +Alternatively, this can be specified using the `GOOGLE_IMPERSONATE_SERVICE_ACCOUNT` environment +variable. * `impersonate_service_account_delegates` - (Optional) The delegation chain for an impersonating a service account as described [here](https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-delegated). From 859d9f2a6348e4cb6b589d4f71c6cfde88610d18 Mon Sep 17 00:00:00 2001 From: upodroid Date: Wed, 7 Oct 2020 18:01:20 +0100 Subject: [PATCH 09/14] use the new gce client --- third_party/terraform/utils/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index 06be3205eda2..8960268d5740 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -112,7 +112,7 @@ func TestAccConfigLoadValidate_impersonated(t *testing.T) { t.Fatalf("error: %v", err) } - _, err = config.clientCompute.Zones.Get(proj, "us-central1-a").Do() + _, err = config.NewComputeClient(config.userAgent).Zones.Get(proj, "us-central1-a").Do() if err != nil { t.Fatalf("expected API call with loaded config to work, got error: %s", err) } From 0e6cc138dfa4ec6babe26501c228e346f9175a90 Mon Sep 17 00:00:00 2001 From: upodroid Date: Fri, 9 Oct 2020 19:35:30 +0100 Subject: [PATCH 10/14] add impersonate to accesstoken + test --- third_party/terraform/utils/config.go.erb | 12 +++++-- third_party/terraform/utils/config_test.go | 42 ++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/third_party/terraform/utils/config.go.erb b/third_party/terraform/utils/config.go.erb index a6fd165b304b..60f6af7e1bdc 100644 --- a/third_party/terraform/utils/config.go.erb +++ b/third_party/terraform/utils/config.go.erb @@ -724,10 +724,19 @@ func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, if err != nil { return googleoauth.Credentials{}, fmt.Errorf("Error loading access token: %s", err) } + token := &oauth2.Token{AccessToken: contents} + + if c.ImpersonateServiceAccount != "" { + opts := []option.ClientOption{option.WithTokenSource(token), option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...), option.WithScopes(clientScopes...)} + creds, err := transport.Creds(context.TODO(), opts...) + if err != nil { + return googleoauth.Credentials{}, err + } + return *creds, nil + } log.Printf("[INFO] Authenticating using configured Google JSON 'access_token'...") log.Printf("[INFO] -- Scopes: %s", clientScopes) - token := &oauth2.Token{AccessToken: contents} return googleoauth.Credentials{ TokenSource: staticTokenSource{oauth2.StaticTokenSource(token)}, @@ -746,7 +755,6 @@ func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, return googleoauth.Credentials{}, err } return *creds, nil - } creds, err := googleoauth.CredentialsFromJSON(c.context, []byte(contents), clientScopes...) if err != nil { diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index 8960268d5740..4ba0875b2a8f 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -97,9 +97,11 @@ func TestAccConfigLoadValidate_impersonated(t *testing.T) { testAccPreCheck(t) serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATE_SERVICE_ACCOUNT"}) + creds := getTestCredsFromEnv() proj := getTestProjectFromEnv() config := &Config{ + Credentials: creds, ImpersonateServiceAccount: serviceaccount, Project: proj, Region: "us-central1", @@ -118,6 +120,46 @@ func TestAccConfigLoadValidate_impersonated(t *testing.T) { } } +func TestAccConfigLoadValidate_accessTokenImpersonated(t *testing.T) { + if os.Getenv(TestEnvVar) == "" { + t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", TestEnvVar)) + } + testAccPreCheck(t) + + creds := getTestCredsFromEnv() + proj := getTestProjectFromEnv() + serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATE_SERVICE_ACCOUNT"}) + + c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), testOauthScope) + if err != nil { + t.Fatalf("invalid test credentials: %s", err) + } + + token, err := c.TokenSource.Token() + if err != nil { + t.Fatalf("Unable to generate test access token: %s", err) + } + + config := &Config{ + AccessToken: token.AccessToken, + ImpersonateServiceAccount: serviceaccount, + Project: proj, + Region: "us-central1", + } + + ConfigureBasePaths(config) + + err = config.LoadAndValidate(context.Background()) + if err != nil { + t.Fatalf("error: %v", err) + } + + _, err = config.NewComputeClient(config.userAgent).Zones.Get(proj, "us-central1-a").Do() + if err != nil { + t.Fatalf("expected API call with loaded config to work, got error: %s", err) + } +} + func TestAccConfigLoadValidate_accessToken(t *testing.T) { if os.Getenv(TestEnvVar) == "" { t.Skip(fmt.Sprintf("Network access not allowed; use %s=1 to enable", TestEnvVar)) From 897a898c492847cbedbc7899e6467a151bcbd67d Mon Sep 17 00:00:00 2001 From: upodroid Date: Fri, 9 Oct 2020 21:30:40 +0100 Subject: [PATCH 11/14] fix tokensource typo --- third_party/terraform/utils/config.go.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/terraform/utils/config.go.erb b/third_party/terraform/utils/config.go.erb index 60f6af7e1bdc..479a43d03eab 100644 --- a/third_party/terraform/utils/config.go.erb +++ b/third_party/terraform/utils/config.go.erb @@ -727,7 +727,7 @@ func (c *Config) GetCredentials(clientScopes []string) (googleoauth.Credentials, token := &oauth2.Token{AccessToken: contents} if c.ImpersonateServiceAccount != "" { - opts := []option.ClientOption{option.WithTokenSource(token), option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...), option.WithScopes(clientScopes...)} + opts := []option.ClientOption{option.WithTokenSource(oauth2.StaticTokenSource(token)), option.ImpersonateCredentials(c.ImpersonateServiceAccount, c.ImpersonateServiceAccountDelegates...), option.WithScopes(clientScopes...)} creds, err := transport.Creds(context.TODO(), opts...) if err != nil { return googleoauth.Credentials{}, err From b70ff7641c2ef38a1a8ddcdf395f517f812ed0c6 Mon Sep 17 00:00:00 2001 From: upodroid Date: Thu, 15 Oct 2020 17:34:24 +0100 Subject: [PATCH 12/14] replace the envs used for testing impersonation --- third_party/terraform/utils/config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index 4ba0875b2a8f..8a255d95e2f8 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -96,7 +96,7 @@ func TestAccConfigLoadValidate_impersonated(t *testing.T) { } testAccPreCheck(t) - serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATE_SERVICE_ACCOUNT"}) + serviceaccount := multiEnvSearch([]string{"IMPERSONATE_SERVICE_ACCOUNT_ACCTEST"}) creds := getTestCredsFromEnv() proj := getTestProjectFromEnv() @@ -128,7 +128,7 @@ func TestAccConfigLoadValidate_accessTokenImpersonated(t *testing.T) { creds := getTestCredsFromEnv() proj := getTestProjectFromEnv() - serviceaccount := multiEnvSearch([]string{"GOOGLE_IMPERSONATE_SERVICE_ACCOUNT"}) + serviceaccount := multiEnvSearch([]string{"IMPERSONATE_SERVICE_ACCOUNT_ACCTEST"}) c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), testOauthScope) if err != nil { From 73391534b07ebed803f00436837185ecd5cd4d49 Mon Sep 17 00:00:00 2001 From: upodroid Date: Thu, 15 Oct 2020 19:20:02 +0100 Subject: [PATCH 13/14] add additional scopes --- third_party/terraform/utils/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index 8a255d95e2f8..cfe81efdb9f6 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -130,7 +130,7 @@ func TestAccConfigLoadValidate_accessTokenImpersonated(t *testing.T) { proj := getTestProjectFromEnv() serviceaccount := multiEnvSearch([]string{"IMPERSONATE_SERVICE_ACCOUNT_ACCTEST"}) - c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), testOauthScope) + c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), DefaultClientScopes..) if err != nil { t.Fatalf("invalid test credentials: %s", err) } From fcfb721ac8dcb162acf1d6181e9e99c1a5b0dfc9 Mon Sep 17 00:00:00 2001 From: upodroid Date: Thu, 15 Oct 2020 19:46:21 +0100 Subject: [PATCH 14/14] typo fix --- third_party/terraform/utils/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/terraform/utils/config_test.go b/third_party/terraform/utils/config_test.go index cfe81efdb9f6..21dd0532d6ed 100644 --- a/third_party/terraform/utils/config_test.go +++ b/third_party/terraform/utils/config_test.go @@ -130,7 +130,7 @@ func TestAccConfigLoadValidate_accessTokenImpersonated(t *testing.T) { proj := getTestProjectFromEnv() serviceaccount := multiEnvSearch([]string{"IMPERSONATE_SERVICE_ACCOUNT_ACCTEST"}) - c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), DefaultClientScopes..) + c, err := google.CredentialsFromJSON(context.Background(), []byte(creds), DefaultClientScopes...) if err != nil { t.Fatalf("invalid test credentials: %s", err) }