From 09595ed9900c029f56d14ecd221e8ddb19100602 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 19 Jan 2021 09:26:12 -0800 Subject: [PATCH 1/6] Create AWS V4 Signing Utility --- google/internal/externalaccount/aws.go | 178 +++++++++++ google/internal/externalaccount/aws_test.go | 313 ++++++++++++++++++++ 2 files changed, 491 insertions(+) create mode 100644 google/internal/externalaccount/aws.go create mode 100644 google/internal/externalaccount/aws_test.go diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go new file mode 100644 index 000000000..2357b2d80 --- /dev/null +++ b/google/internal/externalaccount/aws.go @@ -0,0 +1,178 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package externalaccount + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "io/ioutil" + "net/http" + "path" + "sort" + "strings" + "time" +) + +// A utility class to sign http requests using a AWS V4 signature +type RequestSigner struct { + RegionName string + AwsSecurityCredentials map[string]string + debugTimestamp time.Time +} + +func NewRequestSigner(regionName string, awsSecurityCredentials map[string]string) *RequestSigner { + return &RequestSigner{ + RegionName: regionName, + AwsSecurityCredentials: awsSecurityCredentials, + } +} + +// AWS Signature Version 4 signing algorithm identifier. +const 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 +const awsRequestType = "aws4_request" + +// The AWS authorization header name for the security session token if available. +const awsSecurityTokenHeader = "x-amz-security-token" + +// The AWS authorization header name for the auto-generated date. +const awsDateHeader = "x-amz-date" + +const awsTimeFormatLong = "20060102T150405Z" +const awsTimeFormatShort = "20060102" + +func getSha256(input []byte) string { + hash := sha256.New() + hash.Write(input) + return hex.EncodeToString(hash.Sum(nil)) +} + +func getHmacSha256(key, input []byte) []byte { + hash := hmac.New(sha256.New, key) + hash.Write(input) + return hash.Sum(nil) +} + +func canonicalPath(req *http.Request) string { + result := req.URL.EscapedPath() + if result == "" { + return "/" + } + return path.Clean(result) +} + +func canonicalQuery(req *http.Request) string { + queryValues := req.URL.Query() + for queryKey := range queryValues { + sort.Strings(queryValues[queryKey]) + } + return queryValues.Encode() +} + +func canonicalHeaders(req *http.Request) (string, string) { + // Header keys need to be sorted alphabetically. + var headers []string + lowerCaseHeaders := make(http.Header) + for k, v := range req.Header { + k := strings.ToLower(k) + if _, ok := lowerCaseHeaders[k]; ok { + // include additional values + lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...) + } else { + headers = append(headers, k) + lowerCaseHeaders[k] = v + } + } + sort.Strings(headers) + + var fullHeaders []string + for _, header := range headers { + headerValue := strings.Join(lowerCaseHeaders[header], ",") + fullHeaders = append(fullHeaders, header+":"+headerValue+"\n") + } + + return strings.Join(headers, ";"), strings.Join(fullHeaders, "") +} + +func requestDataHash(req *http.Request) string { + requestData := []byte{} + if req.Body != nil { + requestBody, _ := req.GetBody() + requestData, _ = ioutil.ReadAll(requestBody) + } + + return getSha256(requestData) +} + +func requestHost(req *http.Request) string { + if req.Host != "" { + return req.Host + } + return req.URL.Host +} + +func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) string { + return strings.Join([]string{ + req.Method, + canonicalPath(req), + canonicalQuery(req), + canonicalHeaderData, + canonicalHeaderColumns, + requestDataHash(req), + }, "\n") +} + +func (rs *RequestSigner) SignedRequest(req *http.Request) *http.Request { + timestamp := rs.debugTimestamp + if timestamp.IsZero() { + timestamp = time.Now() + } + signedRequest := req.Clone(req.Context()) + + signedRequest.Header.Add("host", requestHost(req)) + + securityToken, ok := rs.AwsSecurityCredentials["security_token"] + if ok { + signedRequest.Header.Add("x-amz-security-token", securityToken) + } + + if signedRequest.Header.Get("date") == "" { + signedRequest.Header.Add("x-amz-date", timestamp.Format(awsTimeFormatLong)) + } + + signedRequest.Header.Set("Authorization", rs.generateAuthentication(signedRequest, timestamp)) + + return signedRequest +} + +func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) string { + canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req) + + dateStamp := timestamp.Format(awsTimeFormatShort) + serviceName := strings.Split(requestHost(req), ".")[0] + + credentialScope := strings.Join([]string{ + dateStamp, rs.RegionName, serviceName, awsRequestType, + }, "/") + + stringToSign := strings.Join([]string{ + awsAlgorithm, + timestamp.Format(awsTimeFormatLong), + credentialScope, + getSha256([]byte(canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData))), + }, "\n") + + signingKey := []byte("AWS4" + rs.AwsSecurityCredentials["secret_access_key"]) + for _, signingInput := range []string{ + dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign, + } { + signingKey = getHmacSha256(signingKey, []byte(signingInput)) + } + + return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials["access_key_id"], credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)) +} diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go new file mode 100644 index 000000000..dbd5c00c0 --- /dev/null +++ b/google/internal/externalaccount/aws_test.go @@ -0,0 +1,313 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package externalaccount + +import ( + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +var defaultTime = time.Date(2011, 9, 9, 23, 36, 0, 0, time.UTC) +var secondDefaultTime = time.Date(2020, 8, 11, 6, 55, 22, 0, time.UTC) + +var defaultRequestSigner = NewRequestSigner("us-east-1", map[string]string{ + "access_key_id": "AKIDEXAMPLE", + "secret_access_key": "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==" + +var requestSignerWithToken = NewRequestSigner("us-east-2", map[string]string{ + "access_key_id": accessKeyId, + "secret_access_key": secretAccessKey, + "security_token": securityToken, +}) + +func setDefaultTime(req *http.Request) { + // Don't use time.Format for this + // Our output signature expects this to be a Monday, even though Sept 9, 2011 is a Friday + req.Header.Add("date", "Mon, 09 Sep 2011 23:36:00 GMT") +} + +func testRequestSigner(t *testing.T, rs *RequestSigner, input, expectedOutput *http.Request) { + actualOutput := rs.SignedRequest(input) + + if got, want := actualOutput.URL.String(), expectedOutput.URL.String(); !reflect.DeepEqual(got, want) { + t.Errorf("url = %q, want %q", got, want) + } + if got, want := actualOutput.Method, expectedOutput.Method; !reflect.DeepEqual(got, want) { + t.Errorf("method = %q, want %q", got, want) + } + for header := range expectedOutput.Header { + if got, want := actualOutput.Header[header], expectedOutput.Header[header]; !reflect.DeepEqual(got, want) { + t.Errorf("header[%q] = %q, want %q", header, got, want) + } + } +} + +func TestAwsV4Signature_GetRequest(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithRelativePath(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/foo/bar/../..", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/foo/bar/../..", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithDotPath(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/./", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/./", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithPointlessDotPath(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/./foo", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/./foo", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithUtf8Path(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/%E1%88%B4", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/%E1%88%B4", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithDuplicateQuery(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/?foo=Zoo&foo=aha", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/?foo=Zoo&foo=aha", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithMisorderedQuery(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/?foo=b&foo=a", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/?foo=b&foo=a", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithUtf8Query(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/?ሴ=bar", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/?ሴ=bar", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequest(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + setDefaultTime(input) + input.Header.Add("ZOO", "zoobar") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a"}, + "Zoo": []string{"zoobar"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestWithCapitalizedHeaderValue(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + setDefaultTime(input) + input.Header.Add("zoo", "ZOOBAR") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7"}, + "Zoo": []string{"ZOOBAR"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestPhfft(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + setDefaultTime(input) + input.Header.Add("p", "phfft") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592"}, + "P": []string{"phfft"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestWithBody(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", strings.NewReader("foo=bar")) + setDefaultTime(input) + input.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Content-Type": []string{"application/x-www-form-urlencoded"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestWithQueryString(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/?foo=bar", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("POST", "https://host.foo.com/?foo=bar", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithSecurityToken(t *testing.T) { + input, _ := http.NewRequest("GET", "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15", nil) + + 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"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + "X-Amz-Security-Token": []string{securityToken}, + } + + testRequestSigner(t, requestSignerWithToken, input, output) +} + +func TestAwsV4Signature_PostRequestWithSecurityToken(t *testing.T) { + input, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) + + 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"}, + "Host": []string{"sts.us-east-2.amazonaws.com"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + "X-Amz-Security-Token": []string{securityToken}, + } + + testRequestSigner(t, requestSignerWithToken, input, output) +} + +func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *testing.T) { + requestParams := "{\"KeySchema\":[{\"KeyType\":\"HASH\",\"AttributeName\":\"Id\"}],\"TableName\":\"TestTable\",\"AttributeDefinitions\":[{\"AttributeName\":\"Id\",\"AttributeType\":\"S\"}],\"ProvisionedThroughput\":{\"WriteCapacityUnits\":5,\"ReadCapacityUnits\":5}}" + input, _ := http.NewRequest("POST", "https://dynamodb.us-east-2.amazonaws.com/", strings.NewReader(requestParams)) + input.Header.Add("Content-Type", "application/x-amz-json-1.0") + input.Header.Add("x-amz-target", "DynamoDB_20120810.CreateTable") + + 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"}, + "Host": []string{"dynamodb.us-east-2.amazonaws.com"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + "Content-Type": []string{"application/x-amz-json-1.0"}, + "X-Amz-Target": []string{"DynamoDB_20120810.CreateTable"}, + "X-Amz-Security-Token": []string{securityToken}, + } + + testRequestSigner(t, requestSignerWithToken, input, output) +} + +func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { + var requestSigner = NewRequestSigner("us-east-2", map[string]string{ + "access_key_id": accessKeyId, + "secret_access_key": secretAccessKey, + }) + requestSigner.debugTimestamp = secondDefaultTime + + input, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) + + 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"}, + "Host": []string{"sts.us-east-2.amazonaws.com"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + } + + testRequestSigner(t, requestSigner, input, output) +} + +func init() { + defaultRequestSigner.debugTimestamp = defaultTime + requestSignerWithToken.debugTimestamp = secondDefaultTime +} From 3f1a1ba4dba7ccfdcf7924ca15e2c4a519d14a52 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Tue, 19 Jan 2021 09:26:12 -0800 Subject: [PATCH 2/6] Create AWS V4 Signing Utility --- google/internal/externalaccount/aws.go | 178 +++++++++++ google/internal/externalaccount/aws_test.go | 313 ++++++++++++++++++++ 2 files changed, 491 insertions(+) create mode 100644 google/internal/externalaccount/aws.go create mode 100644 google/internal/externalaccount/aws_test.go diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go new file mode 100644 index 000000000..2357b2d80 --- /dev/null +++ b/google/internal/externalaccount/aws.go @@ -0,0 +1,178 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package externalaccount + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "io/ioutil" + "net/http" + "path" + "sort" + "strings" + "time" +) + +// A utility class to sign http requests using a AWS V4 signature +type RequestSigner struct { + RegionName string + AwsSecurityCredentials map[string]string + debugTimestamp time.Time +} + +func NewRequestSigner(regionName string, awsSecurityCredentials map[string]string) *RequestSigner { + return &RequestSigner{ + RegionName: regionName, + AwsSecurityCredentials: awsSecurityCredentials, + } +} + +// AWS Signature Version 4 signing algorithm identifier. +const 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 +const awsRequestType = "aws4_request" + +// The AWS authorization header name for the security session token if available. +const awsSecurityTokenHeader = "x-amz-security-token" + +// The AWS authorization header name for the auto-generated date. +const awsDateHeader = "x-amz-date" + +const awsTimeFormatLong = "20060102T150405Z" +const awsTimeFormatShort = "20060102" + +func getSha256(input []byte) string { + hash := sha256.New() + hash.Write(input) + return hex.EncodeToString(hash.Sum(nil)) +} + +func getHmacSha256(key, input []byte) []byte { + hash := hmac.New(sha256.New, key) + hash.Write(input) + return hash.Sum(nil) +} + +func canonicalPath(req *http.Request) string { + result := req.URL.EscapedPath() + if result == "" { + return "/" + } + return path.Clean(result) +} + +func canonicalQuery(req *http.Request) string { + queryValues := req.URL.Query() + for queryKey := range queryValues { + sort.Strings(queryValues[queryKey]) + } + return queryValues.Encode() +} + +func canonicalHeaders(req *http.Request) (string, string) { + // Header keys need to be sorted alphabetically. + var headers []string + lowerCaseHeaders := make(http.Header) + for k, v := range req.Header { + k := strings.ToLower(k) + if _, ok := lowerCaseHeaders[k]; ok { + // include additional values + lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...) + } else { + headers = append(headers, k) + lowerCaseHeaders[k] = v + } + } + sort.Strings(headers) + + var fullHeaders []string + for _, header := range headers { + headerValue := strings.Join(lowerCaseHeaders[header], ",") + fullHeaders = append(fullHeaders, header+":"+headerValue+"\n") + } + + return strings.Join(headers, ";"), strings.Join(fullHeaders, "") +} + +func requestDataHash(req *http.Request) string { + requestData := []byte{} + if req.Body != nil { + requestBody, _ := req.GetBody() + requestData, _ = ioutil.ReadAll(requestBody) + } + + return getSha256(requestData) +} + +func requestHost(req *http.Request) string { + if req.Host != "" { + return req.Host + } + return req.URL.Host +} + +func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) string { + return strings.Join([]string{ + req.Method, + canonicalPath(req), + canonicalQuery(req), + canonicalHeaderData, + canonicalHeaderColumns, + requestDataHash(req), + }, "\n") +} + +func (rs *RequestSigner) SignedRequest(req *http.Request) *http.Request { + timestamp := rs.debugTimestamp + if timestamp.IsZero() { + timestamp = time.Now() + } + signedRequest := req.Clone(req.Context()) + + signedRequest.Header.Add("host", requestHost(req)) + + securityToken, ok := rs.AwsSecurityCredentials["security_token"] + if ok { + signedRequest.Header.Add("x-amz-security-token", securityToken) + } + + if signedRequest.Header.Get("date") == "" { + signedRequest.Header.Add("x-amz-date", timestamp.Format(awsTimeFormatLong)) + } + + signedRequest.Header.Set("Authorization", rs.generateAuthentication(signedRequest, timestamp)) + + return signedRequest +} + +func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) string { + canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req) + + dateStamp := timestamp.Format(awsTimeFormatShort) + serviceName := strings.Split(requestHost(req), ".")[0] + + credentialScope := strings.Join([]string{ + dateStamp, rs.RegionName, serviceName, awsRequestType, + }, "/") + + stringToSign := strings.Join([]string{ + awsAlgorithm, + timestamp.Format(awsTimeFormatLong), + credentialScope, + getSha256([]byte(canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData))), + }, "\n") + + signingKey := []byte("AWS4" + rs.AwsSecurityCredentials["secret_access_key"]) + for _, signingInput := range []string{ + dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign, + } { + signingKey = getHmacSha256(signingKey, []byte(signingInput)) + } + + return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials["access_key_id"], credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)) +} diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go new file mode 100644 index 000000000..dbd5c00c0 --- /dev/null +++ b/google/internal/externalaccount/aws_test.go @@ -0,0 +1,313 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +package externalaccount + +import ( + "net/http" + "reflect" + "strings" + "testing" + "time" +) + +var defaultTime = time.Date(2011, 9, 9, 23, 36, 0, 0, time.UTC) +var secondDefaultTime = time.Date(2020, 8, 11, 6, 55, 22, 0, time.UTC) + +var defaultRequestSigner = NewRequestSigner("us-east-1", map[string]string{ + "access_key_id": "AKIDEXAMPLE", + "secret_access_key": "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==" + +var requestSignerWithToken = NewRequestSigner("us-east-2", map[string]string{ + "access_key_id": accessKeyId, + "secret_access_key": secretAccessKey, + "security_token": securityToken, +}) + +func setDefaultTime(req *http.Request) { + // Don't use time.Format for this + // Our output signature expects this to be a Monday, even though Sept 9, 2011 is a Friday + req.Header.Add("date", "Mon, 09 Sep 2011 23:36:00 GMT") +} + +func testRequestSigner(t *testing.T, rs *RequestSigner, input, expectedOutput *http.Request) { + actualOutput := rs.SignedRequest(input) + + if got, want := actualOutput.URL.String(), expectedOutput.URL.String(); !reflect.DeepEqual(got, want) { + t.Errorf("url = %q, want %q", got, want) + } + if got, want := actualOutput.Method, expectedOutput.Method; !reflect.DeepEqual(got, want) { + t.Errorf("method = %q, want %q", got, want) + } + for header := range expectedOutput.Header { + if got, want := actualOutput.Header[header], expectedOutput.Header[header]; !reflect.DeepEqual(got, want) { + t.Errorf("header[%q] = %q, want %q", header, got, want) + } + } +} + +func TestAwsV4Signature_GetRequest(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithRelativePath(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/foo/bar/../..", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/foo/bar/../..", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithDotPath(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/./", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/./", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithPointlessDotPath(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/./foo", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/./foo", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithUtf8Path(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/%E1%88%B4", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/%E1%88%B4", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithDuplicateQuery(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/?foo=Zoo&foo=aha", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/?foo=Zoo&foo=aha", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithMisorderedQuery(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/?foo=b&foo=a", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/?foo=b&foo=a", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithUtf8Query(t *testing.T) { + input, _ := http.NewRequest("GET", "https://host.foo.com/?ሴ=bar", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("GET", "https://host.foo.com/?ሴ=bar", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequest(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + setDefaultTime(input) + input.Header.Add("ZOO", "zoobar") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=b7a95a52518abbca0964a999a880429ab734f35ebbf1235bd79a5de87756dc4a"}, + "Zoo": []string{"zoobar"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestWithCapitalizedHeaderValue(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + setDefaultTime(input) + input.Header.Add("zoo", "ZOOBAR") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;zoo, Signature=273313af9d0c265c531e11db70bbd653f3ba074c1009239e8559d3987039cad7"}, + "Zoo": []string{"ZOOBAR"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestPhfft(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + setDefaultTime(input) + input.Header.Add("p", "phfft") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host;p, Signature=debf546796015d6f6ded8626f5ce98597c33b47b9164cf6b17b4642036fcb592"}, + "P": []string{"phfft"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestWithBody(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/", strings.NewReader("foo=bar")) + setDefaultTime(input) + input.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + output, _ := http.NewRequest("POST", "https://host.foo.com/", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Content-Type": []string{"application/x-www-form-urlencoded"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_PostRequestWithQueryString(t *testing.T) { + input, _ := http.NewRequest("POST", "https://host.foo.com/?foo=bar", nil) + setDefaultTime(input) + + output, _ := http.NewRequest("POST", "https://host.foo.com/?foo=bar", nil) + output.Header = http.Header{ + "Host": []string{"host.foo.com"}, + "Date": []string{"Mon, 09 Sep 2011 23:36:00 GMT"}, + "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92"}, + } + + testRequestSigner(t, defaultRequestSigner, input, output) +} + +func TestAwsV4Signature_GetRequestWithSecurityToken(t *testing.T) { + input, _ := http.NewRequest("GET", "https://ec2.us-east-2.amazonaws.com?Action=DescribeRegions&Version=2013-10-15", nil) + + 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"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + "X-Amz-Security-Token": []string{securityToken}, + } + + testRequestSigner(t, requestSignerWithToken, input, output) +} + +func TestAwsV4Signature_PostRequestWithSecurityToken(t *testing.T) { + input, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) + + 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"}, + "Host": []string{"sts.us-east-2.amazonaws.com"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + "X-Amz-Security-Token": []string{securityToken}, + } + + testRequestSigner(t, requestSignerWithToken, input, output) +} + +func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *testing.T) { + requestParams := "{\"KeySchema\":[{\"KeyType\":\"HASH\",\"AttributeName\":\"Id\"}],\"TableName\":\"TestTable\",\"AttributeDefinitions\":[{\"AttributeName\":\"Id\",\"AttributeType\":\"S\"}],\"ProvisionedThroughput\":{\"WriteCapacityUnits\":5,\"ReadCapacityUnits\":5}}" + input, _ := http.NewRequest("POST", "https://dynamodb.us-east-2.amazonaws.com/", strings.NewReader(requestParams)) + input.Header.Add("Content-Type", "application/x-amz-json-1.0") + input.Header.Add("x-amz-target", "DynamoDB_20120810.CreateTable") + + 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"}, + "Host": []string{"dynamodb.us-east-2.amazonaws.com"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + "Content-Type": []string{"application/x-amz-json-1.0"}, + "X-Amz-Target": []string{"DynamoDB_20120810.CreateTable"}, + "X-Amz-Security-Token": []string{securityToken}, + } + + testRequestSigner(t, requestSignerWithToken, input, output) +} + +func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { + var requestSigner = NewRequestSigner("us-east-2", map[string]string{ + "access_key_id": accessKeyId, + "secret_access_key": secretAccessKey, + }) + requestSigner.debugTimestamp = secondDefaultTime + + input, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) + + 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"}, + "Host": []string{"sts.us-east-2.amazonaws.com"}, + "X-Amz-Date": []string{"20200811T065522Z"}, + } + + testRequestSigner(t, requestSigner, input, output) +} + +func init() { + defaultRequestSigner.debugTimestamp = defaultTime + requestSignerWithToken.debugTimestamp = secondDefaultTime +} From d1a7728e50f1ae2ddfdc8a2d7864d9dbd8d2a036 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Wed, 20 Jan 2021 10:44:08 -0800 Subject: [PATCH 3/6] Fixes requested by codyoss@ --- google/internal/externalaccount/aws.go | 162 ++++++++++++++------ google/internal/externalaccount/aws_test.go | 91 +++++++++-- 2 files changed, 198 insertions(+), 55 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index 2357b2d80..d505cf866 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -1,13 +1,16 @@ // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. + package externalaccount import ( "crypto/hmac" "crypto/sha256" "encoding/hex" + "errors" "fmt" + "io" "io/ioutil" "net/http" "path" @@ -16,13 +19,13 @@ import ( "time" ) -// A utility class to sign http requests using a AWS V4 signature +// RequestSigner is a utility class to sign http requests using a AWS V4 signature. type RequestSigner struct { RegionName string AwsSecurityCredentials map[string]string - debugTimestamp time.Time } +// NewRequestSigner is a method to create a RequestSigner with the appropriately filled out fields. func NewRequestSigner(regionName string, awsSecurityCredentials map[string]string) *RequestSigner { return &RequestSigner{ RegionName: regionName, @@ -30,32 +33,60 @@ func NewRequestSigner(regionName string, awsSecurityCredentials map[string]strin } } +const ( // AWS Signature Version 4 signing algorithm identifier. -const awsAlgorithm = "AWS4-HMAC-SHA256" + 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 -const awsRequestType = "aws4_request" + awsRequestType = "aws4_request" // The AWS authorization header name for the security session token if available. -const awsSecurityTokenHeader = "x-amz-security-token" + awsSecurityTokenHeader = "x-amz-security-token" // The AWS authorization header name for the auto-generated date. -const awsDateHeader = "x-amz-date" + awsDateHeader = "x-amz-date" -const awsTimeFormatLong = "20060102T150405Z" -const awsTimeFormatShort = "20060102" + awsTimeFormatLong = "20060102T150405Z" + awsTimeFormatShort = "20060102" +) -func getSha256(input []byte) string { +func getSha256(input []byte) (string, error) { hash := sha256.New() - hash.Write(input) - return hex.EncodeToString(hash.Sum(nil)) + if _, err := hash.Write(input); err != nil { + return "", err + } + return hex.EncodeToString(hash.Sum(nil)), nil } -func getHmacSha256(key, input []byte) []byte { +func getHmacSha256(key, input []byte) ([]byte, error) { hash := hmac.New(sha256.New, key) - hash.Write(input) - return hash.Sum(nil) + if _, err := hash.Write(input); err != nil { + return nil, err + } + return hash.Sum(nil), nil +} + +func cloneRequest(r *http.Request) *http.Request { + r2 := new(http.Request) + *r2 = *r + if r.Header != nil { + r2.Header = make(http.Header, len(r.Header)) + + // Find total number of values. + headerCount := 0 + for _, headerValues := range r.Header { + headerCount += len(headerValues) + } + copiedHeaders := make([]string, headerCount) // shared backing array for headers' values + + for headerKey, headerValues := range r.Header { + headerCount = copy(copiedHeaders, headerValues) + r2.Header[headerKey] = copiedHeaders[:headerCount:headerCount] + copiedHeaders = copiedHeaders[headerCount:] + } + } + return r2 } func canonicalPath(req *http.Request) string { @@ -90,20 +121,31 @@ func canonicalHeaders(req *http.Request) (string, string) { } sort.Strings(headers) - var fullHeaders []string + var fullHeaders strings.Builder for _, header := range headers { headerValue := strings.Join(lowerCaseHeaders[header], ",") - fullHeaders = append(fullHeaders, header+":"+headerValue+"\n") + fullHeaders.WriteString(header) + fullHeaders.WriteRune(':') + fullHeaders.WriteString(headerValue) + fullHeaders.WriteRune('\n') } - return strings.Join(headers, ";"), strings.Join(fullHeaders, "") + return strings.Join(headers, ";"), fullHeaders.String() } -func requestDataHash(req *http.Request) string { - requestData := []byte{} +func requestDataHash(req *http.Request) (string, error) { + var requestData []byte if req.Body != nil { - requestBody, _ := req.GetBody() - requestData, _ = ioutil.ReadAll(requestBody) + requestBody, err := req.GetBody() + if err != nil { + return "", err + } + defer requestBody.Close() + + requestData, err = ioutil.ReadAll(io.LimitReader(requestBody, 1<<20)) + if err != nil { + return "", err + } } return getSha256(requestData) @@ -116,63 +158,93 @@ func requestHost(req *http.Request) string { return req.URL.Host } -func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) string { +func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) (string, error) { + dataHash, err := requestDataHash(req) + if err != nil { + return "", err + } + return strings.Join([]string{ req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, - requestDataHash(req), - }, "\n") + dataHash, + }, "\n"), nil } -func (rs *RequestSigner) SignedRequest(req *http.Request) *http.Request { - timestamp := rs.debugTimestamp - if timestamp.IsZero() { - timestamp = time.Now() - } - signedRequest := req.Clone(req.Context()) +// SignRequest adds the appropriate headers to an http.Request +// or returns an error if something prevented this. +func (rs *RequestSigner) SignRequest(req *http.Request) error { + signedRequest := cloneRequest(req) + timestamp := now() signedRequest.Header.Add("host", requestHost(req)) - securityToken, ok := rs.AwsSecurityCredentials["security_token"] - if ok { - signedRequest.Header.Add("x-amz-security-token", securityToken) + if securityToken, ok := rs.AwsSecurityCredentials["security_token"]; ok { + signedRequest.Header.Add(awsSecurityTokenHeader, securityToken) } if signedRequest.Header.Get("date") == "" { - signedRequest.Header.Add("x-amz-date", timestamp.Format(awsTimeFormatLong)) + signedRequest.Header.Add(awsDateHeader, timestamp.Format(awsTimeFormatLong)) } - signedRequest.Header.Set("Authorization", rs.generateAuthentication(signedRequest, timestamp)) + authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp) + if err != nil { + return err + } + signedRequest.Header.Set("Authorization", authorizationCode) - return signedRequest + req.Header = signedRequest.Header + return nil } -func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) string { +func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) { + secretAccessKey, ok := rs.AwsSecurityCredentials["secret_access_key"] + if !ok { + return "", errors.New("Missing Secret Access Key") + } + accessKeyId, ok := rs.AwsSecurityCredentials["access_key_id"] + if !ok { + return "", errors.New("Missing Access Key Id") + } + canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req) dateStamp := timestamp.Format(awsTimeFormatShort) - serviceName := strings.Split(requestHost(req), ".")[0] + serviceName := "" + if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 { + serviceName = splitHost[0] + } + + credentialScope := fmt.Sprintf("%s/%s/%s/%s",dateStamp, rs.RegionName, serviceName, awsRequestType) - credentialScope := strings.Join([]string{ - dateStamp, rs.RegionName, serviceName, awsRequestType, - }, "/") + requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData) + if err != nil { + return "", err + } + requestHash, err := getSha256([]byte(requestString)) + if err != nil{ + return "", err + } stringToSign := strings.Join([]string{ awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, - getSha256([]byte(canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData))), + requestHash, }, "\n") - signingKey := []byte("AWS4" + rs.AwsSecurityCredentials["secret_access_key"]) + signingKey := []byte("AWS4" + secretAccessKey) for _, signingInput := range []string{ dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign, } { - signingKey = getHmacSha256(signingKey, []byte(signingInput)) + signingKey, err = getHmacSha256(signingKey, []byte(signingInput)) + if err != nil{ + return "", err + } } - return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials["access_key_id"], credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)) + return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, accessKeyId, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil } diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go index dbd5c00c0..a96e76f61 100644 --- a/google/internal/externalaccount/aws_test.go +++ b/google/internal/externalaccount/aws_test.go @@ -14,6 +14,12 @@ import ( var defaultTime = time.Date(2011, 9, 9, 23, 36, 0, 0, time.UTC) var secondDefaultTime = time.Date(2020, 8, 11, 6, 55, 22, 0, time.UTC) +func setTime(testTime time.Time) func() time.Time { + return func() time.Time { + return testTime + } +} + var defaultRequestSigner = NewRequestSigner("us-east-1", map[string]string{ "access_key_id": "AKIDEXAMPLE", "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", @@ -36,16 +42,19 @@ func setDefaultTime(req *http.Request) { } func testRequestSigner(t *testing.T, rs *RequestSigner, input, expectedOutput *http.Request) { - actualOutput := rs.SignedRequest(input) + err := rs.SignRequest(input) + if err != nil { + t.Errorf("unexpected error: %q", err.Error()) + } - if got, want := actualOutput.URL.String(), expectedOutput.URL.String(); !reflect.DeepEqual(got, want) { + if got, want := input.URL.String(), expectedOutput.URL.String(); !reflect.DeepEqual(got, want) { t.Errorf("url = %q, want %q", got, want) } - if got, want := actualOutput.Method, expectedOutput.Method; !reflect.DeepEqual(got, want) { + if got, want := input.Method, expectedOutput.Method; !reflect.DeepEqual(got, want) { t.Errorf("method = %q, want %q", got, want) } for header := range expectedOutput.Header { - if got, want := actualOutput.Header[header], expectedOutput.Header[header]; !reflect.DeepEqual(got, want) { + if got, want := input.Header[header], expectedOutput.Header[header]; !reflect.DeepEqual(got, want) { t.Errorf("header[%q] = %q, want %q", header, got, want) } } @@ -62,6 +71,10 @@ func TestAwsV4Signature_GetRequest(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -76,6 +89,10 @@ func TestAwsV4Signature_GetRequestWithRelativePath(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -90,6 +107,10 @@ func TestAwsV4Signature_GetRequestWithDotPath(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b27ccfbfa7df52a200ff74193ca6e32d4b48b8856fab7ebf1c595d0670a7e470"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -104,6 +125,10 @@ func TestAwsV4Signature_GetRequestWithPointlessDotPath(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=910e4d6c9abafaf87898e1eb4c929135782ea25bb0279703146455745391e63a"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -118,6 +143,10 @@ func TestAwsV4Signature_GetRequestWithUtf8Path(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=8d6634c189aa8c75c2e51e106b6b5121bed103fdb351f7d7d4381c738823af74"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -132,6 +161,10 @@ func TestAwsV4Signature_GetRequestWithDuplicateQuery(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=be7148d34ebccdc6423b19085378aa0bee970bdc61d144bd1a8c48c33079ab09"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -146,6 +179,10 @@ func TestAwsV4Signature_GetRequestWithMisorderedQuery(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=feb926e49e382bec75c9d7dcb2a1b6dc8aa50ca43c25d2bc51143768c0875acc"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -160,6 +197,10 @@ func TestAwsV4Signature_GetRequestWithUtf8Query(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=6fb359e9a05394cc7074e0feb42573a2601abc0c869a953e8c5c12e4e01f1a8c"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -176,6 +217,10 @@ func TestAwsV4Signature_PostRequest(t *testing.T) { "Zoo": []string{"zoobar"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -192,6 +237,10 @@ func TestAwsV4Signature_PostRequestWithCapitalizedHeaderValue(t *testing.T) { "Zoo": []string{"ZOOBAR"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -208,6 +257,10 @@ func TestAwsV4Signature_PostRequestPhfft(t *testing.T) { "P": []string{"phfft"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -224,6 +277,10 @@ func TestAwsV4Signature_PostRequestWithBody(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=content-type;date;host, Signature=5a15b22cf462f047318703b92e6f4f38884e4a7ab7b1d6426ca46a8bd1c26cbc"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -238,6 +295,10 @@ func TestAwsV4Signature_PostRequestWithQueryString(t *testing.T) { "Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20110909/us-east-1/host/aws4_request, SignedHeaders=date;host, Signature=b6e3b79003ce0743a491606ba1035a804593b0efb1e20a11cba83f8c25a57a92"}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(defaultTime) + testRequestSigner(t, defaultRequestSigner, input, output) } @@ -252,6 +313,10 @@ func TestAwsV4Signature_GetRequestWithSecurityToken(t *testing.T) { "X-Amz-Security-Token": []string{securityToken}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(secondDefaultTime) + testRequestSigner(t, requestSignerWithToken, input, output) } @@ -266,6 +331,10 @@ func TestAwsV4Signature_PostRequestWithSecurityToken(t *testing.T) { "X-Amz-Security-Token": []string{securityToken}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(secondDefaultTime) + testRequestSigner(t, requestSignerWithToken, input, output) } @@ -285,6 +354,10 @@ func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test "X-Amz-Security-Token": []string{securityToken}, } + oldNow := now + defer func() { now = oldNow }() + now = setTime(secondDefaultTime) + testRequestSigner(t, requestSignerWithToken, input, output) } @@ -293,7 +366,6 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { "access_key_id": accessKeyId, "secret_access_key": secretAccessKey, }) - requestSigner.debugTimestamp = secondDefaultTime input, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) @@ -304,10 +376,9 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { "X-Amz-Date": []string{"20200811T065522Z"}, } - testRequestSigner(t, requestSigner, input, output) -} + oldNow := now + defer func() { now = oldNow }() + now = setTime(secondDefaultTime) -func init() { - defaultRequestSigner.debugTimestamp = defaultTime - requestSignerWithToken.debugTimestamp = secondDefaultTime + testRequestSigner(t, requestSigner, input, output) } From ff3aac6c198fd8f712f91c8ad154ce4820a18201 Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Wed, 20 Jan 2021 10:58:25 -0800 Subject: [PATCH 4/6] go fmt --- google/internal/externalaccount/aws.go | 18 +++++----- google/internal/externalaccount/aws_test.go | 37 +++++++++++---------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index d505cf866..a06134527 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -34,20 +34,20 @@ func NewRequestSigner(regionName string, awsSecurityCredentials map[string]strin } 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" ) @@ -218,14 +218,14 @@ func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp tim 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 } @@ -241,7 +241,7 @@ func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp tim dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign, } { signingKey, err = getHmacSha256(signingKey, []byte(signingInput)) - if err != nil{ + if err != nil { return "", err } } diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go index d1432dcee..d5e06a124 100644 --- a/google/internal/externalaccount/aws_test.go +++ b/google/internal/externalaccount/aws_test.go @@ -14,7 +14,8 @@ import ( var defaultTime = time.Date(2011, 9, 9, 23, 36, 0, 0, time.UTC) var secondDefaultTime = time.Date(2020, 8, 11, 6, 55, 22, 0, time.UTC) -func setTime(testTime time.Time) (func() time.Time) { + +func setTime(testTime time.Time) func() time.Time { return func() time.Time { return testTime } @@ -72,7 +73,7 @@ func TestAwsV4Signature_GetRequest(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -90,7 +91,7 @@ func TestAwsV4Signature_GetRequestWithRelativePath(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -108,7 +109,7 @@ func TestAwsV4Signature_GetRequestWithDotPath(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -126,7 +127,7 @@ func TestAwsV4Signature_GetRequestWithPointlessDotPath(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -144,7 +145,7 @@ func TestAwsV4Signature_GetRequestWithUtf8Path(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -162,7 +163,7 @@ func TestAwsV4Signature_GetRequestWithDuplicateQuery(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -180,7 +181,7 @@ func TestAwsV4Signature_GetRequestWithMisorderedQuery(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -198,7 +199,7 @@ func TestAwsV4Signature_GetRequestWithUtf8Query(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -218,7 +219,7 @@ func TestAwsV4Signature_PostRequest(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -238,7 +239,7 @@ func TestAwsV4Signature_PostRequestWithCapitalizedHeaderValue(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -258,7 +259,7 @@ func TestAwsV4Signature_PostRequestPhfft(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -278,7 +279,7 @@ func TestAwsV4Signature_PostRequestWithBody(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -296,7 +297,7 @@ func TestAwsV4Signature_PostRequestWithQueryString(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(defaultTime) testRequestSigner(t, defaultRequestSigner, input, output) @@ -314,7 +315,7 @@ func TestAwsV4Signature_GetRequestWithSecurityToken(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(secondDefaultTime) testRequestSigner(t, requestSignerWithToken, input, output) @@ -332,7 +333,7 @@ func TestAwsV4Signature_PostRequestWithSecurityToken(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(secondDefaultTime) testRequestSigner(t, requestSignerWithToken, input, output) @@ -355,7 +356,7 @@ func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(secondDefaultTime) testRequestSigner(t, requestSignerWithToken, input, output) @@ -377,7 +378,7 @@ func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { } oldNow := now - defer func() {now = oldNow}() + defer func() { now = oldNow }() now = setTime(secondDefaultTime) testRequestSigner(t, requestSigner, input, output) From 88fab8941c0b712fa46eb2d6bde087fffa7b47ed Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Thu, 21 Jan 2021 12:48:08 -0800 Subject: [PATCH 5/6] Fixes requested by codyoss@ --- google/internal/externalaccount/aws.go | 34 ++++-------------- google/internal/externalaccount/aws_test.go | 39 +++++++++++++-------- 2 files changed, 32 insertions(+), 41 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index a06134527..fd3d682c0 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -20,19 +20,11 @@ import ( ) // RequestSigner is a utility class to sign http requests using a AWS V4 signature. -type RequestSigner struct { +type awsRequestSigner struct { RegionName string AwsSecurityCredentials map[string]string } -// NewRequestSigner is a method to create a RequestSigner with the appropriately filled out fields. -func NewRequestSigner(regionName string, awsSecurityCredentials map[string]string) *RequestSigner { - return &RequestSigner{ - RegionName: regionName, - AwsSecurityCredentials: awsSecurityCredentials, - } -} - const ( // AWS Signature Version 4 signing algorithm identifier. awsAlgorithm = "AWS4-HMAC-SHA256" @@ -164,19 +156,12 @@ func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeader return "", err } - return strings.Join([]string{ - req.Method, - canonicalPath(req), - canonicalQuery(req), - canonicalHeaderData, - canonicalHeaderColumns, - dataHash, - }, "\n"), nil + return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil } // SignRequest adds the appropriate headers to an http.Request // or returns an error if something prevented this. -func (rs *RequestSigner) SignRequest(req *http.Request) error { +func (rs *awsRequestSigner) SignRequest(req *http.Request) error { signedRequest := cloneRequest(req) timestamp := now() @@ -200,14 +185,14 @@ func (rs *RequestSigner) SignRequest(req *http.Request) error { return nil } -func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, 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("Missing Secret Access Key") + return "", errors.New("oauth2/google: missing secret_access_key header") } accessKeyId, ok := rs.AwsSecurityCredentials["access_key_id"] if !ok { - return "", errors.New("Missing Access Key Id") + return "", errors.New("oauth2/google: missing access_key_id header") } canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req) @@ -229,12 +214,7 @@ func (rs *RequestSigner) generateAuthentication(req *http.Request, timestamp tim return "", err } - stringToSign := strings.Join([]string{ - awsAlgorithm, - timestamp.Format(awsTimeFormatLong), - credentialScope, - requestHash, - }, "\n") + stringToSign := fmt.Sprintf("%s\n%s\n%s\n%s", awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash) signingKey := []byte("AWS4" + secretAccessKey) for _, signingInput := range []string{ diff --git a/google/internal/externalaccount/aws_test.go b/google/internal/externalaccount/aws_test.go index d5e06a124..206c3a1af 100644 --- a/google/internal/externalaccount/aws_test.go +++ b/google/internal/externalaccount/aws_test.go @@ -21,20 +21,26 @@ func setTime(testTime time.Time) func() time.Time { } } -var defaultRequestSigner = NewRequestSigner("us-east-1", map[string]string{ - "access_key_id": "AKIDEXAMPLE", - "secret_access_key": "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY", -}) +var defaultRequestSigner = &awsRequestSigner{ + RegionName: "us-east-1", + AwsSecurityCredentials: map[string]string{ + "access_key_id": "AKIDEXAMPLE", + "secret_access_key": "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==" -var requestSignerWithToken = NewRequestSigner("us-east-2", map[string]string{ - "access_key_id": accessKeyId, - "secret_access_key": secretAccessKey, - "security_token": securityToken, -}) +var requestSignerWithToken = &awsRequestSigner{ + RegionName: "us-east-2", + AwsSecurityCredentials: map[string]string{ + "access_key_id": accessKeyId, + "secret_access_key": secretAccessKey, + "security_token": securityToken, + }, +} func setDefaultTime(req *http.Request) { // Don't use time.Format for this @@ -42,7 +48,9 @@ func setDefaultTime(req *http.Request) { req.Header.Add("date", "Mon, 09 Sep 2011 23:36:00 GMT") } -func testRequestSigner(t *testing.T, rs *RequestSigner, input, expectedOutput *http.Request) { +func testRequestSigner(t *testing.T, rs *awsRequestSigner, input, expectedOutput *http.Request) { + t.Helper() + err := rs.SignRequest(input) if err != nil { t.Errorf("unexpected error: %q", err.Error()) @@ -363,10 +371,13 @@ func TestAwsV4Signature_PostRequestWithSecurityTokenAndAdditionalHeaders(t *test } func TestAwsV4Signature_PostRequestWithAmzDateButNoSecurityToken(t *testing.T) { - var requestSigner = NewRequestSigner("us-east-2", map[string]string{ - "access_key_id": accessKeyId, - "secret_access_key": secretAccessKey, - }) + var requestSigner = &awsRequestSigner{ + RegionName: "us-east-2", + AwsSecurityCredentials: map[string]string{ + "access_key_id": accessKeyId, + "secret_access_key": secretAccessKey, + }, + } input, _ := http.NewRequest("POST", "https://sts.us-east-2.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15", nil) From 8cdc6a9ad02da61234e7d18db77294cef31367da Mon Sep 17 00:00:00 2001 From: Ryan Kohler Date: Mon, 25 Jan 2021 12:05:44 -0800 Subject: [PATCH 6/6] update name change --- google/internal/externalaccount/aws.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/google/internal/externalaccount/aws.go b/google/internal/externalaccount/aws.go index fd3d682c0..906d1fe9d 100644 --- a/google/internal/externalaccount/aws.go +++ b/google/internal/externalaccount/aws.go @@ -26,20 +26,20 @@ type awsRequestSigner struct { } 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" ) @@ -203,14 +203,14 @@ 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 } @@ -221,7 +221,7 @@ func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign, } { signingKey, err = getHmacSha256(signingKey, []byte(signingInput)) - if err != nil { + if err != nil{ return "", err } }