From 85a94dd46e1f4a289f7675d562ae1d2398fa5451 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Fri, 25 Oct 2024 13:53:32 -0700 Subject: [PATCH 01/10] add ephemeral_google_service_account_id_token --- .../fwprovider/framework_provider.go.tmpl | 2 +- ...hemeral_google_service_account_id_token.go | 169 ++++++++++++++++++ 2 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go diff --git a/mmv1/third_party/terraform/fwprovider/framework_provider.go.tmpl b/mmv1/third_party/terraform/fwprovider/framework_provider.go.tmpl index bb85cdeb1fb6..2b538925f768 100644 --- a/mmv1/third_party/terraform/fwprovider/framework_provider.go.tmpl +++ b/mmv1/third_party/terraform/fwprovider/framework_provider.go.tmpl @@ -309,6 +309,6 @@ func (p *FrameworkProvider) Functions(_ context.Context) []func() function.Funct // EphemeralResources defines the resources that are of ephemeral type implemented in the provider. func (p *FrameworkProvider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { return []func() ephemeral.EphemeralResource{ - // TODO! + resourcemanager.GoogleEphemeralServiceAccountIdToken, } } diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go new file mode 100644 index 000000000000..2a7d75bdad5c --- /dev/null +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -0,0 +1,169 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package resourcemanager + +import ( + "context" + "fmt" + + "google.golang.org/api/idtoken" + "google.golang.org/api/option" + + "github.com/hashicorp/terraform-plugin-framework/ephemeral" + "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-provider-google/google/fwtransport" + "google.golang.org/api/iamcredentials/v1" + "google.golang.org/api/idtoken" + "google.golang.org/api/option" +) + +const ( + userInfoScope = "https://www.googleapis.com/auth/userinfo.email" +) + +var _ ephemeral.EphemeralResource = &googleEphemeralServiceAccountIdToken{} + +func GoogleEphemeralServiceAccountIdToken() ephemeral.EphemeralResource { + return &googleEphemeralServiceAccountIdToken{} +} + +type googleEphemeralServiceAccountIdToken struct { + providerConfig *fwtransport.FrameworkProviderConfig +} + +func (p *googleEphemeralServiceAccountIdToken) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { + resp.TypeName = "google_test" +} + +type ephemeralServiceAccountIdTokenModel struct { + TargetAudience types.String `tfsdk:"target_audience"` + TargetServiceAccount types.String `tfsdk:"target_service_account"` + Delegates types.Set `tfsdk:"delegates"` + IncludeEmail types.Bool `tfsdk:"include_email"` + IdToken types.String `tfsdk:"id_token"` +} + +func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "target_audience": { + Type: schema.TypeString, + Required: true, + }, + "target_service_account": { + Type: schema.TypeString, + Optional: true, + //ValidateFunc: verify.ValidateRegexp("(" + strings.Join(verify.PossibleServiceAccountNames, "|") + ")"), + }, + "delegates": { + Type: schema.TypeSet, + Optional: true, + ElementType: types.StringType, + // Validators: verify.ValidateDuration(), // duration <=3600s; TODO: support validateDuration(min,max) + // Default: "3600s", + }, + "include_email": { + Type: schema.TypeBool, + Optional: true, + Default: basetypes.BoolValue(false), + //ValidateFunc: verify.ValidateRegexp("(" + strings.Join(verify.PossibleServiceAccountNames, "|") + ")"), + }, + "id_token": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + }, + } +} + +func (p *googleEphemeralServiceAccountIdToken) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + pd, ok := req.ProviderData.(*fwtransport.FrameworkProviderConfig) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *fwtransport.FrameworkProviderConfig, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + // Required for accessing userAgent and passing as an argument into a util function + p.providerConfig = pd +} + +func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var data ephemeralServiceAccountIdTokenModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + targetAudience := data.TargetAudience.ValueString() + creds, err := p.providerConfig.GetCredentials([]string{userInfoScope}, false) + if err != nil { + resp.Diagnostics.AddError( + "Error calling getCredentials()", + err.Error(), + ) + return + } + + targetServiceAccount := data.TargetServiceAccount.ValueString() + // If a target service account is provided, use the API to generate the idToken + if targetServiceAccount != "" { + service := p.providerConfig.NewIamCredentialsClient(p.providerConfig.UserAgent) + name := fmt.Sprintf("projects/-/serviceAccounts/%s", targetServiceAccount) + DelegatesSetValue, _ := data.Delegates.ToSetValue(ctx) + tokenRequest := &iamcredentials.GenerateIdTokenRequest{ + Audience: targetAudience, + IncludeEmail: data.IncludeEmail.ValueBool(), + Delegates: StringSet(DelegatesSetValue), + } + at, err := service.Projects.ServiceAccounts.GenerateIdToken(name, tokenRequest).Do() + if err != nil { + resp.Diagnostics.AddError( + "Error calling iamcredentials.GenerateIdToken", + err.Error(), + ) + return + } + + data.IdToken = types.StringValue(at.Token) + resp.Diagnostics.Append(resp.Result.Set(ctx, data)...) + return + } + + // If no target service account, use the default credentials + ctx = context.Background() + co := []option.ClientOption{} + if creds.JSON != nil { + co = append(co, idtoken.WithCredentialsJSON(creds.JSON)) + } + + idTokenSource, err := idtoken.NewTokenSource(ctx, targetAudience, co...) + if err != nil { + resp.Diagnostics.AddError( + "Unable to retrieve TokenSource", + err.Error(), + ) + return + } + idToken, err := idTokenSource.Token() + if err != nil { + resp.Diagnostics.AddError( + "Unable to retrieve Token", + err.Error(), + ) + return + } + + data.IdToken = types.StringValue(idToken.AccessToken) + resp.Diagnostics.Append(resp.Result.Set(ctx, data)...) +} From b11f7a587c72ccb242618ec3d252b86cc99ab1f9 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Wed, 6 Nov 2024 08:49:55 -0800 Subject: [PATCH 02/10] working local run of id token --- ...hemeral_google_service_account_id_token.go | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index 2a7d75bdad5c..d438932caf3f 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -1,7 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 package resourcemanager import ( @@ -15,14 +11,9 @@ import ( "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-provider-google/google/fwmodels" "github.com/hashicorp/terraform-provider-google/google/fwtransport" "google.golang.org/api/iamcredentials/v1" - "google.golang.org/api/idtoken" - "google.golang.org/api/option" -) - -const ( - userInfoScope = "https://www.googleapis.com/auth/userinfo.email" ) var _ ephemeral.EphemeralResource = &googleEphemeralServiceAccountIdToken{} @@ -50,30 +41,26 @@ type ephemeralServiceAccountIdTokenModel struct { func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "target_audience": { - Type: schema.TypeString, + "target_audience": schema.StringAttribute{ Required: true, }, - "target_service_account": { - Type: schema.TypeString, + "target_service_account": schema.StringAttribute{ Optional: true, //ValidateFunc: verify.ValidateRegexp("(" + strings.Join(verify.PossibleServiceAccountNames, "|") + ")"), }, - "delegates": { - Type: schema.TypeSet, + "delegates": schema.SetAttribute{ Optional: true, ElementType: types.StringType, // Validators: verify.ValidateDuration(), // duration <=3600s; TODO: support validateDuration(min,max) // Default: "3600s", }, - "include_email": { - Type: schema.TypeBool, + "include_email": schema.BoolAttribute{ Optional: true, - Default: basetypes.BoolValue(false), + Computed: true, + // Default: basetypes.BoolValue(false), //ValidateFunc: verify.ValidateRegexp("(" + strings.Join(verify.PossibleServiceAccountNames, "|") + ")"), }, - "id_token": { - Type: schema.TypeString, + "id_token": schema.StringAttribute{ Computed: true, Sensitive: true, }, @@ -106,14 +93,7 @@ func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req eph resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) targetAudience := data.TargetAudience.ValueString() - creds, err := p.providerConfig.GetCredentials([]string{userInfoScope}, false) - if err != nil { - resp.Diagnostics.AddError( - "Error calling getCredentials()", - err.Error(), - ) - return - } + creds := fwtransport.GetCredentials(ctx, fwmodels.ProviderModel{}, false, &resp.Diagnostics) targetServiceAccount := data.TargetServiceAccount.ValueString() // If a target service account is provided, use the API to generate the idToken @@ -167,3 +147,12 @@ func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req eph data.IdToken = types.StringValue(idToken.AccessToken) resp.Diagnostics.Append(resp.Result.Set(ctx, data)...) } + +func StringSet(d basetypes.SetValue) []string { + + StringSlice := make([]string, 0) + for _, v := range d.Elements() { + StringSlice = append(StringSlice, v.(basetypes.StringValue).ValueString()) + } + return StringSlice +} From 20ec2b643bcf07f053cc9f49ed666e6b710487e0 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Wed, 6 Nov 2024 13:59:50 -0800 Subject: [PATCH 03/10] add tests / validators --- ...hemeral_google_service_account_id_token.go | 105 +++++++++++++++++- ...al_google_service_account_id_token_test.go | 99 +++++++++++++++++ 2 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index d438932caf3f..bf1e3558a8bb 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -3,12 +3,14 @@ package resourcemanager import ( "context" "fmt" + "regexp" "google.golang.org/api/idtoken" "google.golang.org/api/option" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-provider-google/google/fwmodels" @@ -27,7 +29,7 @@ type googleEphemeralServiceAccountIdToken struct { } func (p *googleEphemeralServiceAccountIdToken) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "google_test" + resp.TypeName = req.ProviderTypeName + "_service_account_id_token" } type ephemeralServiceAccountIdTokenModel struct { @@ -46,19 +48,20 @@ func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req e }, "target_service_account": schema.StringAttribute{ Optional: true, - //ValidateFunc: verify.ValidateRegexp("(" + strings.Join(verify.PossibleServiceAccountNames, "|") + ")"), + Validators: []validator.String{ + serviceAccountNameValidator{}, + }, }, "delegates": schema.SetAttribute{ Optional: true, ElementType: types.StringType, - // Validators: verify.ValidateDuration(), // duration <=3600s; TODO: support validateDuration(min,max) - // Default: "3600s", + Validators: []validator.Set{ + serviceAccountNameSetValidator{}, + }, }, "include_email": schema.BoolAttribute{ Optional: true, Computed: true, - // Default: basetypes.BoolValue(false), - //ValidateFunc: verify.ValidateRegexp("(" + strings.Join(verify.PossibleServiceAccountNames, "|") + ")"), }, "id_token": schema.StringAttribute{ Computed: true, @@ -156,3 +159,93 @@ func StringSet(d basetypes.SetValue) []string { } return StringSlice } + +var serviceAccountNamePatterns = []string{ + `^.+@.+\.iam\.gserviceaccount\.com$`, // Standard IAM service account + `^.+@developer\.gserviceaccount\.com$`, // Legacy developer service account + `^.+@appspot\.gserviceaccount\.com$`, // App Engine service account + `^.+@cloudservices\.gserviceaccount\.com$`, // Google Cloud services service account + `^.+@cloudbuild\.gserviceaccount\.com$`, // Cloud Build service account + `^service-[0-9]+@.+-compute\.iam\.gserviceaccount\.com$`, // Compute Engine service account +} + +// Create a custom validator for service account names +type serviceAccountNameValidator struct{} + +func (v serviceAccountNameValidator) Description(ctx context.Context) string { + return "value must be a valid service account email address" +} + +func (v serviceAccountNameValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v serviceAccountNameValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + value := req.ConfigValue.ValueString() + valid := false + for _, pattern := range serviceAccountNamePatterns { + if matched, _ := regexp.MatchString(pattern, value); matched { + valid = true + break + } + } + + if !valid { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Service Account Name", + "Service account name must match one of the expected patterns for Google service accounts", + ) + } +} + +// Create a custom validator for sets of service account names +type serviceAccountNameSetValidator struct{} + +func (v serviceAccountNameSetValidator) Description(ctx context.Context) string { + return "all values must be valid service account email addresses" +} + +func (v serviceAccountNameSetValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v serviceAccountNameSetValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + elements := req.ConfigValue.Elements() + for _, element := range elements { + stringValue, ok := element.(basetypes.StringValue) + if !ok { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Element Type", + "Set element must be a string", + ) + return + } + + value := stringValue.ValueString() + valid := false + for _, pattern := range serviceAccountNamePatterns { + if matched, _ := regexp.MatchString(pattern, value); matched { + valid = true + break + } + } + + if !valid { + resp.Diagnostics.AddAttributeError( + req.Path, + "Invalid Service Account Name", + fmt.Sprintf("Service account name %q must match one of the expected patterns for Google service accounts", value), + ) + } + } +} diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go new file mode 100644 index 000000000000..0636304e9f27 --- /dev/null +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go @@ -0,0 +1,99 @@ +package resourcemanager_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" +) + +func TestEphemeralServiceAccountIdToken_basic(t *testing.T) { + t.Parallel() + + serviceAccount := envvar.GetTestServiceAccountFromEnv(t) + targetServiceAccountEmail := acctest.BootstrapServiceAccount(t, "idtoken", serviceAccount) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccEphemeralServiceAccountIdToken_basic(targetServiceAccountEmail), + }, + }, + }) +} + +func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { + t.Parallel() + + serviceAccount := envvar.GetTestServiceAccountFromEnv(t) + targetServiceAccountEmail := acctest.BootstrapServiceAccount(t, "target", serviceAccount) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccEphemeralServiceAccountIdToken_withDelegates(targetServiceAccountEmail), + }, + }, + }) +} + +func TestEphemeralServiceAccountIdToken_withIncludeEmail(t *testing.T) { + t.Parallel() + + serviceAccount := envvar.GetTestServiceAccountFromEnv(t) + targetServiceAccountEmail := acctest.BootstrapServiceAccount(t, "idtoken-email", serviceAccount) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "time": {}, + }, + Steps: []resource.TestStep{ + { + Config: testAccEphemeralServiceAccountIdToken_withIncludeEmail(targetServiceAccountEmail), + }, + }, + }) +} + +func testAccEphemeralServiceAccountIdToken_basic(serviceAccountEmail string) string { + return fmt.Sprintf(` +ephemeral "google_service_account_id_token" "token" { + target_service_account = "%s" + target_audience = "https://example.com" +} +`, serviceAccountEmail) +} + +func testAccEphemeralServiceAccountIdToken_withDelegates(serviceAccountEmail string) string { + return fmt.Sprintf(` +ephemeral "google_service_account_id_token" "token" { + target_service_account = "%s" + target_audience = "https://example.com" + delegates = ["%[1]s"] +} +`, serviceAccountEmail) +} + +func testAccEphemeralServiceAccountIdToken_withIncludeEmail(serviceAccountEmail string) string { + return fmt.Sprintf(` +ephemeral "google_service_account_id_token" "token" { + target_service_account = "%s" + target_audience = "https://example.com" + include_email = true +} +`, serviceAccountEmail) +} From 32a57cc8bb33895ae32c1ecb7fbf4fe3b75ba947 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Fri, 8 Nov 2024 00:53:45 -0800 Subject: [PATCH 04/10] improve delegates test --- ...al_google_service_account_id_token_test.go | 117 ++++++++++++++++-- 1 file changed, 110 insertions(+), 7 deletions(-) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go index 0636304e9f27..09415853fa82 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go @@ -32,8 +32,11 @@ func TestEphemeralServiceAccountIdToken_basic(t *testing.T) { func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { t.Parallel() - serviceAccount := envvar.GetTestServiceAccountFromEnv(t) - targetServiceAccountEmail := acctest.BootstrapServiceAccount(t, "target", serviceAccount) + project := envvar.GetTestProjectFromEnv() + initialServiceAccount := envvar.GetTestServiceAccountFromEnv(t) + delegateServiceAccountEmailOne := acctest.BootstrapServiceAccount(t, "delegate1", initialServiceAccount) // SA_2 + delegateServiceAccountEmailTwo := acctest.BootstrapServiceAccount(t, "delegate2", delegateServiceAccountEmailOne) // SA_3 + targetServiceAccountEmail := acctest.BootstrapServiceAccount(t, "target", delegateServiceAccountEmailTwo) // SA_4 resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -43,7 +46,10 @@ func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccEphemeralServiceAccountIdToken_withDelegates(targetServiceAccountEmail), + Config: testAccEphemeralServiceAccountIdToken_delegatesSetup(initialServiceAccount, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project), + }, + { + Config: testAccEphemeralServiceAccountIdToken_withDelegates(initialServiceAccount, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project), }, }, }) @@ -78,14 +84,111 @@ ephemeral "google_service_account_id_token" "token" { `, serviceAccountEmail) } -func testAccEphemeralServiceAccountIdToken_withDelegates(serviceAccountEmail string) string { +func testAccEphemeralServiceAccountIdToken_withDelegates(initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project string) string { return fmt.Sprintf(` +resource "google_service_account_iam_binding" "sa2_to_sa3" { + service_account_id = "projects/%[5]s/serviceAccounts/%[4]s" + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:%[3]s" + ] + depends_on = [google_service_account_iam_binding.sa1_to_sa2] +} + +resource "google_service_account_iam_binding" "sa1_to_sa2" { + service_account_id = "projects/%[5]s/serviceAccounts/%[3]s" + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:%[2]s" + ] + depends_on = [google_service_account_iam_binding.terraform_to_delegate1] +} + +resource "google_service_account_iam_binding" "terraform_to_delegate1" { + service_account_id = "projects/%[5]s/serviceAccounts/%[2]s" + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:%[1]s" + ] + depends_on = [google_project_iam_member.terraform_sa_token_creator] +} + +resource "google_project_iam_member" "terraform_sa_token_creator" { + project = "%[5]s" + role = "roles/iam.serviceAccountTokenCreator" + member = "serviceAccount:%[1]s" +} + +resource "time_sleep" "wait_60_seconds" { + depends_on = [ + google_service_account_iam_binding.sa1_to_sa2, + google_service_account_iam_binding.sa2_to_sa3, + google_project_iam_member.terraform_sa_token_creator, + ] + create_duration = "60s" +} + ephemeral "google_service_account_id_token" "token" { - target_service_account = "%s" + target_service_account = "%[4]s" + delegates = [ + "%[3]s", + "%[2]s", + ] target_audience = "https://example.com" - delegates = ["%[1]s"] } -`, serviceAccountEmail) + +# The delegation chain is: +# SA_1 (initialServiceAccountEmail) -> SA_2 (delegateServiceAccountEmailOne) -> SA_3 (delegateServiceAccountEmailTwo) -> SA_4 (targetServiceAccountEmail) +`, initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project) +} + +func testAccEphemeralServiceAccountIdToken_delegatesSetup(initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project string) string { + return fmt.Sprintf(` +resource "google_service_account_iam_binding" "sa2_to_sa3" { + service_account_id = "projects/%[5]s/serviceAccounts/%[4]s" + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:%[3]s" + ] + depends_on = [google_service_account_iam_binding.sa1_to_sa2] +} + +resource "google_service_account_iam_binding" "sa1_to_sa2" { + service_account_id = "projects/%[5]s/serviceAccounts/%[3]s" + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:%[2]s" + ] + depends_on = [google_service_account_iam_binding.terraform_to_delegate1] +} + +resource "google_service_account_iam_binding" "terraform_to_delegate1" { + service_account_id = "projects/%[5]s/serviceAccounts/%[2]s" + role = "roles/iam.serviceAccountTokenCreator" + members = [ + "serviceAccount:%[1]s" + ] + depends_on = [google_project_iam_member.terraform_sa_token_creator] +} + +resource "google_project_iam_member" "terraform_sa_token_creator" { + project = "%[5]s" + role = "roles/iam.serviceAccountTokenCreator" + member = "serviceAccount:%[1]s" +} + +resource "time_sleep" "wait_60_seconds" { + depends_on = [ + google_service_account_iam_binding.sa1_to_sa2, + google_service_account_iam_binding.sa2_to_sa3, + google_project_iam_member.terraform_sa_token_creator, + ] + create_duration = "60s" +} + +# The delegation chain is: +# SA_1 (initialServiceAccountEmail) -> SA_2 (delegateServiceAccountEmailOne) -> SA_3 (delegateServiceAccountEmailTwo) -> SA_4 (targetServiceAccountEmail) +`, initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project) } func testAccEphemeralServiceAccountIdToken_withIncludeEmail(serviceAccountEmail string) string { From a5c6da5923a143ec9852e0a0c4120dfbf566ef91 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Tue, 12 Nov 2024 15:02:27 -0800 Subject: [PATCH 05/10] update validators and delegates test --- ...hemeral_google_service_account_id_token.go | 110 +----------------- ...al_google_service_account_id_token_test.go | 107 +---------------- 2 files changed, 12 insertions(+), 205 deletions(-) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index bf1e3558a8bb..b35ab16f8964 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -3,18 +3,19 @@ package resourcemanager import ( "context" "fmt" - "regexp" "google.golang.org/api/idtoken" "google.golang.org/api/option" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-provider-google/google/fwmodels" "github.com/hashicorp/terraform-provider-google/google/fwtransport" + "github.com/hashicorp/terraform-provider-google/google/fwutils" + "github.com/hashicorp/terraform-provider-google/google/fwvalidators" "google.golang.org/api/iamcredentials/v1" ) @@ -49,14 +50,14 @@ func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req e "target_service_account": schema.StringAttribute{ Optional: true, Validators: []validator.String{ - serviceAccountNameValidator{}, + fwvalidators.ServiceAccountEmailValidator{}, }, }, "delegates": schema.SetAttribute{ Optional: true, ElementType: types.StringType, Validators: []validator.Set{ - serviceAccountNameSetValidator{}, + setvalidator.ValueStringsAre(fwvalidators.ServiceAccountEmailValidator{}), }, }, "include_email": schema.BoolAttribute{ @@ -107,7 +108,7 @@ func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req eph tokenRequest := &iamcredentials.GenerateIdTokenRequest{ Audience: targetAudience, IncludeEmail: data.IncludeEmail.ValueBool(), - Delegates: StringSet(DelegatesSetValue), + Delegates: fwutils.StringSet(DelegatesSetValue), } at, err := service.Projects.ServiceAccounts.GenerateIdToken(name, tokenRequest).Do() if err != nil { @@ -150,102 +151,3 @@ func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req eph data.IdToken = types.StringValue(idToken.AccessToken) resp.Diagnostics.Append(resp.Result.Set(ctx, data)...) } - -func StringSet(d basetypes.SetValue) []string { - - StringSlice := make([]string, 0) - for _, v := range d.Elements() { - StringSlice = append(StringSlice, v.(basetypes.StringValue).ValueString()) - } - return StringSlice -} - -var serviceAccountNamePatterns = []string{ - `^.+@.+\.iam\.gserviceaccount\.com$`, // Standard IAM service account - `^.+@developer\.gserviceaccount\.com$`, // Legacy developer service account - `^.+@appspot\.gserviceaccount\.com$`, // App Engine service account - `^.+@cloudservices\.gserviceaccount\.com$`, // Google Cloud services service account - `^.+@cloudbuild\.gserviceaccount\.com$`, // Cloud Build service account - `^service-[0-9]+@.+-compute\.iam\.gserviceaccount\.com$`, // Compute Engine service account -} - -// Create a custom validator for service account names -type serviceAccountNameValidator struct{} - -func (v serviceAccountNameValidator) Description(ctx context.Context) string { - return "value must be a valid service account email address" -} - -func (v serviceAccountNameValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v serviceAccountNameValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { - return - } - - value := req.ConfigValue.ValueString() - valid := false - for _, pattern := range serviceAccountNamePatterns { - if matched, _ := regexp.MatchString(pattern, value); matched { - valid = true - break - } - } - - if !valid { - resp.Diagnostics.AddAttributeError( - req.Path, - "Invalid Service Account Name", - "Service account name must match one of the expected patterns for Google service accounts", - ) - } -} - -// Create a custom validator for sets of service account names -type serviceAccountNameSetValidator struct{} - -func (v serviceAccountNameSetValidator) Description(ctx context.Context) string { - return "all values must be valid service account email addresses" -} - -func (v serviceAccountNameSetValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v serviceAccountNameSetValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { - if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { - return - } - - elements := req.ConfigValue.Elements() - for _, element := range elements { - stringValue, ok := element.(basetypes.StringValue) - if !ok { - resp.Diagnostics.AddAttributeError( - req.Path, - "Invalid Element Type", - "Set element must be a string", - ) - return - } - - value := stringValue.ValueString() - valid := false - for _, pattern := range serviceAccountNamePatterns { - if matched, _ := regexp.MatchString(pattern, value); matched { - valid = true - break - } - } - - if !valid { - resp.Diagnostics.AddAttributeError( - req.Path, - "Invalid Service Account Name", - fmt.Sprintf("Service account name %q must match one of the expected patterns for Google service accounts", value), - ) - } - } -} diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go index 09415853fa82..e7524bb3427d 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go @@ -32,7 +32,6 @@ func TestEphemeralServiceAccountIdToken_basic(t *testing.T) { func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { t.Parallel() - project := envvar.GetTestProjectFromEnv() initialServiceAccount := envvar.GetTestServiceAccountFromEnv(t) delegateServiceAccountEmailOne := acctest.BootstrapServiceAccount(t, "delegate1", initialServiceAccount) // SA_2 delegateServiceAccountEmailTwo := acctest.BootstrapServiceAccount(t, "delegate2", delegateServiceAccountEmailOne) // SA_3 @@ -46,10 +45,7 @@ func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccEphemeralServiceAccountIdToken_delegatesSetup(initialServiceAccount, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project), - }, - { - Config: testAccEphemeralServiceAccountIdToken_withDelegates(initialServiceAccount, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project), + Config: testAccEphemeralServiceAccountIdToken_withDelegates(delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail), }, }, }) @@ -84,111 +80,20 @@ ephemeral "google_service_account_id_token" "token" { `, serviceAccountEmail) } -func testAccEphemeralServiceAccountIdToken_withDelegates(initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project string) string { +func testAccEphemeralServiceAccountIdToken_withDelegates(delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail string) string { return fmt.Sprintf(` -resource "google_service_account_iam_binding" "sa2_to_sa3" { - service_account_id = "projects/%[5]s/serviceAccounts/%[4]s" - role = "roles/iam.serviceAccountTokenCreator" - members = [ - "serviceAccount:%[3]s" - ] - depends_on = [google_service_account_iam_binding.sa1_to_sa2] -} - -resource "google_service_account_iam_binding" "sa1_to_sa2" { - service_account_id = "projects/%[5]s/serviceAccounts/%[3]s" - role = "roles/iam.serviceAccountTokenCreator" - members = [ - "serviceAccount:%[2]s" - ] - depends_on = [google_service_account_iam_binding.terraform_to_delegate1] -} - -resource "google_service_account_iam_binding" "terraform_to_delegate1" { - service_account_id = "projects/%[5]s/serviceAccounts/%[2]s" - role = "roles/iam.serviceAccountTokenCreator" - members = [ - "serviceAccount:%[1]s" - ] - depends_on = [google_project_iam_member.terraform_sa_token_creator] -} - -resource "google_project_iam_member" "terraform_sa_token_creator" { - project = "%[5]s" - role = "roles/iam.serviceAccountTokenCreator" - member = "serviceAccount:%[1]s" -} - -resource "time_sleep" "wait_60_seconds" { - depends_on = [ - google_service_account_iam_binding.sa1_to_sa2, - google_service_account_iam_binding.sa2_to_sa3, - google_project_iam_member.terraform_sa_token_creator, - ] - create_duration = "60s" -} - ephemeral "google_service_account_id_token" "token" { - target_service_account = "%[4]s" + target_service_account = "%s" delegates = [ - "%[3]s", - "%[2]s", + "%s", + "%s", ] target_audience = "https://example.com" } # The delegation chain is: # SA_1 (initialServiceAccountEmail) -> SA_2 (delegateServiceAccountEmailOne) -> SA_3 (delegateServiceAccountEmailTwo) -> SA_4 (targetServiceAccountEmail) -`, initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project) -} - -func testAccEphemeralServiceAccountIdToken_delegatesSetup(initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project string) string { - return fmt.Sprintf(` -resource "google_service_account_iam_binding" "sa2_to_sa3" { - service_account_id = "projects/%[5]s/serviceAccounts/%[4]s" - role = "roles/iam.serviceAccountTokenCreator" - members = [ - "serviceAccount:%[3]s" - ] - depends_on = [google_service_account_iam_binding.sa1_to_sa2] -} - -resource "google_service_account_iam_binding" "sa1_to_sa2" { - service_account_id = "projects/%[5]s/serviceAccounts/%[3]s" - role = "roles/iam.serviceAccountTokenCreator" - members = [ - "serviceAccount:%[2]s" - ] - depends_on = [google_service_account_iam_binding.terraform_to_delegate1] -} - -resource "google_service_account_iam_binding" "terraform_to_delegate1" { - service_account_id = "projects/%[5]s/serviceAccounts/%[2]s" - role = "roles/iam.serviceAccountTokenCreator" - members = [ - "serviceAccount:%[1]s" - ] - depends_on = [google_project_iam_member.terraform_sa_token_creator] -} - -resource "google_project_iam_member" "terraform_sa_token_creator" { - project = "%[5]s" - role = "roles/iam.serviceAccountTokenCreator" - member = "serviceAccount:%[1]s" -} - -resource "time_sleep" "wait_60_seconds" { - depends_on = [ - google_service_account_iam_binding.sa1_to_sa2, - google_service_account_iam_binding.sa2_to_sa3, - google_project_iam_member.terraform_sa_token_creator, - ] - create_duration = "60s" -} - -# The delegation chain is: -# SA_1 (initialServiceAccountEmail) -> SA_2 (delegateServiceAccountEmailOne) -> SA_3 (delegateServiceAccountEmailTwo) -> SA_4 (targetServiceAccountEmail) -`, initialServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo, targetServiceAccountEmail, project) +`, targetServiceAccountEmail, delegateServiceAccountEmailOne, delegateServiceAccountEmailTwo) } func testAccEphemeralServiceAccountIdToken_withIncludeEmail(serviceAccountEmail string) string { From 01fc7a3ec31ca06ef4a43ad47ddbc61cd811be44 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Wed, 13 Nov 2024 18:07:18 -0800 Subject: [PATCH 06/10] refactor to use PF standards --- .../ephemeral_google_service_account_id_token.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index b35ab16f8964..1d7479965efc 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -99,11 +99,11 @@ func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req eph targetAudience := data.TargetAudience.ValueString() creds := fwtransport.GetCredentials(ctx, fwmodels.ProviderModel{}, false, &resp.Diagnostics) - targetServiceAccount := data.TargetServiceAccount.ValueString() + targetServiceAccount := data.TargetServiceAccount // If a target service account is provided, use the API to generate the idToken - if targetServiceAccount != "" { + if !targetServiceAccount.IsNull() && !targetServiceAccount.IsUnknown() { service := p.providerConfig.NewIamCredentialsClient(p.providerConfig.UserAgent) - name := fmt.Sprintf("projects/-/serviceAccounts/%s", targetServiceAccount) + name := fmt.Sprintf("projects/-/serviceAccounts/%s", targetServiceAccount.ValueString()) DelegatesSetValue, _ := data.Delegates.ToSetValue(ctx) tokenRequest := &iamcredentials.GenerateIdTokenRequest{ Audience: targetAudience, From 0035c097e5ad9e0ce4b02f0ed5f562bd35dfd80f Mon Sep 17 00:00:00 2001 From: BBBmau Date: Wed, 13 Nov 2024 18:08:15 -0800 Subject: [PATCH 07/10] simplify delegates StringSet --- .../ephemeral_google_service_account_id_token.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index 1d7479965efc..cc9e8cef46d9 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -104,11 +104,11 @@ func (p *googleEphemeralServiceAccountIdToken) Open(ctx context.Context, req eph if !targetServiceAccount.IsNull() && !targetServiceAccount.IsUnknown() { service := p.providerConfig.NewIamCredentialsClient(p.providerConfig.UserAgent) name := fmt.Sprintf("projects/-/serviceAccounts/%s", targetServiceAccount.ValueString()) - DelegatesSetValue, _ := data.Delegates.ToSetValue(ctx) + tokenRequest := &iamcredentials.GenerateIdTokenRequest{ Audience: targetAudience, IncludeEmail: data.IncludeEmail.ValueBool(), - Delegates: fwutils.StringSet(DelegatesSetValue), + Delegates: fwutils.StringSet(data.Delegates), } at, err := service.Projects.ServiceAccounts.GenerateIdToken(name, tokenRequest).Do() if err != nil { From aaa2b434dd10e7709f972160f9de58d9c59510df Mon Sep 17 00:00:00 2001 From: BBBmau Date: Wed, 13 Nov 2024 18:09:38 -0800 Subject: [PATCH 08/10] add markdown descriptions --- .../ephemeral_google_service_account_id_token.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index cc9e8cef46d9..4dcfcfab47a8 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -42,6 +42,9 @@ type ephemeralServiceAccountIdTokenModel struct { } func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema.Description = "This ephemeral resource provides a Google OpenID Connect (oidc) id_token." + resp.Schema.MarkdownDescription = "This ephemeral resource provides a Google OpenID Connect (oidc) id_token." + resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "target_audience": schema.StringAttribute{ From 8cf58454bcccda00da77324cca4e11615ddf1f35 Mon Sep 17 00:00:00 2001 From: BBBmau Date: Thu, 14 Nov 2024 16:02:27 -0800 Subject: [PATCH 09/10] add ephemeral-resource markdwon doc and field descriptions --- ...hemeral_google_service_account_id_token.go | 17 ++-- .../service_account_id_token.html.markdown | 89 +++++++++++++++++++ 2 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 mmv1/third_party/terraform/website/docs/ephemeral-resources/service_account_id_token.html.markdown diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go index 4dcfcfab47a8..fb3e9d0db698 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token.go @@ -48,15 +48,18 @@ func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req e resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "target_audience": schema.StringAttribute{ - Required: true, + Description: "The audience claim for the `id_token`.", + Required: true, }, "target_service_account": schema.StringAttribute{ - Optional: true, + Description: "The email of the service account being impersonated. Used only when using impersonation mode.", + Optional: true, Validators: []validator.String{ fwvalidators.ServiceAccountEmailValidator{}, }, }, "delegates": schema.SetAttribute{ + Description: "Delegate chain of approvals needed to perform full impersonation. Specify the fully qualified service account name. Used only when using impersonation mode.", Optional: true, ElementType: types.StringType, Validators: []validator.Set{ @@ -64,12 +67,14 @@ func (p *googleEphemeralServiceAccountIdToken) Schema(ctx context.Context, req e }, }, "include_email": schema.BoolAttribute{ - Optional: true, - Computed: true, + Description: "Include the verified email in the claim. Used only when using impersonation mode.", + Optional: true, + Computed: true, }, "id_token": schema.StringAttribute{ - Computed: true, - Sensitive: true, + Description: "The `id_token` representing the new generated identity.", + Computed: true, + Sensitive: true, }, }, } diff --git a/mmv1/third_party/terraform/website/docs/ephemeral-resources/service_account_id_token.html.markdown b/mmv1/third_party/terraform/website/docs/ephemeral-resources/service_account_id_token.html.markdown new file mode 100644 index 000000000000..00cd9c30b0e4 --- /dev/null +++ b/mmv1/third_party/terraform/website/docs/ephemeral-resources/service_account_id_token.html.markdown @@ -0,0 +1,89 @@ +--- +subcategory: "Cloud Platform" +description: |- + Produces OpenID Connect token for service accounts +--- + +# google_service_account_id_token + +This ephemeral resource provides a Google OpenID Connect (`oidc`) `id_token`. Tokens issued from this ephemeral resource are typically used to call external services that accept OIDC tokens for authentication (e.g. [Google Cloud Run](https://cloud.google.com/run/docs/authenticating/service-to-service)). + +For more information see +[OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html#IDToken). + +## Example Usage - ServiceAccount JSON credential file. + `google_service_account_id_token` will use the configured [provider credentials](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#credentials-1) + + ```hcl + ephemeral "google_service_account_id_token" "oidc" { + target_audience = "https://foo.bar/" + } + ``` + +## Example Usage - Service Account Impersonation. + `google_service_account_access_token` will use background impersonated credentials provided by [google_service_account_access_token](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/service_account_access_token). + + Note: to use the following, you must grant `target_service_account` the + `roles/iam.serviceAccountTokenCreator` role on itself. + + ```hcl + data "google_service_account_access_token" "impersonated" { + provider = google + target_service_account = "impersonated-account@project.iam.gserviceaccount.com" + delegates = [] + scopes = ["userinfo-email", "cloud-platform"] + lifetime = "300s" + } + + provider "google" { + alias = "impersonated" + access_token = data.google_service_account_access_token.impersonated.access_token + } + + ephemeral "google_service_account_id_token" "oidc" { + provider = google.impersonated + target_service_account = "impersonated-account@project.iam.gserviceaccount.com" + delegates = [] + include_email = true + target_audience = "https://foo.bar/" + } + + ``` + +## Example Usage - Invoking Cloud Run Endpoint + + The following configuration will invoke [Cloud Run](https://cloud.google.com/run/docs/authenticating/service-to-service) endpoint where the service account for Terraform has been granted `roles/run.invoker` role previously. + +```hcl + +ephemeral "google_service_account_id_token" "oidc" { + target_audience = "https://your.cloud.run.app/" +} + +data "http" "cloudrun" { + url = "https://your.cloud.run.app/" + request_headers = { + Authorization = "Bearer ${ephemeral.google_service_account_id_token.oidc.id_token}" + } +} + + +output "cloud_run_response" { + value = data.http.cloudrun.body +} +``` + +## Argument Reference + +The following arguments are supported: + +* `target_audience` (Required) - The audience claim for the `id_token`. +* `target_service_account` (Optional) - The email of the service account being impersonated. Used only when using impersonation mode. +* `delegates` (Optional) - Delegate chain of approvals needed to perform full impersonation. Specify the fully qualified service account name. Used only when using impersonation mode. +* `include_email` (Optional) Include the verified email in the claim. Used only when using impersonation mode. + +## Attributes Reference + +The following attribute is exported: + +* `id_token` - The `id_token` representing the new generated identity. From 8b9073c0504d25f6978af4d8b4f5e912d9bc9269 Mon Sep 17 00:00:00 2001 From: Sarah French <15078782+SarahFrench@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:59:28 +0000 Subject: [PATCH 10/10] Make all acceptance tests' names start TestAcc --- .../ephemeral_google_service_account_id_token_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go index e7524bb3427d..7ba832d24d73 100644 --- a/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go +++ b/mmv1/third_party/terraform/services/resourcemanager/ephemeral_google_service_account_id_token_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-provider-google/google/envvar" ) -func TestEphemeralServiceAccountIdToken_basic(t *testing.T) { +func TestAccEphemeralServiceAccountIdToken_basic(t *testing.T) { t.Parallel() serviceAccount := envvar.GetTestServiceAccountFromEnv(t) @@ -29,7 +29,7 @@ func TestEphemeralServiceAccountIdToken_basic(t *testing.T) { }) } -func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { +func TestAccEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { t.Parallel() initialServiceAccount := envvar.GetTestServiceAccountFromEnv(t) @@ -51,7 +51,7 @@ func TestEphemeralServiceAccountIdToken_withDelegates(t *testing.T) { }) } -func TestEphemeralServiceAccountIdToken_withIncludeEmail(t *testing.T) { +func TestAccEphemeralServiceAccountIdToken_withIncludeEmail(t *testing.T) { t.Parallel() serviceAccount := envvar.GetTestServiceAccountFromEnv(t)