-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New data source for oidc tokens (#3739)
Co-authored-by: Cameron Thornton <[email protected]> Co-authored-by: salmaan rashid <[email protected]>
- Loading branch information
1 parent
076c3c2
commit 5cc4d2f
Showing
5 changed files
with
368 additions
and
7 deletions.
There are no files selected for viewing
127 changes: 127 additions & 0 deletions
127
third_party/terraform/data_sources/data_source_google_service_account_id_token.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package google | ||
|
||
import ( | ||
"time" | ||
|
||
"fmt" | ||
"strings" | ||
|
||
iamcredentials "google.golang.org/api/iamcredentials/v1" | ||
"google.golang.org/api/idtoken" | ||
"google.golang.org/api/option" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/schema" | ||
"golang.org/x/net/context" | ||
) | ||
|
||
const ( | ||
userInfoScope = "https://www.googleapis.com/auth/userinfo.email" | ||
) | ||
|
||
func dataSourceGoogleServiceAccountIdToken() *schema.Resource { | ||
|
||
return &schema.Resource{ | ||
Read: dataSourceGoogleServiceAccountIdTokenRead, | ||
Schema: map[string]*schema.Schema{ | ||
"target_audience": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"target_service_account": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validateRegexp("(" + strings.Join(PossibleServiceAccountNames, "|") + ")"), | ||
}, | ||
"delegates": { | ||
Type: schema.TypeSet, | ||
Optional: true, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
ValidateFunc: validateRegexp(ServiceAccountLinkRegex), | ||
}, | ||
}, | ||
"include_email": { | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
Default: false, | ||
}, | ||
// Not used currently | ||
// https://github.com/googleapis/google-api-go-client/issues/542 | ||
// "format": { | ||
// Type: schema.TypeString, | ||
// Optional: true, | ||
// ValidateFunc: validation.StringInSlice([]string{ | ||
// "FULL", "STANDARD"}, true), | ||
// Default: "STANDARD", | ||
// }, | ||
"id_token": { | ||
Type: schema.TypeString, | ||
Sensitive: true, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func dataSourceGoogleServiceAccountIdTokenRead(d *schema.ResourceData, meta interface{}) error { | ||
|
||
config := meta.(*Config) | ||
targetAudience := d.Get("target_audience").(string) | ||
creds, err := config.GetCredentials([]string{userInfoScope}) | ||
if err != nil { | ||
return fmt.Errorf("error calling getCredentials(): %v", err) | ||
} | ||
|
||
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 { | ||
// Use | ||
// https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken | ||
service := config.clientIamCredentials | ||
name := fmt.Sprintf("projects/-/serviceAccounts/%s", d.Get("target_service_account").(string)) | ||
tokenRequest := &iamcredentials.GenerateIdTokenRequest{ | ||
Audience: targetAudience, | ||
IncludeEmail: d.Get("include_email").(bool), | ||
Delegates: convertStringSet(d.Get("delegates").(*schema.Set)), | ||
} | ||
at, err := service.Projects.ServiceAccounts.GenerateIdToken(name, tokenRequest).Do() | ||
if err != nil { | ||
return fmt.Errorf("error calling iamcredentials.GenerateIdToken: %v", err) | ||
} | ||
|
||
d.SetId(time.Now().UTC().String()) | ||
d.Set("id_token", at.Token) | ||
|
||
return nil | ||
} | ||
|
||
tok, err := ts.Token() | ||
if err != nil { | ||
return fmt.Errorf("unable to get Token() from tokenSource: %v", err) | ||
} | ||
|
||
// only user-credential TokenSources have refreshTokens | ||
if tok.RefreshToken != "" { | ||
return fmt.Errorf("unsupported Credential Type supplied. Use serviceAccount 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 { | ||
return fmt.Errorf("unable to retrieve TokenSource: %v", err) | ||
} | ||
idToken, err := idTokenSource.Token() | ||
if err != nil { | ||
return fmt.Errorf("unable to retrieve Token: %v", err) | ||
} | ||
|
||
d.SetId(time.Now().UTC().String()) | ||
d.Set("id_token", idToken.AccessToken) | ||
|
||
return nil | ||
} |
111 changes: 111 additions & 0 deletions
111
third_party/terraform/tests/data_source_google_service_account_id_token_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package google | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"fmt" | ||
|
||
"google.golang.org/api/idtoken" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-sdk/terraform" | ||
) | ||
|
||
const targetAudience = "https://foo.bar/" | ||
|
||
func testAccCheckServiceAccountIdTokenValue(name, audience string) resource.TestCheckFunc { | ||
return func(s *terraform.State) error { | ||
ms := s.RootModule() | ||
|
||
rs, ok := ms.Resources[name] | ||
if !ok { | ||
return fmt.Errorf("can't find %s in state", name) | ||
} | ||
|
||
v, ok := rs.Primary.Attributes["id_token"] | ||
if !ok { | ||
return fmt.Errorf("id_token not found") | ||
} | ||
|
||
_, err := idtoken.Validate(context.Background(), v, audience) | ||
if err != nil { | ||
return fmt.Errorf("token validation failed: %v", err) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func TestAccDataSourceGoogleServiceAccountIdToken_basic(t *testing.T) { | ||
t.Parallel() | ||
|
||
resourceName := "data.google_service_account_id_token.default" | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccCheckGoogleServiceAccountIdToken_basic(targetAudience), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(resourceName, "target_audience", targetAudience), | ||
testAccCheckServiceAccountIdTokenValue(resourceName, targetAudience), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccCheckGoogleServiceAccountIdToken_basic(targetAudience string) string { | ||
|
||
return fmt.Sprintf(` | ||
data "google_service_account_id_token" "default" { | ||
target_audience = "%s" | ||
} | ||
`, targetAudience) | ||
} | ||
|
||
func TestAccDataSourceGoogleServiceAccountIdToken_impersonation(t *testing.T) { | ||
t.Parallel() | ||
|
||
resourceName := "data.google_service_account_id_token.default" | ||
serviceAccount := getTestServiceAccountFromEnv(t) | ||
targetServiceAccountEmail := BootstrapServiceAccount(t, getTestProjectFromEnv(), serviceAccount) | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
Steps: []resource.TestStep{ | ||
{ | ||
Config: testAccCheckGoogleServiceAccountIdToken_impersonation_datasource(targetAudience, targetServiceAccountEmail), | ||
Check: resource.ComposeTestCheckFunc( | ||
resource.TestCheckResourceAttr(resourceName, "target_audience", targetAudience), | ||
testAccCheckServiceAccountIdTokenValue(resourceName, targetAudience), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
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" | ||
} | ||
`, targetServiceAccount, targetServiceAccount, targetAudience) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.