From d6857d1e58c37015553a37b4b9a3454f4db9a464 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 26 Jan 2021 08:45:25 -0800 Subject: [PATCH 1/4] google: support AWS 3rd party credentials --- google/internal/externalaccount/aws.go | 269 +++++++++++- google/internal/externalaccount/aws_test.go | 404 +++++++++++++++++- .../externalaccount/basecredentials.go | 12 + 3 files changed, 649 insertions(+), 36 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index 906d1fe9d..ae96ac364 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -8,38 +8,50 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" + "encoding/json" "errors" "fmt" "io" "io/ioutil" "net/http" + "os" "path" "sort" + "strconv" "strings" "time" ) // RequestSigner is a utility class to sign http requests using a AWS V4 signature. +type awsSecurityCredentials struct { + AccessKeyId string + SecretAccessKey string + SecurityToken string +} + type awsRequestSigner struct { RegionName string - AwsSecurityCredentials map[string]string + AwsSecurityCredentials awsSecurityCredentials } +// getenv aliases os.Getenv for testing +var getenv = os.Getenv + const ( -// AWS Signature Version 4 signing algorithm identifier. + // AWS Signature Version 4 signing algorithm identifier. awsAlgorithm = "AWS4-HMAC-SHA256" -// The termination string for the AWS credential scope value as defined in -// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html + // The termination string for the AWS credential scope value as defined in + // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html awsRequestType = "aws4_request" -// The AWS authorization header name for the security session token if available. + // The AWS authorization header name for the security session token if available. awsSecurityTokenHeader = "x-amz-security-token" -// The AWS authorization header name for the auto-generated date. + // The AWS authorization header name for the auto-generated date. awsDateHeader = "x-amz-date" - awsTimeFormatLong = "20060102T150405Z" + awsTimeFormatLong = "20060102T150405Z" awsTimeFormatShort = "20060102" ) @@ -167,8 +179,8 @@ func (rs *awsRequestSigner) SignRequest(req *http.Request) error { signedRequest.Header.Add("host", requestHost(req)) - if securityToken, ok := rs.AwsSecurityCredentials["security_token"]; ok { - signedRequest.Header.Add(awsSecurityTokenHeader, securityToken) + if rs.AwsSecurityCredentials.SecurityToken != "" { + signedRequest.Header.Add(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SecurityToken) } if signedRequest.Header.Get("date") == "" { @@ -186,15 +198,6 @@ func (rs *awsRequestSigner) SignRequest(req *http.Request) error { } func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) { - secretAccessKey, ok := rs.AwsSecurityCredentials["secret_access_key"] - if !ok { - return "", errors.New("oauth2/google: missing secret_access_key header") - } - accessKeyId, ok := rs.AwsSecurityCredentials["access_key_id"] - if !ok { - return "", errors.New("oauth2/google: missing access_key_id header") - } - canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req) dateStamp := timestamp.Format(awsTimeFormatShort) @@ -203,28 +206,248 @@ func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp serviceName = splitHost[0] } - credentialScope := fmt.Sprintf("%s/%s/%s/%s",dateStamp, rs.RegionName, serviceName, awsRequestType) + credentialScope := fmt.Sprintf("%s/%s/%s/%s", dateStamp, rs.RegionName, serviceName, awsRequestType) requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData) if err != nil { return "", err } requestHash, err := getSha256([]byte(requestString)) - if err != nil{ + if err != nil { return "", err } stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash) - signingKey := []byte("AWS4" + secretAccessKey) + signingKey := []byte("AWS4" + rs.AwsSecurityCredentials.SecretAccessKey) for _, signingInput := range []string{ dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign, } { signingKey, err = getHmacSha256(signingKey, []byte(signingInput)) - if err != nil{ + if err != nil { + return "", err + } + } + + return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyId, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil +} + +type awsCredentialSource struct { + EnvironmentID string + RegionURL string + RegionalCredVerificationURL string + CredVerificationURL string + TargetResource string + requestSigner *awsRequestSigner + region string +} + +type AwsRequestHeader struct { + Key string `json:"key"` + Value string `json:"value"` +} + +type AwsRequest struct { + URL string `json:"url"` + Method string `json:"method"` + Headers []AwsRequestHeader `json:"headers"` +} + +func (cs awsCredentialSource) subjectToken() (string, error) { + if version, _ := strconv.Atoi(cs.EnvironmentID[3:]); version != 1 { + return "", errors.New(fmt.Sprintf("oauth2/google: aws version '%d' is not supported in the current build.", version)) + } + + if cs.requestSigner == nil { + awsSecurityCredentials, err := cs.getSecurityCredentials() + if err != nil { + return "", err + } + + if cs.region, err = cs.getRegion(); err != nil { return "", err } + + cs.requestSigner = &awsRequestSigner{ + RegionName: cs.region, + AwsSecurityCredentials: awsSecurityCredentials, + } + } + + // Generate the signed request to AWS STS GetCallerIdentity API. + // Use the required regional endpoint. Otherwise, the request will fail. + req, err := http.NewRequest("POST", strings.Replace(cs.RegionalCredVerificationURL, "{region}", cs.region, 1), nil) + if err != nil { + return "", err + } + // The full, canonical resource name of the workload identity pool + // provider, with or without the HTTPS prefix. + // Including this header as part of the signature is recommended to + // ensure data integrity. + if cs.TargetResource != "" { + req.Header.Add("x-goog-cloud-target-resource", cs.TargetResource) + } + cs.requestSigner.SignRequest(req) + + /* + The GCP STS endpoint expects the headers to be formatted as: + # [ + # {key: 'x-amz-date', value: '...'}, + # {key: 'Authorization', value: '...'}, + # ... + # ] + # And then serialized as: + # quote(json.dumps({ + # url: '...', + # method: 'POST', + # headers: [{key: 'x-amz-date', value: '...'}, ...] + # })) + */ + + awsSignedReq := AwsRequest{ + URL: req.URL.String(), + Method: req.Method, + } + for headerKey, headerList := range req.Header { + for _, headerValue := range headerList { + awsSignedReq.Headers = append(awsSignedReq.Headers, AwsRequestHeader{ + Key: headerKey, + Value: headerValue, + }) + } + } + sort.Slice(awsSignedReq.Headers, func(i, j int) bool { + headerCompare := strings.Compare(awsSignedReq.Headers[i].Key, awsSignedReq.Headers[j].Key) + if headerCompare == 0 { + return strings.Compare(awsSignedReq.Headers[i].Value, awsSignedReq.Headers[j].Value) < 0 + } + return headerCompare < 0 + }) + + result, err := json.Marshal(awsSignedReq) + if err != nil { + return "", err + } + return string(result), nil +} + +func (cs *awsCredentialSource) getRegion() (string, error) { + if envAwsRegion := getenv("AWS_REGION"); envAwsRegion != "" { + return envAwsRegion, nil + } + + if cs.RegionURL == "" { + return "", errors.New("oauth2/google: Unable to determine AWS region.") + } + + resp, err := http.Get(cs.RegionURL) + if err != nil { + return "", err + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return "", err + } + + if resp.StatusCode != 200 { + return "", errors.New(fmt.Sprintf("oauth2/google: Unable to retrieve AWS region - %s.", string(respBody))) + } + + return string(respBody)[:len(respBody)-1], nil +} + +func (cs *awsCredentialSource) getSecurityCredentials() (securityCredentials awsSecurityCredentials, err error) { + if accessKeyId := getenv("AWS_ACCESS_KEY_ID"); accessKeyId != "" { + if secretAccessKey := getenv("AWS_SECRET_ACCESS_KEY"); secretAccessKey != "" { + return awsSecurityCredentials{ + AccessKeyId: accessKeyId, + SecretAccessKey: secretAccessKey, + SecurityToken: getenv("AWS_SESSION_TOKEN"), + }, nil + } + } + + roleName, err := cs.getMetadataRoleName() + if err != nil { + return awsSecurityCredentials{}, err + } + + credentials, err := cs.getMetadataSecurityCredentials(roleName) + if err != nil { + return awsSecurityCredentials{}, err + } + + accessKeyId, ok := credentials["AccessKeyId"] + if !ok { + return awsSecurityCredentials{}, errors.New("oauth2/google: missing AccessKeyId credential.") + } + + secretAccessKey, ok := credentials["SecretAccessKey"] + if !ok { + return awsSecurityCredentials{}, errors.New("oauth2/google: missing SecretAccessKey credential.") + } + + securityToken, _ := credentials["Token"] + + return awsSecurityCredentials{ + AccessKeyId: accessKeyId, + SecretAccessKey: secretAccessKey, + SecurityToken: securityToken, + }, nil +} + +func (cs *awsCredentialSource) getMetadataSecurityCredentials(roleName string) (map[string]string, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", cs.CredVerificationURL, roleName), nil) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return nil, err + } + + if resp.StatusCode != 200 { + return nil, errors.New(fmt.Sprintf("oauth2/google: Unable to retrieve AWS security credentials - %s.", string(respBody))) + } + + var result map[string]string + err = json.Unmarshal(respBody, &result) + if err != nil { + return nil, err + } + + return result, nil +} + +func (cs *awsCredentialSource) getMetadataRoleName() (string, error) { + if cs.CredVerificationURL == "" { + return "", errors.New("oauth2/google: Unable to determine the AWS metadata server security credentials endpoint.") + } + + resp, err := http.Get(cs.CredVerificationURL) + if err != nil { + return "", err + } + defer resp.Body.Close() + + respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return "", err + } + + if resp.StatusCode != 200 { + return "", errors.New(fmt.Sprintf("oauth2/google: Unable to retrieve AWS role name - %s.", string(respBody))) } - return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, accessKeyId, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil + return string(respBody), nil } diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go index 206c3a1af..18081f249 100644 --- a/google/internal/externalaccount/aws_test.go +++ b/google/internal/externalaccount/aws_test.go @@ -5,7 +5,10 @@ package externalaccount import ( + "encoding/json" + "fmt" "net/http" + "net/http/httptest" "reflect" "strings" "testing" @@ -21,24 +24,33 @@ func setTime(testTime time.Time) func() time.Time { } } +func setEnvironment(env map[string]string) func(string) string { + return func(key string) string { + value, _ := env[key] + return value + } +} + var defaultRequestSigner = &awsRequestSigner{ RegionName: "us-east-1", - AwsSecurityCredentials: map[string]string{ - "access_key_id": "AKIDEXAMPLE", - "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", + AwsSecurityCredentials: awsSecurityCredentials{ + AccessKeyId: "AKIDEXAMPLE", + SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", }, } -const accessKeyId = "ASIARD4OQDT6A77FR3CL" -const secretAccessKey = "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx" -const securityToken = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA==" +const ( + accessKeyId = "ASIARD4OQDT6A77FR3CL" + secretAccessKey = "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx" + securityToken = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA==" +) var requestSignerWithToken = &awsRequestSigner{ RegionName: "us-east-2", - AwsSecurityCredentials: map[string]string{ - "access_key_id": accessKeyId, - "secret_access_key": secretAccessKey, - "security_token": securityToken, + AwsSecurityCredentials: awsSecurityCredentials{ + AccessKeyId: accessKeyId, + SecretAccessKey: secretAccessKey, + SecurityToken: securityToken, }, } @@ -373,9 +385,9 @@ func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { var requestSigner = &awsRequestSigner{ RegionName: "us-east-2", - AwsSecurityCredentials: map[string]string{ - "access_key_id": accessKeyId, - "secret_access_key": secretAccessKey, + AwsSecurityCredentials: awsSecurityCredentials{ + AccessKeyId: accessKeyId, + SecretAccessKey: secretAccessKey, }, } @@ -394,3 +406,369 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { testRequestSigner(t, requestSigner, input, output) } + +type testAwsServer struct { + url string + securityCredentialUrl string + regionUrl string + regionalCredVerificationUrl string + + Credentials map[string]string + + WriteRolename func(http.ResponseWriter) + WriteSecurityCredentials func(http.ResponseWriter) + WriteRegion func(http.ResponseWriter) +} + +func createAwsTestServer(url, regionUrl, regionalCredVerificationUrl, rolename, region string, credentials map[string]string) *testAwsServer { + server := &testAwsServer{ + url: url, + securityCredentialUrl: fmt.Sprintf("%s/%s", url, rolename), + regionUrl: regionUrl, + regionalCredVerificationUrl: regionalCredVerificationUrl, + Credentials: credentials, + WriteRolename: func(w http.ResponseWriter) { + w.Write([]byte(rolename)) + }, + WriteRegion: func(w http.ResponseWriter) { + w.Write([]byte(region)) + }, + } + + server.WriteSecurityCredentials = func(w http.ResponseWriter) { + jsonCredentials, _ := json.Marshal(server.Credentials) + w.Write(jsonCredentials) + } + + return server +} + +func createDefaultAwsTestServer() *testAwsServer { + return createAwsTestServer( + "/latest/meta-data/iam/security-credentials", + "/latest/meta-data/placement/availability-zone", + "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + "gcp-aws-role", + "us-east-2b", + map[string]string{ + "SecretAccessKey": secretAccessKey, + "AccessKeyId": accessKeyId, + "Token": securityToken, + }, + ) +} + +func (server *testAwsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + switch p := r.URL.Path; p { + case server.url: + server.WriteRolename(w) + case server.securityCredentialUrl: + server.WriteSecurityCredentials(w) + case server.regionUrl: + server.WriteRegion(w) + } +} + +func notFound(w http.ResponseWriter) { + w.WriteHeader(404) + w.Write([]byte("Not Found")) +} + +func (server *testAwsServer) getCredentialSource(url string) CredentialSource { + return CredentialSource{ + EnvironmentID: "aws1", + URL: url + server.url, + RegionURL: url + server.regionUrl, + RegionalCredVerificationURL: server.regionalCredVerificationUrl, + } +} + +func getExpectedSubjectToken(url, region, accessKeyId, secretAccessKey, securityToken string) string { + req, _ := http.NewRequest("POST", url, nil) + req.Header.Add("x-goog-cloud-target-resource", testFileConfig.Audience) + signer := &awsRequestSigner{ + RegionName: region, + AwsSecurityCredentials: awsSecurityCredentials{ + AccessKeyId: accessKeyId, + SecretAccessKey: secretAccessKey, + SecurityToken: securityToken, + }, + } + signer.SignRequest(req) + + result := AwsRequest{ + URL: url, + Method: "POST", + Headers: []AwsRequestHeader{ + AwsRequestHeader{ + Key: "Authorization", + Value: req.Header.Get("Authorization"), + }, + AwsRequestHeader{ + Key: "Host", + Value: req.Header.Get("Host"), + }, + AwsRequestHeader{ + Key: "X-Amz-Date", + Value: req.Header.Get("X-Amz-Date"), + }, + }, + } + + if securityToken != "" { + result.Headers = append(result.Headers, AwsRequestHeader{ + Key: "X-Amz-Security-Token", + Value: securityToken, + }) + } + + result.Headers = append(result.Headers, AwsRequestHeader{ + Key: "X-Goog-Cloud-Target-Resource", + Value: testFileConfig.Audience, + }) + + str, _ := json.Marshal(result) + return string(str) +} + +func TestAwsCredential_BasicRequest(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + out, err := tfc.parse(nil).subjectToken() + if err != nil { + t.Fatalf("retrieveSubjectToken() failed: %v", err) + } + + expected := getExpectedSubjectToken( + "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + "us-east-2", + accessKeyId, + secretAccessKey, + securityToken, + ) + + if got, want := out, expected; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_BasicRequestWithEnv(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{ + "AWS_ACCESS_KEY_ID": "AKIDEXAMPLE", + "AWS_SECRET_ACCESS_KEY": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", + "AWS_REGION": "us-west-1", + }) + + out, err := tfc.parse(nil).subjectToken() + if err != nil { + t.Fatalf("retrieveSubjectToken() failed: %v", err) + } + + expected := getExpectedSubjectToken( + "https://sts.us-west-1.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + "us-west-1", + "AKIDEXAMPLE", + "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", + "", + ) + + if got, want := out, expected; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithBadVersion(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + tfc.CredentialSource.EnvironmentID = "aws3" + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: aws version '3' is not supported in the current build."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithNoRegionUrl(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + tfc.CredentialSource.RegionURL = "" + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: Unable to determine AWS region."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithBadRegionUrl(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + server.WriteRegion = notFound + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: Unable to retrieve AWS region - Not Found."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithMissingCredential(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + server.WriteSecurityCredentials = func(w http.ResponseWriter) { + w.Write([]byte("{}")) + } + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: missing AccessKeyId credential."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithIncompleteCredential(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + server.WriteSecurityCredentials = func(w http.ResponseWriter) { + w.Write([]byte("{\"AccessKeyId\":\"FOOBARBAS\"}")) + } + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: missing SecretAccessKey credential."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithNoCredentialUrl(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + tfc.CredentialSource.URL = "" + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: Unable to determine the AWS metadata server security credentials endpoint."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithBadCredentialUrl(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + server.WriteRolename = notFound + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: Unable to retrieve AWS role name - Not Found."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + +func TestAwsCredential_RequestWithBadFinalCredentialUrl(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + server.WriteSecurityCredentials = notFound + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + _, err := tfc.parse(nil).subjectToken() + if err == nil { + t.Fatalf("retrieveSubjectToken() should have failed") + } + + if got, want := err.Error(), "oauth2/google: Unable to retrieve AWS security credentials - Not Found."; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} diff --git a/google/internal/externalaccount/basecredentials.go b/google/internal/externalaccount/basecredentials.go index dff08811a..63d52f720 100644 --- a/google/internal/externalaccount/basecredentials.go +++ b/google/internal/externalaccount/basecredentials.go @@ -9,6 +9,7 @@ import ( "fmt" "golang.org/x/oauth2" "net/http" + "strconv" "time" ) @@ -67,6 +68,17 @@ type CredentialSource struct { // parse determines the type of CredentialSource needed func (c *Config) parse(ctx context.Context) baseCredentialSource { + if len(c.CredentialSource.EnvironmentID) > 3 && c.CredentialSource.EnvironmentID[:3] == "aws" { + if _, err := strconv.Atoi(c.CredentialSource.EnvironmentID[3:]); err == nil { + return awsCredentialSource{ + EnvironmentID: c.CredentialSource.EnvironmentID, + RegionURL: c.CredentialSource.RegionURL, + RegionalCredVerificationURL: c.CredentialSource.RegionalCredVerificationURL, + CredVerificationURL: c.CredentialSource.URL, + TargetResource: c.Audience, + } + } + } if c.CredentialSource.File != "" { return fileCredentialSource{File: c.CredentialSource.File, Format: c.CredentialSource.Format} } else if c.CredentialSource.URL != "" { From ba9ae8ce1b3711988dbf77cc42712813a4678a27 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Fri, 29 Jan 2021 15:34:54 -0800 Subject: [PATCH 2/4] changes requested by codyoss@ --- google/internal/externalaccount/aws.go | 115 +++++++------- google/internal/externalaccount/aws_test.go | 144 ++++++++++++++---- .../externalaccount/basecredentials.go | 23 +-- .../externalaccount/filecredsource_test.go | 7 +- .../externalaccount/urlcredsource_test.go | 21 ++- 5 files changed, 212 insertions(+), 98 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index ae96ac364..0f8c31c15 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -5,30 +5,31 @@ package externalaccount import ( + "context" "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" + "golang.org/x/oauth2" "io" "io/ioutil" "net/http" "os" "path" "sort" - "strconv" "strings" "time" ) -// RequestSigner is a utility class to sign http requests using a AWS V4 signature. type awsSecurityCredentials struct { - AccessKeyId string - SecretAccessKey string - SecurityToken string + AccessKeyID string `json:"AccessKeyID"` + SecretAccessKey string `json:"SecretAccessKey"` + SecurityToken string `json:"Token"` } +// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature. type awsRequestSigner struct { RegionName string AwsSecurityCredentials awsSecurityCredentials @@ -229,7 +230,7 @@ func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp } } - return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyId, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil + return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil } type awsCredentialSource struct { @@ -240,24 +241,29 @@ type awsCredentialSource struct { TargetResource string requestSigner *awsRequestSigner region string + ctx context.Context + client *http.Client } -type AwsRequestHeader struct { +type awsRequestHeader struct { Key string `json:"key"` Value string `json:"value"` } -type AwsRequest struct { +type awsRequest struct { URL string `json:"url"` Method string `json:"method"` - Headers []AwsRequestHeader `json:"headers"` + Headers []awsRequestHeader `json:"headers"` } -func (cs awsCredentialSource) subjectToken() (string, error) { - if version, _ := strconv.Atoi(cs.EnvironmentID[3:]); version != 1 { - return "", errors.New(fmt.Sprintf("oauth2/google: aws version '%d' is not supported in the current build.", version)) +func (cs awsCredentialSource) request(req *http.Request) (*http.Response, error) { + if cs.client == nil { + cs.client = oauth2.NewClient(cs.ctx, nil) } + return cs.client.Do(req.WithContext(cs.ctx)) +} +func (cs awsCredentialSource) subjectToken() (string, error) { if cs.requestSigner == nil { awsSecurityCredentials, err := cs.getSecurityCredentials() if err != nil { @@ -304,13 +310,13 @@ func (cs awsCredentialSource) subjectToken() (string, error) { # })) */ - awsSignedReq := AwsRequest{ + awsSignedReq := awsRequest{ URL: req.URL.String(), - Method: req.Method, + Method: "POST", } for headerKey, headerList := range req.Header { for _, headerValue := range headerList { - awsSignedReq.Headers = append(awsSignedReq.Headers, AwsRequestHeader{ + awsSignedReq.Headers = append(awsSignedReq.Headers, awsRequestHeader{ Key: headerKey, Value: headerValue, }) @@ -337,10 +343,15 @@ func (cs *awsCredentialSource) getRegion() (string, error) { } if cs.RegionURL == "" { - return "", errors.New("oauth2/google: Unable to determine AWS region.") + return "", errors.New("oauth2/google: unable to determine AWS region") } - resp, err := http.Get(cs.RegionURL) + req, err := http.NewRequest("GET", cs.RegionURL, nil) + if err != nil { + return "", err + } + + resp, err := cs.request(req) if err != nil { return "", err } @@ -352,17 +363,23 @@ func (cs *awsCredentialSource) getRegion() (string, error) { } if resp.StatusCode != 200 { - return "", errors.New(fmt.Sprintf("oauth2/google: Unable to retrieve AWS region - %s.", string(respBody))) + return "", fmt.Errorf("oauth2/google: unable to retrieve AWS region - %s", string(respBody)) } - return string(respBody)[:len(respBody)-1], nil + // This endpoint will return the region in format: us-east-2b. + // Only the us-east-2 part should be used. + respBodyEnd := 0 + if len(respBody) > 1 { + respBodyEnd = len(respBody) - 1 + } + return string(respBody[:respBodyEnd]), nil } -func (cs *awsCredentialSource) getSecurityCredentials() (securityCredentials awsSecurityCredentials, err error) { - if accessKeyId := getenv("AWS_ACCESS_KEY_ID"); accessKeyId != "" { +func (cs *awsCredentialSource) getSecurityCredentials() (result awsSecurityCredentials, err error) { + if accessKeyID := getenv("AWS_ACCESS_KEY_ID"); accessKeyID != "" { if secretAccessKey := getenv("AWS_SECRET_ACCESS_KEY"); secretAccessKey != "" { return awsSecurityCredentials{ - AccessKeyId: accessKeyId, + AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, SecurityToken: getenv("AWS_SESSION_TOKEN"), }, nil @@ -371,70 +388,64 @@ func (cs *awsCredentialSource) getSecurityCredentials() (securityCredentials aws roleName, err := cs.getMetadataRoleName() if err != nil { - return awsSecurityCredentials{}, err + return } credentials, err := cs.getMetadataSecurityCredentials(roleName) if err != nil { - return awsSecurityCredentials{}, err + return } - accessKeyId, ok := credentials["AccessKeyId"] - if !ok { - return awsSecurityCredentials{}, errors.New("oauth2/google: missing AccessKeyId credential.") + if credentials.AccessKeyID == "" { + return result, errors.New("oauth2/google: missing AccessKeyId credential") } - secretAccessKey, ok := credentials["SecretAccessKey"] - if !ok { - return awsSecurityCredentials{}, errors.New("oauth2/google: missing SecretAccessKey credential.") + if credentials.SecretAccessKey == "" { + return result, errors.New("oauth2/google: missing SecretAccessKey credential") } - securityToken, _ := credentials["Token"] - - return awsSecurityCredentials{ - AccessKeyId: accessKeyId, - SecretAccessKey: secretAccessKey, - SecurityToken: securityToken, - }, nil + return credentials, nil } -func (cs *awsCredentialSource) getMetadataSecurityCredentials(roleName string) (map[string]string, error) { +func (cs *awsCredentialSource) getMetadataSecurityCredentials(roleName string) (awsSecurityCredentials, error) { + var result awsSecurityCredentials + req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", cs.CredVerificationURL, roleName), nil) if err != nil { - return nil, err + return result, err } req.Header.Add("Content-Type", "application/json") - resp, err := http.DefaultClient.Do(req) + resp, err := cs.request(req) if err != nil { - return nil, err + return result, err } defer resp.Body.Close() respBody, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { - return nil, err + return result, err } if resp.StatusCode != 200 { - return nil, errors.New(fmt.Sprintf("oauth2/google: Unable to retrieve AWS security credentials - %s.", string(respBody))) + return result, fmt.Errorf("oauth2/google: unable to retrieve AWS security credentials - %s", string(respBody)) } - var result map[string]string err = json.Unmarshal(respBody, &result) - if err != nil { - return nil, err - } - - return result, nil + return result, err } func (cs *awsCredentialSource) getMetadataRoleName() (string, error) { if cs.CredVerificationURL == "" { - return "", errors.New("oauth2/google: Unable to determine the AWS metadata server security credentials endpoint.") + return "", errors.New("oauth2/google: unable to determine the AWS metadata server security credentials endpoint") + } + + req, err := http.NewRequest("GET", cs.CredVerificationURL, nil) + if err != nil { + return "", err } - resp, err := http.Get(cs.CredVerificationURL) + resp, err := cs.request(req) if err != nil { return "", err } @@ -446,7 +457,7 @@ func (cs *awsCredentialSource) getMetadataRoleName() (string, error) { } if resp.StatusCode != 200 { - return "", errors.New(fmt.Sprintf("oauth2/google: Unable to retrieve AWS role name - %s.", string(respBody))) + return "", fmt.Errorf("oauth2/google: unable to retrieve AWS role name - %s", string(respBody)) } return string(respBody), nil diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go index 18081f249..08f366d93 100644 --- a/google/internal/externalaccount/aws_test.go +++ b/google/internal/externalaccount/aws_test.go @@ -5,6 +5,7 @@ package externalaccount import ( + "context" "encoding/json" "fmt" "net/http" @@ -34,7 +35,7 @@ func setEnvironment(env map[string]string) func(string) string { var defaultRequestSigner = &awsRequestSigner{ RegionName: "us-east-1", AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyId: "AKIDEXAMPLE", + AccessKeyID: "AKIDEXAMPLE", SecretAccessKey: "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", }, } @@ -48,7 +49,7 @@ const ( var requestSignerWithToken = &awsRequestSigner{ RegionName: "us-east-2", AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyId: accessKeyId, + AccessKeyID: accessKeyId, SecretAccessKey: secretAccessKey, SecurityToken: securityToken, }, @@ -386,7 +387,7 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { var requestSigner = &awsRequestSigner{ RegionName: "us-east-2", AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyId: accessKeyId, + AccessKeyID: accessKeyId, SecretAccessKey: secretAccessKey, }, } @@ -489,26 +490,24 @@ func getExpectedSubjectToken(url, region, accessKeyId, secretAccessKey, security signer := &awsRequestSigner{ RegionName: region, AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyId: accessKeyId, + AccessKeyID: accessKeyId, SecretAccessKey: secretAccessKey, SecurityToken: securityToken, }, } signer.SignRequest(req) - result := AwsRequest{ + result := awsRequest{ URL: url, Method: "POST", - Headers: []AwsRequestHeader{ - AwsRequestHeader{ + Headers: []awsRequestHeader{ + { Key: "Authorization", Value: req.Header.Get("Authorization"), - }, - AwsRequestHeader{ + }, { Key: "Host", Value: req.Header.Get("Host"), - }, - AwsRequestHeader{ + }, { Key: "X-Amz-Date", Value: req.Header.Get("X-Amz-Date"), }, @@ -516,13 +515,13 @@ func getExpectedSubjectToken(url, region, accessKeyId, secretAccessKey, security } if securityToken != "" { - result.Headers = append(result.Headers, AwsRequestHeader{ + result.Headers = append(result.Headers, awsRequestHeader{ Key: "X-Amz-Security-Token", Value: securityToken, }) } - result.Headers = append(result.Headers, AwsRequestHeader{ + result.Headers = append(result.Headers, awsRequestHeader{ Key: "X-Goog-Cloud-Target-Resource", Value: testFileConfig.Audience, }) @@ -542,7 +541,12 @@ func TestAwsCredential_BasicRequest(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - out, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() if err != nil { t.Fatalf("retrieveSubjectToken() failed: %v", err) } @@ -560,6 +564,41 @@ func TestAwsCredential_BasicRequest(t *testing.T) { } } +func TestAwsCredential_BasicRequestWithoutSecurityToken(t *testing.T) { + server := createDefaultAwsTestServer() + ts := httptest.NewServer(server) + delete(server.Credentials, "Token") + + tfc := testFileConfig + tfc.CredentialSource = server.getCredentialSource(ts.URL) + + oldGetenv := getenv + defer func() { getenv = oldGetenv }() + getenv = setEnvironment(map[string]string{}) + + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() + if err != nil { + t.Fatalf("retrieveSubjectToken() failed: %v", err) + } + + expected := getExpectedSubjectToken( + "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", + "us-east-2", + accessKeyId, + secretAccessKey, + "", + ) + + if got, want := out, expected; !reflect.DeepEqual(got, want) { + t.Errorf("subjectToken = %q, want %q", got, want) + } +} + func TestAwsCredential_BasicRequestWithEnv(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) @@ -575,7 +614,12 @@ func TestAwsCredential_BasicRequestWithEnv(t *testing.T) { "AWS_REGION": "us-west-1", }) - out, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() if err != nil { t.Fatalf("retrieveSubjectToken() failed: %v", err) } @@ -605,12 +649,11 @@ func TestAwsCredential_RequestWithBadVersion(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + _, err := tfc.parse(context.Background()) if err == nil { - t.Fatalf("retrieveSubjectToken() should have failed") + t.Fatalf("parse() should have failed") } - - if got, want := err.Error(), "oauth2/google: aws version '3' is not supported in the current build."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: aws version '3' is not supported in the current build"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -627,12 +670,17 @@ func TestAwsCredential_RequestWithNoRegionUrl(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: Unable to determine AWS region."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: unable to determine AWS region"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -649,12 +697,17 @@ func TestAwsCredential_RequestWithBadRegionUrl(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: Unable to retrieve AWS region - Not Found."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: unable to retrieve AWS region - Not Found"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -673,12 +726,17 @@ func TestAwsCredential_RequestWithMissingCredential(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: missing AccessKeyId credential."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: missing AccessKeyId credential"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -697,12 +755,17 @@ func TestAwsCredential_RequestWithIncompleteCredential(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: missing SecretAccessKey credential."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: missing SecretAccessKey credential"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -719,12 +782,17 @@ func TestAwsCredential_RequestWithNoCredentialUrl(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: Unable to determine the AWS metadata server security credentials endpoint."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: unable to determine the AWS metadata server security credentials endpoint"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -741,12 +809,17 @@ func TestAwsCredential_RequestWithBadCredentialUrl(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: Unable to retrieve AWS role name - Not Found."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: unable to retrieve AWS role name - Not Found"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } @@ -763,12 +836,17 @@ func TestAwsCredential_RequestWithBadFinalCredentialUrl(t *testing.T) { defer func() { getenv = oldGetenv }() getenv = setEnvironment(map[string]string{}) - _, err := tfc.parse(nil).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + _, err = base.subjectToken() if err == nil { t.Fatalf("retrieveSubjectToken() should have failed") } - if got, want := err.Error(), "oauth2/google: Unable to retrieve AWS security credentials - Not Found."; !reflect.DeepEqual(got, want) { + if got, want := err.Error(), "oauth2/google: unable to retrieve AWS security credentials - Not Found"; !reflect.DeepEqual(got, want) { t.Errorf("subjectToken = %q, want %q", got, want) } } diff --git a/google/internal/externalaccount/basecredentials.go b/google/internal/externalaccount/basecredentials.go index 63d52f720..c343a22f6 100644 --- a/google/internal/externalaccount/basecredentials.go +++ b/google/internal/externalaccount/basecredentials.go @@ -67,24 +67,28 @@ type CredentialSource struct { } // parse determines the type of CredentialSource needed -func (c *Config) parse(ctx context.Context) baseCredentialSource { +func (c *Config) parse(ctx context.Context) (baseCredentialSource, error) { if len(c.CredentialSource.EnvironmentID) > 3 && c.CredentialSource.EnvironmentID[:3] == "aws" { - if _, err := strconv.Atoi(c.CredentialSource.EnvironmentID[3:]); err == nil { + if awsVersion, err := strconv.Atoi(c.CredentialSource.EnvironmentID[3:]); err == nil { + if awsVersion != 1 { + return nil, fmt.Errorf("oauth2/google: aws version '%d' is not supported in the current build", awsVersion) + } return awsCredentialSource{ EnvironmentID: c.CredentialSource.EnvironmentID, RegionURL: c.CredentialSource.RegionURL, RegionalCredVerificationURL: c.CredentialSource.RegionalCredVerificationURL, CredVerificationURL: c.CredentialSource.URL, TargetResource: c.Audience, - } + ctx: ctx, + }, nil } } if c.CredentialSource.File != "" { - return fileCredentialSource{File: c.CredentialSource.File, Format: c.CredentialSource.Format} + return fileCredentialSource{File: c.CredentialSource.File, Format: c.CredentialSource.Format}, nil } else if c.CredentialSource.URL != "" { - return urlCredentialSource{URL: c.CredentialSource.URL, Format: c.CredentialSource.Format, ctx: ctx} + return urlCredentialSource{URL: c.CredentialSource.URL, Format: c.CredentialSource.Format, ctx: ctx}, nil } - return nil + return nil, fmt.Errorf("oauth2/google: unable to parse credential source") } type baseCredentialSource interface { @@ -101,11 +105,12 @@ type tokenSource struct { func (ts tokenSource) Token() (*oauth2.Token, error) { conf := ts.conf - credSource := conf.parse(ts.ctx) - if credSource == nil { - return nil, fmt.Errorf("oauth2/google: unable to parse credential source") + credSource, err := conf.parse(ts.ctx) + if err != nil { + return nil, err } subjectToken, err := credSource.subjectToken() + if err != nil { return nil, err } diff --git a/google/internal/externalaccount/filecredsource_test.go b/google/internal/externalaccount/filecredsource_test.go index 56dd71e9f..ebd2bb7f7 100644 --- a/google/internal/externalaccount/filecredsource_test.go +++ b/google/internal/externalaccount/filecredsource_test.go @@ -56,7 +56,12 @@ func TestRetrieveFileSubjectToken(t *testing.T) { tfc.CredentialSource = test.cs t.Run(test.name, func(t *testing.T) { - out, err := tfc.parse(context.Background()).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() if err != nil { t.Errorf("Method subjectToken() errored.") } else if test.want != out { diff --git a/google/internal/externalaccount/urlcredsource_test.go b/google/internal/externalaccount/urlcredsource_test.go index 592610fb9..1b78e6802 100644 --- a/google/internal/externalaccount/urlcredsource_test.go +++ b/google/internal/externalaccount/urlcredsource_test.go @@ -28,7 +28,12 @@ func TestRetrieveURLSubjectToken_Text(t *testing.T) { tfc := testFileConfig tfc.CredentialSource = cs - out, err := tfc.parse(context.Background()).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() if err != nil { t.Fatalf("retrieveSubjectToken() failed: %v", err) } @@ -51,7 +56,12 @@ func TestRetrieveURLSubjectToken_Untyped(t *testing.T) { tfc := testFileConfig tfc.CredentialSource = cs - out, err := tfc.parse(context.Background()).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() if err != nil { t.Fatalf("Failed to retrieve URL subject token: %v", err) } @@ -82,7 +92,12 @@ func TestRetrieveURLSubjectToken_JSON(t *testing.T) { tfc := testFileConfig tfc.CredentialSource = cs - out, err := tfc.parse(context.Background()).subjectToken() + base, err := tfc.parse(context.Background()) + if err != nil { + t.Fatalf("parse() failed %v", err) + } + + out, err := base.subjectToken() if err != nil { t.Fatalf("%v", err) } From 471b94b563897d70230e977134ce35ab9f9e4a1c Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Mon, 1 Feb 2021 08:22:37 -0800 Subject: [PATCH 3/4] changes request by codyoss@ --- google/internal/externalaccount/aws.go | 8 +-- google/internal/externalaccount/aws_test.go | 58 ++++++++++----------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index 0f8c31c15..3725a0fcb 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -256,7 +256,7 @@ type awsRequest struct { Headers []awsRequestHeader `json:"headers"` } -func (cs awsCredentialSource) request(req *http.Request) (*http.Response, error) { +func (cs awsCredentialSource) doRequest(req *http.Request) (*http.Response, error) { if cs.client == nil { cs.client = oauth2.NewClient(cs.ctx, nil) } @@ -351,7 +351,7 @@ func (cs *awsCredentialSource) getRegion() (string, error) { return "", err } - resp, err := cs.request(req) + resp, err := cs.doRequest(req) if err != nil { return "", err } @@ -416,7 +416,7 @@ func (cs *awsCredentialSource) getMetadataSecurityCredentials(roleName string) ( } req.Header.Add("Content-Type", "application/json") - resp, err := cs.request(req) + resp, err := cs.doRequest(req) if err != nil { return result, err } @@ -445,7 +445,7 @@ func (cs *awsCredentialSource) getMetadataRoleName() (string, error) { return "", err } - resp, err := cs.request(req) + resp, err := cs.doRequest(req) if err != nil { return "", err } diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go index 08f366d93..1a83a7b3b 100644 --- a/google/internal/externalaccount/aws_test.go +++ b/google/internal/externalaccount/aws_test.go @@ -41,7 +41,7 @@ var defaultRequestSigner = &awsRequestSigner{ } const ( - accessKeyId = "ASIARD4OQDT6A77FR3CL" + accessKeyID = "ASIARD4OQDT6A77FR3CL" secretAccessKey = "Y8AfSaucF37G4PpvfguKZ3/l7Id4uocLXxX0+VTx" securityToken = "IQoJb3JpZ2luX2VjEIz//////////wEaCXVzLWVhc3QtMiJGMEQCIH7MHX/Oy/OB8OlLQa9GrqU1B914+iMikqWQW7vPCKlgAiA/Lsv8Jcafn14owfxXn95FURZNKaaphj0ykpmS+Ki+CSq0AwhlEAAaDDA3NzA3MTM5MTk5NiIMx9sAeP1ovlMTMKLjKpEDwuJQg41/QUKx0laTZYjPlQvjwSqS3OB9P1KAXPWSLkliVMMqaHqelvMF/WO/glv3KwuTfQsavRNs3v5pcSEm4SPO3l7mCs7KrQUHwGP0neZhIKxEXy+Ls//1C/Bqt53NL+LSbaGv6RPHaX82laz2qElphg95aVLdYgIFY6JWV5fzyjgnhz0DQmy62/Vi8pNcM2/VnxeCQ8CC8dRDSt52ry2v+nc77vstuI9xV5k8mPtnaPoJDRANh0bjwY5Sdwkbp+mGRUJBAQRlNgHUJusefXQgVKBCiyJY4w3Csd8Bgj9IyDV+Azuy1jQqfFZWgP68LSz5bURyIjlWDQunO82stZ0BgplKKAa/KJHBPCp8Qi6i99uy7qh76FQAqgVTsnDuU6fGpHDcsDSGoCls2HgZjZFPeOj8mmRhFk1Xqvkbjuz8V1cJk54d3gIJvQt8gD2D6yJQZecnuGWd5K2e2HohvCc8Fc9kBl1300nUJPV+k4tr/A5R/0QfEKOZL1/k5lf1g9CREnrM8LVkGxCgdYMxLQow1uTL+QU67AHRRSp5PhhGX4Rek+01vdYSnJCMaPhSEgcLqDlQkhk6MPsyT91QMXcWmyO+cAZwUPwnRamFepuP4K8k2KVXs/LIJHLELwAZ0ekyaS7CptgOqS7uaSTFG3U+vzFZLEnGvWQ7y9IPNQZ+Dffgh4p3vF4J68y9049sI6Sr5d5wbKkcbm8hdCDHZcv4lnqohquPirLiFQ3q7B17V9krMPu3mz1cg4Ekgcrn/E09NTsxAqD8NcZ7C7ECom9r+X3zkDOxaajW6hu3Az8hGlyylDaMiFfRbBJpTIlxp7jfa7CxikNgNtEKLH9iCzvuSg2vhA==" ) @@ -49,7 +49,7 @@ const ( var requestSignerWithToken = &awsRequestSigner{ RegionName: "us-east-2", AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyID: accessKeyId, + AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, SecurityToken: securityToken, }, @@ -330,7 +330,7 @@ func TestAwsV4Signature_GetRequestWithSecurityToken(t *testing.T) { output, _ := http.NewRequest("GET", "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15", nil) output.Header = http.Header{ "Host": []string{"ec2.us-east-2.amazonaws.com"}, - "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyId + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=631ea80cddfaa545fdadb120dc92c9f18166e38a5c47b50fab9fce476e022855"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyID + "/20200811/us-east-2/ec2/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=631ea80cddfaa545fdadb120dc92c9f18166e38a5c47b50fab9fce476e022855"}, "X-Amz-Date": []string{"20200811T065522Z"}, "X-Amz-Security-Token": []string{securityToken}, } @@ -347,7 +347,7 @@ func TestAwsV4Signature_PostRequestWithSecurityToken(t *testing.T) { output, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) output.Header = http.Header{ - "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyId + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=73452984e4a880ffdc5c392355733ec3f5ba310d5e0609a89244440cadfe7a7a"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyID + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=73452984e4a880ffdc5c392355733ec3f5ba310d5e0609a89244440cadfe7a7a"}, "Host": []string{"sts.us-east-2.amazonaws.com"}, "X-Amz-Date": []string{"20200811T065522Z"}, "X-Amz-Security-Token": []string{securityToken}, @@ -368,7 +368,7 @@ func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test output, _ := http.NewRequest("POST", "https://dynamodb.us-east-2.amazonaws.com/", strings.NewReader(requestParams)) output.Header = http.Header{ - "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyId + "/20200811/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=fdaa5b9cc9c86b80fe61eaf504141c0b3523780349120f2bd8145448456e0385"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyID + "/20200811/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-security-token;x-amz-target, Signature=fdaa5b9cc9c86b80fe61eaf504141c0b3523780349120f2bd8145448456e0385"}, "Host": []string{"dynamodb.us-east-2.amazonaws.com"}, "X-Amz-Date": []string{"20200811T065522Z"}, "Content-Type": []string{"application/x-amz-json-1.0"}, @@ -387,7 +387,7 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { var requestSigner = &awsRequestSigner{ RegionName: "us-east-2", AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyID: accessKeyId, + AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, }, } @@ -396,7 +396,7 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { output, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) output.Header = http.Header{ - "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyId + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=d095ba304919cd0d5570ba8a3787884ee78b860f268ed040ba23831d55536d56"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + accessKeyID + "/20200811/us-east-2/sts/aws4_request, SignedHeaders=host;x-amz-date, Signature=d095ba304919cd0d5570ba8a3787884ee78b860f268ed040ba23831d55536d56"}, "Host": []string{"sts.us-east-2.amazonaws.com"}, "X-Amz-Date": []string{"20200811T065522Z"}, } @@ -410,9 +410,9 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { type testAwsServer struct { url string - securityCredentialUrl string - regionUrl string - regionalCredVerificationUrl string + securityCredentialURL string + regionURL string + regionalCredVerificationURL string Credentials map[string]string @@ -421,12 +421,12 @@ type testAwsServer struct { WriteRegion func(http.ResponseWriter) } -func createAwsTestServer(url, regionUrl, regionalCredVerificationUrl, rolename, region string, credentials map[string]string) *testAwsServer { +func createAwsTestServer(url, regionURL, regionalCredVerificationURL, rolename, region string, credentials map[string]string) *testAwsServer { server := &testAwsServer{ url: url, - securityCredentialUrl: fmt.Sprintf("%s/%s", url, rolename), - regionUrl: regionUrl, - regionalCredVerificationUrl: regionalCredVerificationUrl, + securityCredentialURL: fmt.Sprintf("%s/%s", url, rolename), + regionURL: regionURL, + regionalCredVerificationURL: regionalCredVerificationURL, Credentials: credentials, WriteRolename: func(w http.ResponseWriter) { w.Write([]byte(rolename)) @@ -453,7 +453,7 @@ func createDefaultAwsTestServer() *testAwsServer { "us-east-2b", map[string]string{ "SecretAccessKey": secretAccessKey, - "AccessKeyId": accessKeyId, + "AccessKeyId": accessKeyID, "Token": securityToken, }, ) @@ -463,9 +463,9 @@ func (server *testAwsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch p := r.URL.Path; p { case server.url: server.WriteRolename(w) - case server.securityCredentialUrl: + case server.securityCredentialURL: server.WriteSecurityCredentials(w) - case server.regionUrl: + case server.regionURL: server.WriteRegion(w) } } @@ -479,18 +479,18 @@ func (server *testAwsServer) getCredentialSource(url string) CredentialSource { return CredentialSource{ EnvironmentID: "aws1", URL: url + server.url, - RegionURL: url + server.regionUrl, - RegionalCredVerificationURL: server.regionalCredVerificationUrl, + RegionURL: url + server.regionURL, + RegionalCredVerificationURL: server.regionalCredVerificationURL, } } -func getExpectedSubjectToken(url, region, accessKeyId, secretAccessKey, securityToken string) string { +func getExpectedSubjectToken(url, region, accessKeyID, secretAccessKey, securityToken string) string { req, _ := http.NewRequest("POST", url, nil) req.Header.Add("x-goog-cloud-target-resource", testFileConfig.Audience) signer := &awsRequestSigner{ RegionName: region, AwsSecurityCredentials: awsSecurityCredentials{ - AccessKeyID: accessKeyId, + AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, SecurityToken: securityToken, }, @@ -554,7 +554,7 @@ func TestAwsCredential_BasicRequest(t *testing.T) { expected := getExpectedSubjectToken( "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", "us-east-2", - accessKeyId, + accessKeyID, secretAccessKey, securityToken, ) @@ -589,7 +589,7 @@ func TestAwsCredential_BasicRequestWithoutSecurityToken(t *testing.T) { expected := getExpectedSubjectToken( "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", "us-east-2", - accessKeyId, + accessKeyID, secretAccessKey, "", ) @@ -658,7 +658,7 @@ func TestAwsCredential_RequestWithBadVersion(t *testing.T) { } } -func TestAwsCredential_RequestWithNoRegionUrl(t *testing.T) { +func TestAwsCredential_RequestWithNoRegionURL(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) @@ -685,7 +685,7 @@ func TestAwsCredential_RequestWithNoRegionUrl(t *testing.T) { } } -func TestAwsCredential_RequestWithBadRegionUrl(t *testing.T) { +func TestAwsCredential_RequestWithBadRegionURL(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) server.WriteRegion = notFound @@ -745,7 +745,7 @@ func TestAwsCredential_RequestWithIncompleteCredential(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) server.WriteSecurityCredentials = func(w http.ResponseWriter) { - w.Write([]byte("{\"AccessKeyId\":\"FOOBARBAS\"}")) + w.Write([]byte(`{"AccessKeyId":"FOOBARBAS"}`)) } tfc := testFileConfig @@ -770,7 +770,7 @@ func TestAwsCredential_RequestWithIncompleteCredential(t *testing.T) { } } -func TestAwsCredential_RequestWithNoCredentialUrl(t *testing.T) { +func TestAwsCredential_RequestWithNoCredentialURL(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) @@ -797,7 +797,7 @@ func TestAwsCredential_RequestWithNoCredentialUrl(t *testing.T) { } } -func TestAwsCredential_RequestWithBadCredentialUrl(t *testing.T) { +func TestAwsCredential_RequestWithBadCredentialURL(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) server.WriteRolename = notFound @@ -824,7 +824,7 @@ func TestAwsCredential_RequestWithBadCredentialUrl(t *testing.T) { } } -func TestAwsCredential_RequestWithBadFinalCredentialUrl(t *testing.T) { +func TestAwsCredential_RequestWithBadFinalCredentialURL(t *testing.T) { server := createDefaultAwsTestServer() ts := httptest.NewServer(server) server.WriteSecurityCredentials = notFound From 648f0b3d45d94760bb29e6bfe4680351d8e0364d Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Mon, 1 Feb 2021 08:24:39 -0800 Subject: [PATCH 4/4] missed a comment --- google/internal/externalaccount/basecredentials.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google/internal/externalaccount/basecredentials.go b/google/internal/externalaccount/basecredentials.go index c343a22f6..b5331e86b 100644 --- a/google/internal/externalaccount/basecredentials.go +++ b/google/internal/externalaccount/basecredentials.go @@ -82,8 +82,7 @@ func (c *Config) parse(ctx context.Context) (baseCredentialSource, error) { ctx: ctx, }, nil } - } - if c.CredentialSource.File != "" { + } else if c.CredentialSource.File != "" { return fileCredentialSource{File: c.CredentialSource.File, Format: c.CredentialSource.Format}, nil } else if c.CredentialSource.URL != "" { return urlCredentialSource{URL: c.CredentialSource.URL, Format: c.CredentialSource.Format, ctx: ctx}, nil