diff --git a/google-beta/bootstrap_utils_test.go b/google-beta/bootstrap_utils_test.go index f4f869acf6..f6ccc0b1bd 100644 --- a/google-beta/bootstrap_utils_test.go +++ b/google-beta/bootstrap_utils_test.go @@ -7,6 +7,7 @@ import ( "testing" "google.golang.org/api/cloudkms/v1" + "google.golang.org/api/iam/v1" ) var SharedKeyRing = "tftest-shared-keyring-1" @@ -103,3 +104,96 @@ func BootstrapKMSKey(t *testing.T) bootstrappedKMS { cryptoKey, } } + +var serviceAccountEmail = "tf-bootstrap-service-account" +var serviceAccountDisplay = "Bootstrapped Service Account for Terraform tests" + +// Some tests need a second service account, other than the test runner, to assert functionality on. +// This provides a well-known service account that can be used when dynamically creating a service +// account isn't an option. +func getOrCreateServiceAccount(config Config, project string) (*iam.ServiceAccount, error) { + name := fmt.Sprintf("projects/%s/serviceAccounts/%s@%s.iam.gserviceaccount.com", project, serviceAccountEmail, project) + log.Printf("[DEBUG] Verifying %s as bootstrapped service account.\n", name) + + sa, err := config.clientIAM.Projects.ServiceAccounts.Get(name).Do() + if err != nil && !isGoogleApiErrorWithCode(err, 404) { + return nil, err + } + + if sa == nil { + log.Printf("[DEBUG] Account missing. Creating %s as bootstrapped service account.\n", name) + sa = &iam.ServiceAccount{ + DisplayName: serviceAccountDisplay, + } + + r := &iam.CreateServiceAccountRequest{ + AccountId: serviceAccountEmail, + ServiceAccount: sa, + } + sa, err = config.clientIAM.Projects.ServiceAccounts.Create("projects/"+project, r).Do() + if err != nil { + return nil, err + } + } + + return sa, nil +} + +// In order to test impersonation we need to grant the testRunner's account the ability to grant tokens +// on a different service account. Granting permissions takes time and there is no operation to wait on +// so instead this creates a single service account once per test-suite with the correct permissions. +// The first time this test is run it will fail, but subsequent runs will succeed. +func impersonationServiceAccountPermissions(config Config, sa *iam.ServiceAccount, testRunner string) error { + log.Printf("[DEBUG] Setting service account permissions.\n") + policy := iam.Policy{ + Bindings: []*iam.Binding{}, + } + + binding := &iam.Binding{ + Role: "roles/iam.serviceAccountTokenCreator", + Members: []string{"serviceAccount:" + sa.Email, "serviceAccount:" + testRunner}, + } + policy.Bindings = append(policy.Bindings, binding) + + // Overwrite the roles each time on this service account. This is because this account is + // only created for the test suite and will stop snowflaking of permissions to get tests + // to run. Overwriting permissions on 1 service account shouldn't affect others. + _, err := config.clientIAM.Projects.ServiceAccounts.SetIamPolicy(sa.Name, &iam.SetIamPolicyRequest{ + Policy: &policy, + }).Do() + if err != nil { + return err + } + + return nil +} + +func BootstrapServiceAccount(t *testing.T, project, testRunner string) string { + if v := os.Getenv("TF_ACC"); v == "" { + log.Println("Acceptance tests and bootstrapping skipped unless env 'TF_ACC' set") + return "" + } + + config := Config{ + Credentials: getTestCredsFromEnv(), + Project: getTestProjectFromEnv(), + Region: getTestRegionFromEnv(), + Zone: getTestZoneFromEnv(), + } + + if err := config.LoadAndValidate(); err != nil { + t.Fatalf("Bootstrapping failed. Unable to load test config: %s", err) + } + + sa, err := getOrCreateServiceAccount(config, project) + if err != nil { + t.Fatalf("Bootstrapping failed. Cannot retrieve service account, %s", err) + } + + err = impersonationServiceAccountPermissions(config, sa, testRunner) + if err != nil { + t.Fatalf("Bootstrapping failed. Cannot set service account permissions, %s", err) + } + + return sa.Email +} diff --git a/google-beta/data_source_google_service_account_access_token_test.go b/google-beta/data_source_google_service_account_access_token_test.go index 27e2ba4599..53fbc71fda 100644 --- a/google-beta/data_source_google_service_account_access_token_test.go +++ b/google-beta/data_source_google_service_account_access_token_test.go @@ -30,8 +30,8 @@ func TestAccDataSourceGoogleServiceAccountAccessToken_basic(t *testing.T) { t.Parallel() resourceName := "data.google_service_account_access_token.default" - - targetServiceAccountEmail := getTestServiceAccountFromEnv(t) + serviceAccount := getTestServiceAccountFromEnv(t) + targetServiceAccountEmail := BootstrapServiceAccount(t, getTestProjectFromEnv(), serviceAccount) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) },