2024-10-16 10:54:40 +00:00
|
|
|
// Copyright 2023 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
package externalaccount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/hmac"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
"cloud.google.com/go/auth/internal"
|
2022-07-07 20:11:50 +00:00
|
|
|
)
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
var (
|
|
|
|
// getenv aliases os.Getenv for testing
|
|
|
|
getenv = os.Getenv
|
|
|
|
)
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
// 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
|
|
|
|
awsRequestType = "aws4_request"
|
|
|
|
|
|
|
|
// The AWS authorization header name for the security session token if available.
|
|
|
|
awsSecurityTokenHeader = "x-amz-security-token"
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
// The name of the header containing the session token for metadata endpoint calls
|
|
|
|
awsIMDSv2SessionTokenHeader = "X-aws-ec2-metadata-token"
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
awsIMDSv2SessionTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
|
2024-02-24 00:34:55 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
awsIMDSv2SessionTTL = "300"
|
2024-02-24 00:34:55 +00:00
|
|
|
|
2022-07-07 20:11:50 +00:00
|
|
|
// The AWS authorization header name for the auto-generated date.
|
|
|
|
awsDateHeader = "x-amz-date"
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
defaultRegionalCredentialVerificationURL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
// Supported AWS configuration environment variables.
|
2024-10-16 10:54:40 +00:00
|
|
|
awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
|
|
|
|
awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
|
|
|
|
awsRegionEnvVar = "AWS_REGION"
|
|
|
|
awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
|
|
|
|
awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
|
2024-02-24 00:34:55 +00:00
|
|
|
|
2022-07-07 20:11:50 +00:00
|
|
|
awsTimeFormatLong = "20060102T150405Z"
|
|
|
|
awsTimeFormatShort = "20060102"
|
2024-10-16 10:54:40 +00:00
|
|
|
awsProviderType = "aws"
|
2022-07-07 20:11:50 +00:00
|
|
|
)
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
type awsSubjectProvider struct {
|
2022-07-07 20:11:50 +00:00
|
|
|
EnvironmentID string
|
|
|
|
RegionURL string
|
|
|
|
RegionalCredVerificationURL string
|
|
|
|
CredVerificationURL string
|
2024-02-24 00:34:55 +00:00
|
|
|
IMDSv2SessionTokenURL string
|
2022-07-07 20:11:50 +00:00
|
|
|
TargetResource string
|
|
|
|
requestSigner *awsRequestSigner
|
|
|
|
region string
|
2024-10-16 10:54:40 +00:00
|
|
|
securityCredentialsProvider AwsSecurityCredentialsProvider
|
|
|
|
reqOpts *RequestOptions
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
Client *http.Client
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
|
|
|
|
// Set Defaults
|
|
|
|
if sp.RegionalCredVerificationURL == "" {
|
|
|
|
sp.RegionalCredVerificationURL = defaultRegionalCredentialVerificationURL
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
if sp.requestSigner == nil {
|
2024-02-24 00:34:55 +00:00
|
|
|
headers := make(map[string]string)
|
2024-10-16 10:54:40 +00:00
|
|
|
if sp.shouldUseMetadataServer() {
|
|
|
|
awsSessionToken, err := sp.getAWSSessionToken(ctx)
|
2024-02-24 00:34:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if awsSessionToken != "" {
|
|
|
|
headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
awsSecurityCredentials, err := sp.getSecurityCredentials(ctx, headers)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
if sp.region, err = sp.getRegion(ctx, headers); err != nil {
|
2022-07-07 20:11:50 +00:00
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
sp.requestSigner = &awsRequestSigner{
|
|
|
|
RegionName: sp.region,
|
2022-07-07 20:11:50 +00:00
|
|
|
AwsSecurityCredentials: awsSecurityCredentials,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the signed request to AWS STS GetCallerIdentity API.
|
|
|
|
// Use the required regional endpoint. Otherwise, the request will fail.
|
2024-10-16 10:54:40 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", strings.Replace(sp.RegionalCredVerificationURL, "{region}", sp.region, 1), nil)
|
2022-07-07 20:11:50 +00:00
|
|
|
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.
|
2024-10-16 10:54:40 +00:00
|
|
|
if sp.TargetResource != "" {
|
|
|
|
req.Header.Set("x-goog-cloud-target-resource", sp.TargetResource)
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
sp.requestSigner.signRequest(req)
|
2022-07-07 20:11:50 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
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: "POST",
|
|
|
|
}
|
|
|
|
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 url.QueryEscape(string(result)), nil
|
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) providerType() string {
|
|
|
|
if sp.securityCredentialsProvider != nil {
|
|
|
|
return programmaticProviderType
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return awsProviderType
|
|
|
|
}
|
2024-02-24 00:34:55 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, error) {
|
|
|
|
if sp.IMDSv2SessionTokenURL == "" {
|
|
|
|
return "", nil
|
2024-02-24 00:34:55 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "PUT", sp.IMDSv2SessionTokenURL, nil)
|
2024-02-24 00:34:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
|
2024-02-24 00:34:55 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
resp, body, err := internal.DoRequest(sp.Client, req)
|
2024-02-24 00:34:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
|
2024-02-24 00:34:55 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return string(body), nil
|
2024-02-24 00:34:55 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
|
|
|
|
if sp.securityCredentialsProvider != nil {
|
|
|
|
return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
|
|
|
|
}
|
2024-02-24 00:34:55 +00:00
|
|
|
if canRetrieveRegionFromEnvironment() {
|
2024-10-16 10:54:40 +00:00
|
|
|
if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
|
2024-02-24 00:34:55 +00:00
|
|
|
return envAwsRegion, nil
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return getenv(awsDefaultRegionEnvVar), nil
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
if sp.RegionURL == "" {
|
|
|
|
return "", errors.New("credentials: unable to determine AWS region")
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", sp.RegionURL, nil)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2024-02-24 00:34:55 +00:00
|
|
|
for name, value := range headers {
|
|
|
|
req.Header.Add(name, value)
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
resp, body, err := internal.DoRequest(sp.Client, req)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This endpoint will return the region in format: us-east-2b.
|
|
|
|
// Only the us-east-2 part should be used.
|
2024-10-16 10:54:40 +00:00
|
|
|
bodyLen := len(body)
|
|
|
|
if bodyLen == 0 {
|
|
|
|
return "", nil
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return string(body[:bodyLen-1]), nil
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
|
|
|
|
if sp.securityCredentialsProvider != nil {
|
|
|
|
return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
|
|
|
|
}
|
2024-02-24 00:34:55 +00:00
|
|
|
if canRetrieveSecurityCredentialFromEnvironment() {
|
2024-10-16 10:54:40 +00:00
|
|
|
return &AwsSecurityCredentials{
|
|
|
|
AccessKeyID: getenv(awsAccessKeyIDEnvVar),
|
|
|
|
SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
|
|
|
|
SessionToken: getenv(awsSessionTokenEnvVar),
|
2024-02-24 00:34:55 +00:00
|
|
|
}, nil
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
roleName, err := sp.getMetadataRoleName(ctx, headers)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
credentials, err := sp.getMetadataSecurityCredentials(ctx, roleName, headers)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if credentials.AccessKeyID == "" {
|
2024-10-16 10:54:40 +00:00
|
|
|
return result, errors.New("credentials: missing AccessKeyId credential")
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
if credentials.SecretAccessKey == "" {
|
2024-10-16 10:54:40 +00:00
|
|
|
return result, errors.New("credentials: missing SecretAccessKey credential")
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return credentials, nil
|
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
|
|
|
|
var result *AwsSecurityCredentials
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
2024-02-24 00:34:55 +00:00
|
|
|
for name, value := range headers {
|
|
|
|
req.Header.Add(name, value)
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
resp, body, err := internal.DoRequest(sp.Client, req)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &result); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return result, nil
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers map[string]string) (string, error) {
|
|
|
|
if sp.CredVerificationURL == "" {
|
|
|
|
return "", errors.New("credentials: unable to determine the AWS metadata server security credentials endpoint")
|
|
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", sp.CredVerificationURL, nil)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
2024-10-16 10:54:40 +00:00
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
for name, value := range headers {
|
|
|
|
req.Header.Add(name, value)
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
resp, body, err := internal.DoRequest(sp.Client, req)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
|
|
|
|
}
|
|
|
|
return string(body), nil
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
|
|
|
|
type awsRequestSigner struct {
|
|
|
|
RegionName string
|
|
|
|
AwsSecurityCredentials *AwsSecurityCredentials
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
// signRequest adds the appropriate headers to an http.Request
|
|
|
|
// or returns an error if something prevented this.
|
|
|
|
func (rs *awsRequestSigner) signRequest(req *http.Request) error {
|
|
|
|
// req is assumed non-nil
|
|
|
|
signedRequest := cloneRequest(req)
|
|
|
|
timestamp := Now()
|
|
|
|
signedRequest.Header.Set("host", requestHost(req))
|
|
|
|
if rs.AwsSecurityCredentials.SessionToken != "" {
|
|
|
|
signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
|
|
|
|
}
|
|
|
|
if signedRequest.Header.Get("date") == "" {
|
|
|
|
signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
|
|
|
|
}
|
|
|
|
authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
signedRequest.Header.Set("Authorization", authorizationCode)
|
|
|
|
req.Header = signedRequest.Header
|
|
|
|
return nil
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) {
|
|
|
|
canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req)
|
|
|
|
dateStamp := timestamp.Format(awsTimeFormatShort)
|
|
|
|
serviceName := ""
|
|
|
|
|
|
|
|
if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 {
|
|
|
|
serviceName = splitHost[0]
|
|
|
|
}
|
|
|
|
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))
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
stringToSign := strings.Join([]string{awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash}, "\n")
|
|
|
|
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 {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-02-24 00:34:55 +00:00
|
|
|
}
|
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getSha256(input []byte) (string, error) {
|
|
|
|
hash := sha256.New()
|
|
|
|
if _, err := hash.Write(input); err != nil {
|
2022-07-07 20:11:50 +00:00
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func getHmacSha256(key, input []byte) ([]byte, error) {
|
|
|
|
hash := hmac.New(sha256.New, key)
|
|
|
|
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 {
|
|
|
|
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 bytes.Buffer
|
|
|
|
for _, header := range headers {
|
|
|
|
headerValue := strings.Join(lowerCaseHeaders[header], ",")
|
|
|
|
fullHeaders.WriteString(header)
|
|
|
|
fullHeaders.WriteRune(':')
|
|
|
|
fullHeaders.WriteString(headerValue)
|
|
|
|
fullHeaders.WriteRune('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(headers, ";"), fullHeaders.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func requestDataHash(req *http.Request) (string, error) {
|
|
|
|
var requestData []byte
|
|
|
|
if req.Body != nil {
|
|
|
|
requestBody, err := req.GetBody()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer requestBody.Close()
|
|
|
|
|
|
|
|
requestData, err = internal.ReadAll(requestBody)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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, error) {
|
|
|
|
dataHash, err := requestDataHash(req)
|
2022-07-07 20:11:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2024-10-16 10:54:40 +00:00
|
|
|
return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
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"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
|
|
|
|
// required.
|
|
|
|
func canRetrieveRegionFromEnvironment() bool {
|
|
|
|
return getenv(awsRegionEnvVar) != "" || getenv(awsDefaultRegionEnvVar) != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
|
|
|
|
func canRetrieveSecurityCredentialFromEnvironment() bool {
|
|
|
|
return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
|
|
|
|
}
|
2022-07-07 20:11:50 +00:00
|
|
|
|
2024-10-16 10:54:40 +00:00
|
|
|
func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
|
|
|
|
return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
|
2022-07-07 20:11:50 +00:00
|
|
|
}
|