google/externalaccount: add support for workforce pool credentials
Workforce pools (external account credentials for non-Google users) are organization-level resources which means that issued workforce pool tokens will not have any client project ID on token exchange as currently designed. "To use a Google API, the client must identify the application to the server. If the API requires authentication, the client must also identify the principal running the application." The application here is the client project. The token will identify the user principal but not the application. This will result in APIs rejecting requests authenticated with these tokens. Note that passing a x-goog-user-project override header on API request is still not sufficient. The token is still expected to have a client project. As a result, we have extended the spec to support an additional workforce_pool_user_project for these credentials (workforce pools) which will be passed when exchanging an external token for a Google Access token. After the exchange, the issued access token will use the supplied project as the client project. The underlying principal must still have serviceusage.services.use IAM permission to use the project for billing/quota. This field is not needed for flows with basic client authentication (e.g. client ID is supplied). The client ID is sufficient to determine the client project and any additionally supplied workforce_pool_user_project value will be ignored. Note that this feature is not usable yet publicly. Change-Id: I8311d7783e4048c260cbb68e90d3565df864d7e0 GitHub-Last-Rev: a6dc5ebc95207b4cf04a0f3df45e745b24cd76c4 GitHub-Pull-Request: golang/oauth2#520 Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/353393 Reviewed-by: Cody Oss <codyoss@google.com> Reviewed-by: Bassam Ojeil <bojeil@google.com> Trust: Cody Oss <codyoss@google.com> Trust: Tyler Bui-Palsulich <tbp@google.com> Run-TryBot: Cody Oss <codyoss@google.com> TryBot-Result: Go Bot <gobot@golang.org>
This commit is contained in:
@@ -53,6 +53,11 @@ type Config struct {
|
||||
QuotaProjectID string
|
||||
// Scopes contains the desired scopes for the returned access token.
|
||||
Scopes []string
|
||||
// The optional workforce pool user project number when the credential
|
||||
// corresponds to a workforce pool and not a workload identity pool.
|
||||
// The underlying principal must still have serviceusage.services.use IAM
|
||||
// permission to use the project for billing/quota.
|
||||
WorkforcePoolUserProject string
|
||||
}
|
||||
|
||||
// Each element consists of a list of patterns. validateURLs checks for matches
|
||||
@@ -73,6 +78,7 @@ var (
|
||||
regexp.MustCompile(`^iamcredentials\.[^\.\s\/\\]+\.googleapis\.com$`),
|
||||
regexp.MustCompile(`^[^\.\s\/\\]+-iamcredentials\.googleapis\.com$`),
|
||||
}
|
||||
validWorkforceAudiencePattern *regexp.Regexp = regexp.MustCompile(`//iam\.googleapis\.com/locations/[^/]+/workforcePools/`)
|
||||
)
|
||||
|
||||
func validateURL(input string, patterns []*regexp.Regexp, scheme string) bool {
|
||||
@@ -86,14 +92,17 @@ func validateURL(input string, patterns []*regexp.Regexp, scheme string) bool {
|
||||
toTest := parsed.Host
|
||||
|
||||
for _, pattern := range patterns {
|
||||
|
||||
if valid := pattern.MatchString(toTest); valid {
|
||||
if pattern.MatchString(toTest) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateWorkforceAudience(input string) bool {
|
||||
return validWorkforceAudiencePattern.MatchString(input)
|
||||
}
|
||||
|
||||
// TokenSource Returns an external account TokenSource struct. This is to be called by package google to construct a google.Credentials.
|
||||
func (c *Config) TokenSource(ctx context.Context) (oauth2.TokenSource, error) {
|
||||
return c.tokenSource(ctx, validTokenURLPatterns, validImpersonateURLPatterns, "https")
|
||||
@@ -115,6 +124,13 @@ func (c *Config) tokenSource(ctx context.Context, tokenURLValidPats []*regexp.Re
|
||||
}
|
||||
}
|
||||
|
||||
if c.WorkforcePoolUserProject != "" {
|
||||
valid := validateWorkforceAudience(c.Audience)
|
||||
if !valid {
|
||||
return nil, fmt.Errorf("oauth2/google: workforce_pool_user_project should not be set for non-workforce pool credentials")
|
||||
}
|
||||
}
|
||||
|
||||
ts := tokenSource{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
@@ -224,7 +240,15 @@ func (ts tokenSource) Token() (*oauth2.Token, error) {
|
||||
ClientID: conf.ClientID,
|
||||
ClientSecret: conf.ClientSecret,
|
||||
}
|
||||
stsResp, err := exchangeToken(ts.ctx, conf.TokenURL, &stsRequest, clientAuth, header, nil)
|
||||
var options map[string]interface{}
|
||||
// Do not pass workforce_pool_user_project when client authentication is used.
|
||||
// The client ID is sufficient for determining the user project.
|
||||
if conf.WorkforcePoolUserProject != "" && conf.ClientID == "" {
|
||||
options = map[string]interface{}{
|
||||
"userProject": conf.WorkforcePoolUserProject,
|
||||
}
|
||||
}
|
||||
stsResp, err := exchangeToken(ts.ctx, conf.TokenURL, &stsRequest, clientAuth, header, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user