|
@@ -7,10 +7,14 @@ package externalaccount
|
|
|
import (
|
|
|
"context"
|
|
|
"fmt"
|
|
|
- "golang.org/x/oauth2"
|
|
|
"net/http"
|
|
|
+ "net/url"
|
|
|
+ "regexp"
|
|
|
"strconv"
|
|
|
+ "strings"
|
|
|
"time"
|
|
|
+
|
|
|
+ "golang.org/x/oauth2"
|
|
|
)
|
|
|
|
|
|
// now aliases time.Now for testing
|
|
@@ -22,43 +26,101 @@ var now = func() time.Time {
|
|
|
type Config struct {
|
|
|
// Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
|
|
|
// identity pool or the workforce pool and the provider identifier in that pool.
|
|
|
- Audience string
|
|
|
+ Audience string
|
|
|
// SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
|
|
|
// e.g. `urn:ietf:params:oauth:token-type:jwt`.
|
|
|
- SubjectTokenType string
|
|
|
+ SubjectTokenType string
|
|
|
// TokenURL is the STS token exchange endpoint.
|
|
|
- TokenURL string
|
|
|
+ TokenURL string
|
|
|
// TokenInfoURL is the token_info endpoint used to retrieve the account related information (
|
|
|
// user attributes like account identifier, eg. email, username, uid, etc). This is
|
|
|
// needed for gCloud session account identification.
|
|
|
- TokenInfoURL string
|
|
|
+ TokenInfoURL string
|
|
|
// ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
|
|
|
// required for workload identity pools when APIs to be accessed have not integrated with UberMint.
|
|
|
ServiceAccountImpersonationURL string
|
|
|
// ClientSecret is currently only required if token_info endpoint also
|
|
|
// needs to be called with the generated GCP access token. When provided, STS will be
|
|
|
// called with additional basic authentication using client_id as username and client_secret as password.
|
|
|
- ClientSecret string
|
|
|
+ ClientSecret string
|
|
|
// ClientID is only required in conjunction with ClientSecret, as described above.
|
|
|
- ClientID string
|
|
|
+ ClientID string
|
|
|
// CredentialSource contains the necessary information to retrieve the token itself, as well
|
|
|
// as some environmental information.
|
|
|
- CredentialSource CredentialSource
|
|
|
+ CredentialSource CredentialSource
|
|
|
// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
|
|
|
// will set the x-goog-user-project which overrides the project associated with the credentials.
|
|
|
- QuotaProjectID string
|
|
|
+ QuotaProjectID string
|
|
|
// Scopes contains the desired scopes for the returned access token.
|
|
|
- Scopes []string
|
|
|
+ Scopes []string
|
|
|
+}
|
|
|
+
|
|
|
+// Each element consists of a list of patterns. validateURLs checks for matches
|
|
|
+// that include all elements in a given list, in that order.
|
|
|
+
|
|
|
+var (
|
|
|
+ validTokenURLPatterns = []*regexp.Regexp{
|
|
|
+ // The complicated part in the middle matches any number of characters that
|
|
|
+ // aren't period, spaces, or slashes.
|
|
|
+ regexp.MustCompile(`(?i)^[^\.\s\/\\]+\.sts\.googleapis\.com$`),
|
|
|
+ regexp.MustCompile(`(?i)^sts\.googleapis\.com$`),
|
|
|
+ regexp.MustCompile(`(?i)^sts\.[^\.\s\/\\]+\.googleapis\.com$`),
|
|
|
+ regexp.MustCompile(`(?i)^[^\.\s\/\\]+-sts\.googleapis\.com$`),
|
|
|
+ }
|
|
|
+ validImpersonateURLPatterns = []*regexp.Regexp{
|
|
|
+ regexp.MustCompile(`^[^\.\s\/\\]+\.iamcredentials\.googleapis\.com$`),
|
|
|
+ regexp.MustCompile(`^iamcredentials\.googleapis\.com$`),
|
|
|
+ regexp.MustCompile(`^iamcredentials\.[^\.\s\/\\]+\.googleapis\.com$`),
|
|
|
+ regexp.MustCompile(`^[^\.\s\/\\]+-iamcredentials\.googleapis\.com$`),
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+func validateURL(input string, patterns []*regexp.Regexp, scheme string) bool {
|
|
|
+ parsed, err := url.Parse(input)
|
|
|
+ if err != nil {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ if !strings.EqualFold(parsed.Scheme, scheme) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ toTest := parsed.Host
|
|
|
+
|
|
|
+ for _, pattern := range patterns {
|
|
|
+
|
|
|
+ if valid := pattern.MatchString(toTest); valid {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false
|
|
|
}
|
|
|
|
|
|
// 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 {
|
|
|
+func (c *Config) TokenSource(ctx context.Context) (oauth2.TokenSource, error) {
|
|
|
+ return c.tokenSource(ctx, validTokenURLPatterns, validImpersonateURLPatterns, "https")
|
|
|
+}
|
|
|
+
|
|
|
+// tokenSource is a private function that's directly called by some of the tests,
|
|
|
+// because the unit test URLs are mocked, and would otherwise fail the
|
|
|
+// validity check.
|
|
|
+func (c *Config) tokenSource(ctx context.Context, tokenURLValidPats []*regexp.Regexp, impersonateURLValidPats []*regexp.Regexp, scheme string) (oauth2.TokenSource, error) {
|
|
|
+ valid := validateURL(c.TokenURL, tokenURLValidPats, scheme)
|
|
|
+ if !valid {
|
|
|
+ return nil, fmt.Errorf("oauth2/google: invalid TokenURL provided while constructing tokenSource")
|
|
|
+ }
|
|
|
+
|
|
|
+ if c.ServiceAccountImpersonationURL != "" {
|
|
|
+ valid := validateURL(c.ServiceAccountImpersonationURL, impersonateURLValidPats, scheme)
|
|
|
+ if !valid {
|
|
|
+ return nil, fmt.Errorf("oauth2/google: invalid ServiceAccountImpersonationURL provided while constructing tokenSource")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
ts := tokenSource{
|
|
|
ctx: ctx,
|
|
|
conf: c,
|
|
|
}
|
|
|
if c.ServiceAccountImpersonationURL == "" {
|
|
|
- return oauth2.ReuseTokenSource(nil, ts)
|
|
|
+ return oauth2.ReuseTokenSource(nil, ts), nil
|
|
|
}
|
|
|
scopes := c.Scopes
|
|
|
ts.conf.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
|
@@ -68,7 +130,7 @@ func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
|
|
scopes: scopes,
|
|
|
ts: oauth2.ReuseTokenSource(nil, ts),
|
|
|
}
|
|
|
- return oauth2.ReuseTokenSource(nil, imp)
|
|
|
+ return oauth2.ReuseTokenSource(nil, imp), nil
|
|
|
}
|
|
|
|
|
|
// Subject token file types.
|
|
@@ -78,9 +140,9 @@ const (
|
|
|
)
|
|
|
|
|
|
type format struct {
|
|
|
- // Type is either "text" or "json". When not provided "text" type is assumed.
|
|
|
+ // Type is either "text" or "json". When not provided "text" type is assumed.
|
|
|
Type string `json:"type"`
|
|
|
- // SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
|
|
|
+ // SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
|
|
|
SubjectTokenFieldName string `json:"subject_token_field_name"`
|
|
|
}
|
|
|
|
|
@@ -128,7 +190,7 @@ type baseCredentialSource interface {
|
|
|
subjectToken() (string, error)
|
|
|
}
|
|
|
|
|
|
-// tokenSource is the source that handles external credentials. It is used to retrieve Tokens.
|
|
|
+// tokenSource is the source that handles external credentials. It is used to retrieve Tokens.
|
|
|
type tokenSource struct {
|
|
|
ctx context.Context
|
|
|
conf *Config
|