forked from Mirrors/oauth2
google/externalaccount: validate tokenURL and ServiceAccountImpersonationURL
This commit is contained in:
parent
387bb65d12
commit
0925f5e864
|
@ -650,7 +650,7 @@ func TestAwsCredential_BasicRequestWithDefaultEnv(t *testing.T) {
|
||||||
getenv = setEnvironment(map[string]string{
|
getenv = setEnvironment(map[string]string{
|
||||||
"AWS_ACCESS_KEY_ID": "AKIDEXAMPLE",
|
"AWS_ACCESS_KEY_ID": "AKIDEXAMPLE",
|
||||||
"AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
"AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||||
"AWS_DEFAULT_REGION": "us-west-1",
|
"AWS_DEFAULT_REGION": "us-west-1",
|
||||||
})
|
})
|
||||||
|
|
||||||
base, err := tfc.parse(context.Background())
|
base, err := tfc.parse(context.Background())
|
||||||
|
@ -688,7 +688,7 @@ func TestAwsCredential_BasicRequestWithTwoRegions(t *testing.T) {
|
||||||
"AWS_ACCESS_KEY_ID": "AKIDEXAMPLE",
|
"AWS_ACCESS_KEY_ID": "AKIDEXAMPLE",
|
||||||
"AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
"AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY",
|
||||||
"AWS_REGION": "us-west-1",
|
"AWS_REGION": "us-west-1",
|
||||||
"AWS_DEFAULT_REGION": "us-east-1",
|
"AWS_DEFAULT_REGION": "us-east-1",
|
||||||
})
|
})
|
||||||
|
|
||||||
base, err := tfc.parse(context.Background())
|
base, err := tfc.parse(context.Background())
|
||||||
|
|
|
@ -7,10 +7,12 @@ package externalaccount
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// now aliases time.Now for testing
|
// now aliases time.Now for testing
|
||||||
|
@ -22,43 +24,102 @@ var now = func() time.Time {
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
|
// 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.
|
// 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
|
// SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
|
||||||
// e.g. `urn:ietf:params:oauth:token-type:jwt`.
|
// e.g. `urn:ietf:params:oauth:token-type:jwt`.
|
||||||
SubjectTokenType string
|
SubjectTokenType string
|
||||||
// TokenURL is the STS token exchange endpoint.
|
// TokenURL is the STS token exchange endpoint.
|
||||||
TokenURL string
|
TokenURL string
|
||||||
// TokenInfoURL is the token_info endpoint used to retrieve the account related information (
|
// 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
|
// user attributes like account identifier, eg. email, username, uid, etc). This is
|
||||||
// needed for gCloud session account identification.
|
// needed for gCloud session account identification.
|
||||||
TokenInfoURL string
|
TokenInfoURL string
|
||||||
// ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
|
// 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.
|
// required for workload identity pools when APIs to be accessed have not integrated with UberMint.
|
||||||
ServiceAccountImpersonationURL string
|
ServiceAccountImpersonationURL string
|
||||||
// ClientSecret is currently only required if token_info endpoint also
|
// 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
|
// 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.
|
// 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 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
|
// CredentialSource contains the necessary information to retrieve the token itself, as well
|
||||||
// as some environmental information.
|
// as some environmental information.
|
||||||
CredentialSource CredentialSource
|
CredentialSource CredentialSource
|
||||||
// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
|
// 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.
|
// 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 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 = []string{
|
||||||
|
"https://[^\\.]+\\.sts\\.googleapis\\.com",
|
||||||
|
"https://sts\\.googleapis\\.com",
|
||||||
|
"https://sts\\.[^\\.]+\\.googleapis\\.com",
|
||||||
|
"https://[^\\.]+-sts\\.googleapis\\.com",
|
||||||
|
}
|
||||||
|
validImpersonateURLPatterns = []string{
|
||||||
|
"https://[^\\.]+\\.iamcredentials\\.googleapis\\.com",
|
||||||
|
"https://iamcredentials\\.googleapis\\.com",
|
||||||
|
"https://iamcredentials\\.[^\\.]+\\.googleapis\\.com",
|
||||||
|
"https://[^\\.]+-iamcredentials\\.googleapis\\.com",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func validateURL(input string, patterns []string) (bool, error) {
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
valid, err := regexp.MatchString(pattern, input)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if valid {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenSource Returns an external account TokenSource struct. This is to be called by package google to construct a google.Credentials.
|
// 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, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, testing bool) (oauth2.TokenSource, error) {
|
||||||
|
if !testing {
|
||||||
|
// Check the validity of TokenURL.
|
||||||
|
valid, err := validateURL(c.TokenURL, validTokenURLPatterns)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: invalid TokenURL provided while constructing tokenSource")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ServiceAccountImpersonationURL is present, check its validity.
|
||||||
|
if c.ServiceAccountImpersonationURL != "" {
|
||||||
|
valid, err := validateURL(c.ServiceAccountImpersonationURL, validImpersonateURLPatterns)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: invalid ServiceAccountImpersonationURL provided while constructing tokenSource")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ts := tokenSource{
|
ts := tokenSource{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
conf: c,
|
conf: c,
|
||||||
}
|
}
|
||||||
if c.ServiceAccountImpersonationURL == "" {
|
if c.ServiceAccountImpersonationURL == "" {
|
||||||
return oauth2.ReuseTokenSource(nil, ts)
|
return oauth2.ReuseTokenSource(nil, ts), nil
|
||||||
}
|
}
|
||||||
scopes := c.Scopes
|
scopes := c.Scopes
|
||||||
ts.conf.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
ts.conf.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
||||||
|
@ -68,7 +129,7 @@ func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||||
scopes: scopes,
|
scopes: scopes,
|
||||||
ts: oauth2.ReuseTokenSource(nil, ts),
|
ts: oauth2.ReuseTokenSource(nil, ts),
|
||||||
}
|
}
|
||||||
return oauth2.ReuseTokenSource(nil, imp)
|
return oauth2.ReuseTokenSource(nil, imp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subject token file types.
|
// Subject token file types.
|
||||||
|
@ -78,9 +139,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type format struct {
|
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"`
|
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"`
|
SubjectTokenFieldName string `json:"subject_token_field_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +189,7 @@ type baseCredentialSource interface {
|
||||||
subjectToken() (string, error)
|
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 {
|
type tokenSource struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
conf *Config
|
conf *Config
|
||||||
|
|
|
@ -95,3 +95,43 @@ func TestToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateURL(t *testing.T) {
|
||||||
|
var urlValidityTests = []struct {
|
||||||
|
input string
|
||||||
|
pattern []string
|
||||||
|
result bool
|
||||||
|
}{
|
||||||
|
{"https://sts.googleapis.com", validTokenURLPatterns, true},
|
||||||
|
{"https://.sts.google.com", validTokenURLPatterns, false},
|
||||||
|
{"https://badsts.googleapis.com", validTokenURLPatterns, false},
|
||||||
|
{"https://sts.asfeasfesef.googleapis.com", validTokenURLPatterns, true},
|
||||||
|
{"https://sts.asfe.asfesef.googleapis.com", validTokenURLPatterns, false},
|
||||||
|
{"https://sts..googleapis.com", validTokenURLPatterns, false},
|
||||||
|
{"https://-sts.googleapis.com", validTokenURLPatterns, false},
|
||||||
|
{"https://us-east-1-sts.googleapis.com", validTokenURLPatterns, true},
|
||||||
|
{"https://us-ea.st-1-sts.googleapis.com", validTokenURLPatterns, false},
|
||||||
|
// Repeat for iamcredentials as well
|
||||||
|
{"https://iamcredentials.googleapis.com", validImpersonateURLPatterns, true},
|
||||||
|
{"https://.iamcredentials.googleapis.com", validImpersonateURLPatterns, false},
|
||||||
|
{"https://badiamcredentials.googleapis.com", validImpersonateURLPatterns, false},
|
||||||
|
{"https://iamcredentials.asfeasfesef.googleapis.com", validImpersonateURLPatterns, true},
|
||||||
|
{"https://iamcredentials.asfe.asfesef.googleapis.com", validImpersonateURLPatterns, false},
|
||||||
|
{"https://iamcredentials..googleapis.com", validImpersonateURLPatterns, false},
|
||||||
|
{"https://-iamcredentials.googleapis.com", validImpersonateURLPatterns, false},
|
||||||
|
{"https://us-east-1-iamcredentials.googleapis.com", validImpersonateURLPatterns, true},
|
||||||
|
{"https://us-ea.st-1-iamcredentials.googleapis.com", validImpersonateURLPatterns, false},
|
||||||
|
}
|
||||||
|
for _, tt := range urlValidityTests {
|
||||||
|
t.Run(" "+tt.input, func(t *testing.T) { // We prepend a space ahead of the test input when outputting for sake of readability.
|
||||||
|
valid, err := validateURL(tt.input, tt.pattern)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("validateURL returned an error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if valid != tt.result {
|
||||||
|
t.Errorf("got %v, want %v", valid, tt.result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,10 @@ package externalaccount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// clientAuthentication represents an OAuth client ID and secret and the mechanism for passing these credentials as stated in rfc6749#2.3.1.
|
// clientAuthentication represents an OAuth client ID and secret and the mechanism for passing these credentials as stated in rfc6749#2.3.1.
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
package externalaccount
|
package externalaccount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var clientID = "rbrgnognrhongo3bi4gb9ghg9g"
|
var clientID = "rbrgnognrhongo3bi4gb9ghg9g"
|
||||||
|
|
|
@ -9,11 +9,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// generateAccesstokenReq is used for service account impersonation
|
// generateAccesstokenReq is used for service account impersonation
|
||||||
|
|
|
@ -6,6 +6,7 @@ package externalaccount
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -76,7 +77,11 @@ func TestImpersonation(t *testing.T) {
|
||||||
defer targetServer.Close()
|
defer targetServer.Close()
|
||||||
|
|
||||||
testImpersonateConfig.TokenURL = targetServer.URL
|
testImpersonateConfig.TokenURL = targetServer.URL
|
||||||
ourTS := testImpersonateConfig.TokenSource(context.Background())
|
ourTS, err := testImpersonateConfig.tokenSource(context.Background(), true)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(testImpersonateConfig.TokenURL)
|
||||||
|
t.Fatalf("Failed to create TokenSource: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
oldNow := now
|
oldNow := now
|
||||||
defer func() { now = oldNow }()
|
defer func() { now = oldNow }()
|
||||||
|
|
|
@ -7,12 +7,13 @@ package externalaccount
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var auth = clientAuthentication{
|
var auth = clientAuthentication{
|
||||||
|
|
|
@ -9,10 +9,11 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type urlCredentialSource struct {
|
type urlCredentialSource struct {
|
||||||
|
|
Loading…
Reference in New Issue