From 07b4c115520a9d2867f0dd962bddb06897af8299 Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Thu, 7 Mar 2024 12:45:04 -0500 Subject: [PATCH] feat: Closes #431, Refactored aws signer: removed unnecessary codes, fixed staticcheck errors --- aws/NOTICE.txt | 1 + aws/internal/auth/auth.go | 45 -- aws/internal/auth/scheme.go | 191 -------- aws/internal/auth/scheme_test.go | 101 ---- .../auth/smithy/bearer_token_adapter.go | 43 -- .../smithy/bearer_token_signer_adapter.go | 35 -- .../auth/smithy/credentials_adapter.go | 46 -- aws/internal/auth/smithy/smithy.go | 2 - aws/internal/auth/smithy/v4signer_adapter.go | 53 --- aws/internal/awstesting/assert.go | 222 --------- aws/internal/awstesting/assert_test.go | 89 ---- aws/internal/awstesting/certificate_utils.go | 291 ------------ aws/internal/awstesting/discard.go | 11 - aws/internal/awstesting/endless_reader.go | 12 - .../awstesting/sandbox/Dockerfile.golang-tip | 43 -- .../awstesting/sandbox/Dockerfile.test.gotip | 16 - .../sandbox/Dockerfile.test.goversion | 18 - aws/internal/awstesting/util.go | 201 -------- aws/internal/awstesting/util_test.go | 75 --- aws/internal/sdk/interfaces.go | 9 - aws/internal/sdk/time.go | 74 --- aws/internal/sdk/time_test.go | 32 -- aws/internal/strings/strings.go | 11 - aws/internal/strings/strings_test.go | 81 ---- aws/signer/internal/v4/header_rules.go | 10 +- aws/signer/v4/middleware.go | 443 ------------------ aws/signer/v4/middleware_test.go | 415 ---------------- aws/signer/v4/presign_middleware.go | 127 ----- aws/signer/v4/presign_middleware_test.go | 224 --------- aws/signer/v4/stream.go | 87 ---- aws/signer/v4/v4_test.go | 3 +- aws/staticcheck.conf | 1 - 32 files changed, 10 insertions(+), 3002 deletions(-) delete mode 100644 aws/internal/auth/auth.go delete mode 100644 aws/internal/auth/scheme.go delete mode 100644 aws/internal/auth/scheme_test.go delete mode 100644 aws/internal/auth/smithy/bearer_token_adapter.go delete mode 100644 aws/internal/auth/smithy/bearer_token_signer_adapter.go delete mode 100644 aws/internal/auth/smithy/credentials_adapter.go delete mode 100644 aws/internal/auth/smithy/smithy.go delete mode 100644 aws/internal/auth/smithy/v4signer_adapter.go delete mode 100644 aws/internal/awstesting/assert.go delete mode 100644 aws/internal/awstesting/assert_test.go delete mode 100644 aws/internal/awstesting/certificate_utils.go delete mode 100644 aws/internal/awstesting/discard.go delete mode 100644 aws/internal/awstesting/endless_reader.go delete mode 100644 aws/internal/awstesting/sandbox/Dockerfile.golang-tip delete mode 100644 aws/internal/awstesting/sandbox/Dockerfile.test.gotip delete mode 100644 aws/internal/awstesting/sandbox/Dockerfile.test.goversion delete mode 100644 aws/internal/awstesting/util.go delete mode 100644 aws/internal/awstesting/util_test.go delete mode 100644 aws/internal/sdk/interfaces.go delete mode 100644 aws/internal/sdk/time.go delete mode 100644 aws/internal/sdk/time_test.go delete mode 100644 aws/internal/strings/strings.go delete mode 100644 aws/internal/strings/strings_test.go delete mode 100644 aws/signer/v4/middleware.go delete mode 100644 aws/signer/v4/middleware_test.go delete mode 100644 aws/signer/v4/presign_middleware.go delete mode 100644 aws/signer/v4/presign_middleware_test.go delete mode 100644 aws/signer/v4/stream.go delete mode 100644 aws/staticcheck.conf diff --git a/aws/NOTICE.txt b/aws/NOTICE.txt index 899129e..5cc3afc 100644 --- a/aws/NOTICE.txt +++ b/aws/NOTICE.txt @@ -1,3 +1,4 @@ AWS SDK for Go Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. Copyright 2014-2015 Stripe, Inc. +Copyright 2024 Versity Software diff --git a/aws/internal/auth/auth.go b/aws/internal/auth/auth.go deleted file mode 100644 index 0b81db5..0000000 --- a/aws/internal/auth/auth.go +++ /dev/null @@ -1,45 +0,0 @@ -package auth - -import ( - "github.com/aws/smithy-go/auth" - smithyhttp "github.com/aws/smithy-go/transport/http" -) - -// HTTPAuthScheme is the SDK's internal implementation of smithyhttp.AuthScheme -// for pre-existing implementations where the signer was added to client -// config. SDK clients will key off of this type and ensure per-operation -// updates to those signers persist on the scheme itself. -type HTTPAuthScheme struct { - schemeID string - signer smithyhttp.Signer -} - -var _ smithyhttp.AuthScheme = (*HTTPAuthScheme)(nil) - -// NewHTTPAuthScheme returns an auth scheme instance with the given config. -func NewHTTPAuthScheme(schemeID string, signer smithyhttp.Signer) *HTTPAuthScheme { - return &HTTPAuthScheme{ - schemeID: schemeID, - signer: signer, - } -} - -// SchemeID identifies the auth scheme. -func (s *HTTPAuthScheme) SchemeID() string { - return s.schemeID -} - -// IdentityResolver gets the identity resolver for the auth scheme. -func (s *HTTPAuthScheme) IdentityResolver(o auth.IdentityResolverOptions) auth.IdentityResolver { - return o.GetIdentityResolver(s.schemeID) -} - -// Signer gets the signer for the auth scheme. -func (s *HTTPAuthScheme) Signer() smithyhttp.Signer { - return s.signer -} - -// WithSigner returns a new instance of the auth scheme with the updated signer. -func (s *HTTPAuthScheme) WithSigner(signer smithyhttp.Signer) *HTTPAuthScheme { - return NewHTTPAuthScheme(s.schemeID, signer) -} diff --git a/aws/internal/auth/scheme.go b/aws/internal/auth/scheme.go deleted file mode 100644 index bbc2ec0..0000000 --- a/aws/internal/auth/scheme.go +++ /dev/null @@ -1,191 +0,0 @@ -package auth - -import ( - "context" - "fmt" - - smithy "github.com/aws/smithy-go" - "github.com/aws/smithy-go/middleware" -) - -// SigV4 is a constant representing -// Authentication Scheme Signature Version 4 -const SigV4 = "sigv4" - -// SigV4A is a constant representing -// Authentication Scheme Signature Version 4A -const SigV4A = "sigv4a" - -// SigV4S3Express identifies the S3 S3Express auth scheme. -const SigV4S3Express = "sigv4-s3express" - -// None is a constant representing the -// None Authentication Scheme -const None = "none" - -// SupportedSchemes is a data structure -// that indicates the list of supported AWS -// authentication schemes -var SupportedSchemes = map[string]bool{ - SigV4: true, - SigV4A: true, - SigV4S3Express: true, - None: true, -} - -// AuthenticationScheme is a representation of -// AWS authentication schemes -type AuthenticationScheme interface { - isAuthenticationScheme() -} - -// AuthenticationSchemeV4 is a AWS SigV4 representation -type AuthenticationSchemeV4 struct { - Name string - SigningName *string - SigningRegion *string - DisableDoubleEncoding *bool -} - -func (a *AuthenticationSchemeV4) isAuthenticationScheme() {} - -// AuthenticationSchemeV4A is a AWS SigV4A representation -type AuthenticationSchemeV4A struct { - Name string - SigningName *string - SigningRegionSet []string - DisableDoubleEncoding *bool -} - -func (a *AuthenticationSchemeV4A) isAuthenticationScheme() {} - -// AuthenticationSchemeNone is a representation for the none auth scheme -type AuthenticationSchemeNone struct{} - -func (a *AuthenticationSchemeNone) isAuthenticationScheme() {} - -// NoAuthenticationSchemesFoundError is used in signaling -// that no authentication schemes have been specified. -type NoAuthenticationSchemesFoundError struct{} - -func (e *NoAuthenticationSchemesFoundError) Error() string { - return fmt.Sprint("No authentication schemes specified.") -} - -// UnSupportedAuthenticationSchemeSpecifiedError is used in -// signaling that only unsupported authentication schemes -// were specified. -type UnSupportedAuthenticationSchemeSpecifiedError struct { - UnsupportedSchemes []string -} - -func (e *UnSupportedAuthenticationSchemeSpecifiedError) Error() string { - return fmt.Sprint("Unsupported authentication scheme specified.") -} - -// GetAuthenticationSchemes extracts the relevant authentication scheme data -// into a custom strongly typed Go data structure. -func GetAuthenticationSchemes(p *smithy.Properties) ([]AuthenticationScheme, error) { - var result []AuthenticationScheme - if !p.Has("authSchemes") { - return nil, &NoAuthenticationSchemesFoundError{} - } - - authSchemes, _ := p.Get("authSchemes").([]interface{}) - - var unsupportedSchemes []string - for _, scheme := range authSchemes { - authScheme, _ := scheme.(map[string]interface{}) - - version := authScheme["name"].(string) - switch version { - case SigV4, SigV4S3Express: - v4Scheme := AuthenticationSchemeV4{ - Name: version, - SigningName: getSigningName(authScheme), - SigningRegion: getSigningRegion(authScheme), - DisableDoubleEncoding: getDisableDoubleEncoding(authScheme), - } - result = append(result, AuthenticationScheme(&v4Scheme)) - case SigV4A: - v4aScheme := AuthenticationSchemeV4A{ - Name: SigV4A, - SigningName: getSigningName(authScheme), - SigningRegionSet: getSigningRegionSet(authScheme), - DisableDoubleEncoding: getDisableDoubleEncoding(authScheme), - } - result = append(result, AuthenticationScheme(&v4aScheme)) - case None: - noneScheme := AuthenticationSchemeNone{} - result = append(result, AuthenticationScheme(&noneScheme)) - default: - unsupportedSchemes = append(unsupportedSchemes, authScheme["name"].(string)) - continue - } - } - - if len(result) == 0 { - return nil, &UnSupportedAuthenticationSchemeSpecifiedError{ - UnsupportedSchemes: unsupportedSchemes, - } - } - - return result, nil -} - -type disableDoubleEncoding struct{} - -// SetDisableDoubleEncoding sets or modifies the disable double encoding option -// on the context. -// -// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues -// to clear all stack values. -func SetDisableDoubleEncoding(ctx context.Context, value bool) context.Context { - return middleware.WithStackValue(ctx, disableDoubleEncoding{}, value) -} - -// GetDisableDoubleEncoding retrieves the disable double encoding option -// from the context. -// -// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues -// to clear all stack values. -func GetDisableDoubleEncoding(ctx context.Context) (value bool, ok bool) { - value, ok = middleware.GetStackValue(ctx, disableDoubleEncoding{}).(bool) - return value, ok -} - -func getSigningName(authScheme map[string]interface{}) *string { - signingName, ok := authScheme["signingName"].(string) - if !ok || signingName == "" { - return nil - } - return &signingName -} - -func getSigningRegionSet(authScheme map[string]interface{}) []string { - untypedSigningRegionSet, ok := authScheme["signingRegionSet"].([]interface{}) - if !ok { - return nil - } - signingRegionSet := []string{} - for _, item := range untypedSigningRegionSet { - signingRegionSet = append(signingRegionSet, item.(string)) - } - return signingRegionSet -} - -func getSigningRegion(authScheme map[string]interface{}) *string { - signingRegion, ok := authScheme["signingRegion"].(string) - if !ok || signingRegion == "" { - return nil - } - return &signingRegion -} - -func getDisableDoubleEncoding(authScheme map[string]interface{}) *bool { - disableDoubleEncoding, ok := authScheme["disableDoubleEncoding"].(bool) - if !ok { - return nil - } - return &disableDoubleEncoding -} diff --git a/aws/internal/auth/scheme_test.go b/aws/internal/auth/scheme_test.go deleted file mode 100644 index 83e1dd6..0000000 --- a/aws/internal/auth/scheme_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package auth - -import ( - "testing" - - smithy "github.com/aws/smithy-go" -) - -func TestV4(t *testing.T) { - - propsV4 := smithy.Properties{} - - propsV4.Set("authSchemes", interface{}([]interface{}{ - map[string]interface{}{ - "disableDoubleEncoding": true, - "name": "sigv4", - "signingName": "s3", - "signingRegion": "us-west-2", - }, - })) - - result, err := GetAuthenticationSchemes(&propsV4) - if err != nil { - t.Fatalf("Did not expect error, got %v", err) - } - - _, ok := result[0].(AuthenticationScheme) - if !ok { - t.Fatalf("Did not get expected AuthenticationScheme. %v", result[0]) - } - - v4Scheme, ok := result[0].(*AuthenticationSchemeV4) - if !ok { - t.Fatalf("Did not get expected AuthenticationSchemeV4. %v", result[0]) - } - - if v4Scheme.Name != "sigv4" { - t.Fatalf("Did not get expected AuthenticationSchemeV4 signer version name") - } - -} - -func TestV4A(t *testing.T) { - - propsV4A := smithy.Properties{} - - propsV4A.Set("authSchemes", []interface{}{ - map[string]interface{}{ - "disableDoubleEncoding": true, - "name": "sigv4a", - "signingName": "s3", - "signingRegionSet": []string{"*"}, - }, - }) - - result, err := GetAuthenticationSchemes(&propsV4A) - if err != nil { - t.Fatalf("Did not expect error, got %v", err) - } - - _, ok := result[0].(AuthenticationScheme) - if !ok { - t.Fatalf("Did not get expected AuthenticationScheme. %v", result[0]) - } - - v4AScheme, ok := result[0].(*AuthenticationSchemeV4A) - if !ok { - t.Fatalf("Did not get expected AuthenticationSchemeV4A. %v", result[0]) - } - - if v4AScheme.Name != "sigv4a" { - t.Fatalf("Did not get expected AuthenticationSchemeV4A signer version name") - } - -} - -func TestV4S3Express(t *testing.T) { - props := smithy.Properties{} - props.Set("authSchemes", []interface{}{ - map[string]interface{}{ - "name": SigV4S3Express, - "signingName": "s3", - "signingRegion": "us-east-1", - "disableDoubleEncoding": true, - }, - }) - - result, err := GetAuthenticationSchemes(&props) - if err != nil { - t.Fatalf("Did not expect error, got %v", err) - } - - scheme, ok := result[0].(*AuthenticationSchemeV4) - if !ok { - t.Fatalf("Did not get expected AuthenticationSchemeV4. %v", result[0]) - } - - if scheme.Name != SigV4S3Express { - t.Fatalf("expected %s, got %s", SigV4S3Express, scheme.Name) - } -} diff --git a/aws/internal/auth/smithy/bearer_token_adapter.go b/aws/internal/auth/smithy/bearer_token_adapter.go deleted file mode 100644 index f059b5d..0000000 --- a/aws/internal/auth/smithy/bearer_token_adapter.go +++ /dev/null @@ -1,43 +0,0 @@ -package smithy - -import ( - "context" - "fmt" - "time" - - "github.com/aws/smithy-go" - "github.com/aws/smithy-go/auth" - "github.com/aws/smithy-go/auth/bearer" -) - -// BearerTokenAdapter adapts smithy bearer.Token to smithy auth.Identity. -type BearerTokenAdapter struct { - Token bearer.Token -} - -var _ auth.Identity = (*BearerTokenAdapter)(nil) - -// Expiration returns the time of expiration for the token. -func (v *BearerTokenAdapter) Expiration() time.Time { - return v.Token.Expires -} - -// BearerTokenProviderAdapter adapts smithy bearer.TokenProvider to smithy -// auth.IdentityResolver. -type BearerTokenProviderAdapter struct { - Provider bearer.TokenProvider -} - -var _ (auth.IdentityResolver) = (*BearerTokenProviderAdapter)(nil) - -// GetIdentity retrieves a bearer token using the underlying provider. -func (v *BearerTokenProviderAdapter) GetIdentity(ctx context.Context, _ smithy.Properties) ( - auth.Identity, error, -) { - token, err := v.Provider.RetrieveBearerToken(ctx) - if err != nil { - return nil, fmt.Errorf("get token: %w", err) - } - - return &BearerTokenAdapter{Token: token}, nil -} diff --git a/aws/internal/auth/smithy/bearer_token_signer_adapter.go b/aws/internal/auth/smithy/bearer_token_signer_adapter.go deleted file mode 100644 index a882815..0000000 --- a/aws/internal/auth/smithy/bearer_token_signer_adapter.go +++ /dev/null @@ -1,35 +0,0 @@ -package smithy - -import ( - "context" - "fmt" - - "github.com/aws/smithy-go" - "github.com/aws/smithy-go/auth" - "github.com/aws/smithy-go/auth/bearer" - smithyhttp "github.com/aws/smithy-go/transport/http" -) - -// BearerTokenSignerAdapter adapts smithy bearer.Signer to smithy http -// auth.Signer. -type BearerTokenSignerAdapter struct { - Signer bearer.Signer -} - -var _ (smithyhttp.Signer) = (*BearerTokenSignerAdapter)(nil) - -// SignRequest signs the request with the provided bearer token. -func (v *BearerTokenSignerAdapter) SignRequest(ctx context.Context, r *smithyhttp.Request, identity auth.Identity, _ smithy.Properties) error { - ca, ok := identity.(*BearerTokenAdapter) - if !ok { - return fmt.Errorf("unexpected identity type: %T", identity) - } - - signed, err := v.Signer.SignWithBearerToken(ctx, ca.Token, r) - if err != nil { - return fmt.Errorf("sign request: %w", err) - } - - *r = *signed.(*smithyhttp.Request) - return nil -} diff --git a/aws/internal/auth/smithy/credentials_adapter.go b/aws/internal/auth/smithy/credentials_adapter.go deleted file mode 100644 index f926c4a..0000000 --- a/aws/internal/auth/smithy/credentials_adapter.go +++ /dev/null @@ -1,46 +0,0 @@ -package smithy - -import ( - "context" - "fmt" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/smithy-go" - "github.com/aws/smithy-go/auth" -) - -// CredentialsAdapter adapts aws.Credentials to auth.Identity. -type CredentialsAdapter struct { - Credentials aws.Credentials -} - -var _ auth.Identity = (*CredentialsAdapter)(nil) - -// Expiration returns the time of expiration for the credentials. -func (v *CredentialsAdapter) Expiration() time.Time { - return v.Credentials.Expires -} - -// CredentialsProviderAdapter adapts aws.CredentialsProvider to auth.IdentityResolver. -type CredentialsProviderAdapter struct { - Provider aws.CredentialsProvider -} - -var _ (auth.IdentityResolver) = (*CredentialsProviderAdapter)(nil) - -// GetIdentity retrieves AWS credentials using the underlying provider. -func (v *CredentialsProviderAdapter) GetIdentity(ctx context.Context, _ smithy.Properties) ( - auth.Identity, error, -) { - if v.Provider == nil { - return &CredentialsAdapter{Credentials: aws.Credentials{}}, nil - } - - creds, err := v.Provider.Retrieve(ctx) - if err != nil { - return nil, fmt.Errorf("get credentials: %w", err) - } - - return &CredentialsAdapter{Credentials: creds}, nil -} diff --git a/aws/internal/auth/smithy/smithy.go b/aws/internal/auth/smithy/smithy.go deleted file mode 100644 index 42b4586..0000000 --- a/aws/internal/auth/smithy/smithy.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package smithy adapts concrete AWS auth and signing types to the generic smithy versions. -package smithy diff --git a/aws/internal/auth/smithy/v4signer_adapter.go b/aws/internal/auth/smithy/v4signer_adapter.go deleted file mode 100644 index 263f164..0000000 --- a/aws/internal/auth/smithy/v4signer_adapter.go +++ /dev/null @@ -1,53 +0,0 @@ -package smithy - -import ( - "context" - "fmt" - - v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" - "github.com/aws/smithy-go" - "github.com/aws/smithy-go/auth" - "github.com/aws/smithy-go/logging" - smithyhttp "github.com/aws/smithy-go/transport/http" - "github.com/versity/versitygw/aws/internal/sdk" -) - -// V4SignerAdapter adapts v4.HTTPSigner to smithy http.Signer. -type V4SignerAdapter struct { - Signer v4.HTTPSigner - Logger logging.Logger - LogSigning bool -} - -var _ (smithyhttp.Signer) = (*V4SignerAdapter)(nil) - -// SignRequest signs the request with the provided identity. -func (v *V4SignerAdapter) SignRequest(ctx context.Context, r *smithyhttp.Request, identity auth.Identity, props smithy.Properties) error { - ca, ok := identity.(*CredentialsAdapter) - if !ok { - return fmt.Errorf("unexpected identity type: %T", identity) - } - - name, ok := smithyhttp.GetSigV4SigningName(&props) - if !ok { - return fmt.Errorf("sigv4 signing name is required") - } - - region, ok := smithyhttp.GetSigV4SigningRegion(&props) - if !ok { - return fmt.Errorf("sigv4 signing region is required") - } - - hash := v4.GetPayloadHash(ctx) - err := v.Signer.SignHTTP(ctx, ca.Credentials, r.Request, hash, name, region, sdk.NowTime(), func(o *v4.SignerOptions) { - o.DisableURIPathEscaping, _ = smithyhttp.GetDisableDoubleEncoding(&props) - - o.Logger = v.Logger - o.LogSigning = v.LogSigning - }) - if err != nil { - return fmt.Errorf("sign http: %w", err) - } - - return nil -} diff --git a/aws/internal/awstesting/assert.go b/aws/internal/awstesting/assert.go deleted file mode 100644 index 9b884c3..0000000 --- a/aws/internal/awstesting/assert.go +++ /dev/null @@ -1,222 +0,0 @@ -package awstesting - -import ( - "encoding/json" - "encoding/xml" - "fmt" - "net/url" - "reflect" - "regexp" - "sort" - "strings" - "testing" -) - -// Match is a testing helper to test for testing error by comparing expected -// with a regular expression. -func Match(t *testing.T, regex, expected string) { - t.Helper() - - if !regexp.MustCompile(regex).Match([]byte(expected)) { - t.Errorf("%q\n\tdoes not match /%s/", expected, regex) - } -} - -// AssertURL verifies the expected URL is matches the actual. -func AssertURL(t *testing.T, expect, actual string, msgAndArgs ...interface{}) bool { - t.Helper() - - expectURL, err := url.Parse(expect) - if err != nil { - t.Errorf(errMsg("unable to parse expected URL", err, msgAndArgs)) - return false - } - actualURL, err := url.Parse(actual) - if err != nil { - t.Errorf(errMsg("unable to parse actual URL", err, msgAndArgs)) - return false - } - - equal(t, expectURL.Host, actualURL.Host, msgAndArgs...) - equal(t, expectURL.Scheme, actualURL.Scheme, msgAndArgs...) - equal(t, expectURL.Path, actualURL.Path, msgAndArgs...) - - return AssertQuery(t, expectURL.Query().Encode(), actualURL.Query().Encode(), msgAndArgs...) -} - -var queryMapKey = regexp.MustCompile(`(.*?)\.[0-9]+\.key`) - -// AssertQuery verifies the expect HTTP query string matches the actual. -func AssertQuery(t *testing.T, expect, actual string, msgAndArgs ...interface{}) bool { - t.Helper() - - expectQ, err := url.ParseQuery(expect) - if err != nil { - t.Errorf(errMsg("unable to parse expected Query", err, msgAndArgs)) - return false - } - actualQ, err := url.ParseQuery(actual) - if err != nil { - t.Errorf(errMsg("unable to parse actual Query", err, msgAndArgs)) - return false - } - - // Make sure the keys are the same - if !equal(t, queryValueKeys(expectQ), queryValueKeys(actualQ), msgAndArgs...) { - return false - } - - keys := map[string][]string{} - for key, v := range expectQ { - if queryMapKey.Match([]byte(key)) { - submatch := queryMapKey.FindStringSubmatch(key) - keys[submatch[1]] = append(keys[submatch[1]], v...) - } - } - - for k, v := range keys { - // clear all keys that have prefix - for key := range expectQ { - if strings.HasPrefix(key, k) { - delete(expectQ, key) - } - } - - sort.Strings(v) - for i, value := range v { - expectQ[fmt.Sprintf("%s.%d.key", k, i+1)] = []string{value} - } - } - - for k, expectQVals := range expectQ { - sort.Strings(expectQVals) - actualQVals := actualQ[k] - sort.Strings(actualQVals) - if !equal(t, expectQVals, actualQVals, msgAndArgs...) { - return false - } - } - - return true -} - -// AssertJSON verifies that the expect json string matches the actual. -func AssertJSON(t *testing.T, expect, actual string, msgAndArgs ...interface{}) bool { - t.Helper() - - expectVal := map[string]interface{}{} - if err := json.Unmarshal([]byte(expect), &expectVal); err != nil { - t.Errorf(errMsg("unable to parse expected JSON", err, msgAndArgs...)) - return false - } - - actualVal := map[string]interface{}{} - if err := json.Unmarshal([]byte(actual), &actualVal); err != nil { - t.Errorf(errMsg("unable to parse actual JSON", err, msgAndArgs...)) - return false - } - - return equal(t, expectVal, actualVal, msgAndArgs...) -} - -// AssertXML verifies that the expect xml string matches the actual. -func AssertXML(t *testing.T, expect, actual string, container interface{}, msgAndArgs ...interface{}) bool { - expectVal := container - if err := xml.Unmarshal([]byte(expect), &expectVal); err != nil { - t.Errorf(errMsg("unable to parse expected XML", err, msgAndArgs...)) - } - - actualVal := container - if err := xml.Unmarshal([]byte(actual), &actualVal); err != nil { - t.Errorf(errMsg("unable to parse actual XML", err, msgAndArgs...)) - } - return equal(t, expectVal, actualVal, msgAndArgs...) -} - -// DidPanic returns if the function paniced and returns true if the function paniced. -func DidPanic(fn func()) (bool, interface{}) { - var paniced bool - var msg interface{} - func() { - defer func() { - if msg = recover(); msg != nil { - paniced = true - } - }() - fn() - }() - - return paniced, msg -} - -// objectsAreEqual determines if two objects are considered equal. -// -// This function does no assertion of any kind. -// -// Based on github.com/stretchr/testify/assert.ObjectsAreEqual -// Copied locally to prevent non-test build dependencies on testify -func objectsAreEqual(expected, actual interface{}) bool { - if expected == nil || actual == nil { - return expected == actual - } - - return reflect.DeepEqual(expected, actual) -} - -// Equal asserts that two objects are equal. -// -// assert.Equal(t, 123, 123, "123 and 123 should be equal") -// -// Returns whether the assertion was successful (true) or not (false). -// -// Based on github.com/stretchr/testify/assert.Equal -// Copied locally to prevent non-test build dependencies on testify -func equal(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) bool { - t.Helper() - - if !objectsAreEqual(expected, actual) { - t.Errorf("%s\n%s", messageFromMsgAndArgs(msgAndArgs), - SprintExpectActual(expected, actual)) - return false - } - - return true -} - -func errMsg(baseMsg string, err error, msgAndArgs ...interface{}) string { - message := messageFromMsgAndArgs(msgAndArgs) - if message != "" { - message += ", " - } - return fmt.Sprintf("%s%s, %v", message, baseMsg, err) -} - -// Based on github.com/stretchr/testify/assert.messageFromMsgAndArgs -// Copied locally to prevent non-test build dependencies on testify -func messageFromMsgAndArgs(msgAndArgs []interface{}) string { - if len(msgAndArgs) == 0 || msgAndArgs == nil { - return "" - } - if len(msgAndArgs) == 1 { - return msgAndArgs[0].(string) - } - if len(msgAndArgs) > 1 { - return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) - } - return "" -} - -func queryValueKeys(v url.Values) []string { - keys := make([]string, 0, len(v)) - for k := range v { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - -// SprintExpectActual returns a string for test failure cases when the actual -// value is not the same as the expected. -func SprintExpectActual(expect, actual interface{}) string { - return fmt.Sprintf("expect: %+v\nactual: %+v\n", expect, actual) -} diff --git a/aws/internal/awstesting/assert_test.go b/aws/internal/awstesting/assert_test.go deleted file mode 100644 index e14ff12..0000000 --- a/aws/internal/awstesting/assert_test.go +++ /dev/null @@ -1,89 +0,0 @@ -package awstesting_test - -import ( - "encoding/xml" - "testing" - - "github.com/versity/versitygw/aws/internal/awstesting" -) - -func TestAssertJSON(t *testing.T) { - cases := []struct { - e, a string - asserts bool - }{ - { - e: `{"RecursiveStruct":{"RecursiveMap":{"foo":{"NoRecurse":"foo"},"bar":{"NoRecurse":"bar"}}}}`, - a: `{"RecursiveStruct":{"RecursiveMap":{"bar":{"NoRecurse":"bar"},"foo":{"NoRecurse":"foo"}}}}`, - asserts: true, - }, - } - - for i, c := range cases { - mockT := &testing.T{} - if awstesting.AssertJSON(mockT, c.e, c.a) != c.asserts { - t.Error("Assert JSON result was not expected.", i) - } - } -} - -func TestAssertXML(t *testing.T) { - cases := []struct { - e, a string - asserts bool - container struct { - XMLName xml.Name `xml:"OperationRequest"` - NS string `xml:"xmlns,attr"` - RecursiveStruct struct { - RecursiveMap struct { - Entries []struct { - XMLName xml.Name `xml:"entries"` - Key string `xml:"key"` - Value struct { - XMLName xml.Name `xml:"value"` - NoRecurse string - } - } - } - } - } - }{ - { - e: `foofoobarbar`, - a: `barbarfoofoo`, - asserts: true, - }, - } - - for i, c := range cases { - // mockT := &testing.T{} - if awstesting.AssertXML(t, c.e, c.a, c.container) != c.asserts { - t.Error("Assert XML result was not expected.", i) - } - } -} - -func TestAssertQuery(t *testing.T) { - cases := []struct { - e, a string - asserts bool - }{ - { - e: `Action=OperationName&Version=2014-01-01&Foo=val1&Bar=val2`, - a: `Action=OperationName&Version=2014-01-01&Foo=val2&Bar=val3`, - asserts: false, - }, - { - e: `Action=OperationName&Version=2014-01-01&Foo=val1&Bar=val2`, - a: `Action=OperationName&Version=2014-01-01&Foo=val1&Bar=val2`, - asserts: true, - }, - } - - for i, c := range cases { - mockT := &testing.T{} - if awstesting.AssertQuery(mockT, c.e, c.a) != c.asserts { - t.Error("Assert Query result was not expected.", i) - } - } -} diff --git a/aws/internal/awstesting/certificate_utils.go b/aws/internal/awstesting/certificate_utils.go deleted file mode 100644 index 8cabf79..0000000 --- a/aws/internal/awstesting/certificate_utils.go +++ /dev/null @@ -1,291 +0,0 @@ -package awstesting - -import ( - "bytes" - "crypto/rand" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "fmt" - "io/ioutil" - "math/big" - "net" - "net/http" - "net/http/httptest" - "os" - "strings" - "time" -) - -var ( - // TLSBundleCA is the CA PEM - TLSBundleCA []byte - - // TLSBundleCert is the Server PEM - TLSBundleCert []byte - - // TLSBundleKey is the Server private key PEM - TLSBundleKey []byte - - // ClientTLSCert is the Client PEM - ClientTLSCert []byte - - // ClientTLSKey is the Client private key PEM - ClientTLSKey []byte -) - -func init() { - caPEM, _, caCert, caPrivKey, err := generateRootCA() - if err != nil { - panic("failed to generate testing root CA, " + err.Error()) - } - TLSBundleCA = caPEM - - serverCertPEM, serverCertPrivKeyPEM, err := generateLocalCert(caCert, caPrivKey) - if err != nil { - panic("failed to generate testing server cert, " + err.Error()) - } - TLSBundleCert = serverCertPEM - TLSBundleKey = serverCertPrivKeyPEM - - clientCertPEM, clientCertPrivKeyPEM, err := generateLocalCert(caCert, caPrivKey) - if err != nil { - panic("failed to generate testing client cert, " + err.Error()) - } - ClientTLSCert = clientCertPEM - ClientTLSKey = clientCertPrivKeyPEM -} - -func generateRootCA() ( - caPEM, caPrivKeyPEM []byte, caCert *x509.Certificate, caPrivKey *rsa.PrivateKey, err error, -) { - caCert = &x509.Certificate{ - SerialNumber: big.NewInt(42), - Subject: pkix.Name{ - Country: []string{"US"}, - Organization: []string{"AWS SDK for Go Test Certificate"}, - CommonName: "Test Root CA", - }, - NotBefore: time.Now().Add(-time.Minute), - NotAfter: time.Now().AddDate(1, 0, 0), - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - x509.ExtKeyUsageServerAuth, - }, - BasicConstraintsValid: true, - IsCA: true, - } - - // Create CA private and public key - caPrivKey, err = rsa.GenerateKey(rand.Reader, 4096) - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed generate CA RSA key, %w", err) - } - - // Create CA certificate - caBytes, err := x509.CreateCertificate(rand.Reader, caCert, caCert, &caPrivKey.PublicKey, caPrivKey) - if err != nil { - return nil, nil, nil, nil, fmt.Errorf("failed generate CA certificate, %w", err) - } - - // PEM encode CA certificate and private key - var caPEMBuf bytes.Buffer - pem.Encode(&caPEMBuf, &pem.Block{ - Type: "CERTIFICATE", - Bytes: caBytes, - }) - - var caPrivKeyPEMBuf bytes.Buffer - pem.Encode(&caPrivKeyPEMBuf, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), - }) - - return caPEMBuf.Bytes(), caPrivKeyPEMBuf.Bytes(), caCert, caPrivKey, nil -} - -func generateLocalCert(parentCert *x509.Certificate, parentPrivKey *rsa.PrivateKey) ( - certPEM, certPrivKeyPEM []byte, err error, -) { - cert := &x509.Certificate{ - SerialNumber: big.NewInt(42), - Subject: pkix.Name{ - Country: []string{"US"}, - Organization: []string{"AWS SDK for Go Test Certificate"}, - CommonName: "Test Root CA", - }, - IPAddresses: []net.IP{ - net.IPv4(127, 0, 0, 1), - net.IPv6loopback, - }, - NotBefore: time.Now().Add(-time.Minute), - NotAfter: time.Now().AddDate(1, 0, 0), - ExtKeyUsage: []x509.ExtKeyUsage{ - x509.ExtKeyUsageClientAuth, - x509.ExtKeyUsageServerAuth, - }, - KeyUsage: x509.KeyUsageDigitalSignature, - } - - // Create server private and public key - certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate server RSA private key, %w", err) - } - - // Create server certificate - certBytes, err := x509.CreateCertificate(rand.Reader, cert, parentCert, &certPrivKey.PublicKey, parentPrivKey) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate server certificate, %w", err) - } - - // PEM encode certificate and private key - var certPEMBuf bytes.Buffer - pem.Encode(&certPEMBuf, &pem.Block{ - Type: "CERTIFICATE", - Bytes: certBytes, - }) - - var certPrivKeyPEMBuf bytes.Buffer - pem.Encode(&certPrivKeyPEMBuf, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), - }) - - return certPEMBuf.Bytes(), certPrivKeyPEMBuf.Bytes(), nil -} - -// NewTLSClientCertServer creates a new HTTP test server initialize to require -// HTTP clients authenticate with TLS client certificates. -func NewTLSClientCertServer(handler http.Handler) (*httptest.Server, error) { - server := httptest.NewUnstartedServer(handler) - - if server.TLS == nil { - server.TLS = &tls.Config{} - } - server.TLS.ClientAuth = tls.RequireAndVerifyClientCert - - if server.TLS.ClientCAs == nil { - server.TLS.ClientCAs = x509.NewCertPool() - } - certPem := append(ClientTLSCert, ClientTLSKey...) - if ok := server.TLS.ClientCAs.AppendCertsFromPEM(certPem); !ok { - return nil, fmt.Errorf("failed to append client certs") - } - - return server, nil -} - -// CreateClientTLSCertFiles returns a set of temporary files for the client -// certificate and key files. -func CreateClientTLSCertFiles() (cert, key string, err error) { - cert, err = createTmpFile(ClientTLSCert) - if err != nil { - return "", "", err - } - - key, err = createTmpFile(ClientTLSKey) - if err != nil { - return "", "", err - } - - return cert, key, nil -} - -func availableLocalAddr(ip string) (v string, err error) { - l, err := net.Listen("tcp", ip+":0") - if err != nil { - return "", err - } - defer func() { - closeErr := l.Close() - if err == nil { - err = closeErr - } else if closeErr != nil { - err = fmt.Errorf("ip listener close error: %v, original error: %w", closeErr, err) - } - }() - - return l.Addr().String(), nil -} - -// CreateTLSServer will create the TLS server on an open port using the -// certificate and key. The address will be returned that the server is running on. -func CreateTLSServer(cert, key string, mux *http.ServeMux) (string, error) { - addr, err := availableLocalAddr("127.0.0.1") - if err != nil { - return "", err - } - - if mux == nil { - mux = http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) - } - - go func() { - if err := http.ListenAndServeTLS(addr, cert, key, mux); err != nil { - panic(err) - } - }() - - for i := 0; i < 60; i++ { - if _, err := http.Get("https://" + addr); err != nil && !strings.Contains(err.Error(), "connection refused") { - break - } - - time.Sleep(1 * time.Second) - } - - return "https://" + addr, nil -} - -// CreateTLSBundleFiles returns the temporary filenames for the certificate -// key, and CA PEM content. These files should be deleted when no longer -// needed. CleanupTLSBundleFiles can be used for this cleanup. -func CreateTLSBundleFiles() (cert, key, ca string, err error) { - cert, err = createTmpFile(TLSBundleCert) - if err != nil { - return "", "", "", err - } - - key, err = createTmpFile(TLSBundleKey) - if err != nil { - return "", "", "", err - } - - ca, err = createTmpFile(TLSBundleCA) - if err != nil { - return "", "", "", err - } - - return cert, key, ca, nil -} - -// CleanupTLSBundleFiles takes variadic list of files to be deleted. -func CleanupTLSBundleFiles(files ...string) error { - for _, file := range files { - if err := os.Remove(file); err != nil { - return err - } - } - - return nil -} - -func createTmpFile(b []byte) (string, error) { - bundleFile, err := ioutil.TempFile(os.TempDir(), "aws-sdk-go-session-test") - if err != nil { - return "", err - } - - _, err = bundleFile.Write(b) - if err != nil { - return "", err - } - - defer bundleFile.Close() - return bundleFile.Name(), nil -} diff --git a/aws/internal/awstesting/discard.go b/aws/internal/awstesting/discard.go deleted file mode 100644 index 866dfca..0000000 --- a/aws/internal/awstesting/discard.go +++ /dev/null @@ -1,11 +0,0 @@ -package awstesting - -// DiscardAt is an io.WriteAt that discards -// the requested bytes to be written -type DiscardAt struct{} - -// WriteAt discards the given []byte slice and returns len(p) bytes -// as having been written at the given offset. It will never return an error. -func (d DiscardAt) WriteAt(p []byte, off int64) (n int, err error) { - return len(p), nil -} diff --git a/aws/internal/awstesting/endless_reader.go b/aws/internal/awstesting/endless_reader.go deleted file mode 100644 index da1b429..0000000 --- a/aws/internal/awstesting/endless_reader.go +++ /dev/null @@ -1,12 +0,0 @@ -package awstesting - -// EndlessReader is an io.Reader that will always return -// that bytes have been read. -type EndlessReader struct{} - -// Read will report that it has read len(p) bytes in p. -// The content in the []byte will be unmodified. -// This will never return an error. -func (e EndlessReader) Read(p []byte) (int, error) { - return len(p), nil -} diff --git a/aws/internal/awstesting/sandbox/Dockerfile.golang-tip b/aws/internal/awstesting/sandbox/Dockerfile.golang-tip deleted file mode 100644 index e610d6e..0000000 --- a/aws/internal/awstesting/sandbox/Dockerfile.golang-tip +++ /dev/null @@ -1,43 +0,0 @@ -# Based on docker-library's golang 1.6 alpine and wheezy docker files. -# https://github.com/docker-library/golang/blob/master/1.6/alpine/Dockerfile -# https://github.com/docker-library/golang/blob/master/1.6/wheezy/Dockerfile -FROM buildpack-deps:buster-scm - -ENV GOLANG_SRC_REPO_URL https://github.com/golang/go - -# as of 1.20 Go 1.17 is required to bootstrap -# see https://github.com/golang/go/issues/44505 -ENV GOLANG_BOOTSTRAP_URL https://go.dev/dl/go1.17.13.linux-amd64.tar.gz -ENV GOLANG_BOOTSTRAP_SHA256 4cdd2bc664724dc7db94ad51b503512c5ae7220951cac568120f64f8e94399fc -ENV GOLANG_BOOTSTRAP_PATH /usr/local/bootstrap - -# gcc for cgo -RUN apt-get update && apt-get install -y --no-install-recommends \ - g++ \ - gcc \ - libc6-dev \ - make \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Setup the Bootstrap -RUN mkdir -p "$GOLANG_BOOTSTRAP_PATH" \ - && curl -fsSL "$GOLANG_BOOTSTRAP_URL" -o golang.tar.gz \ - && echo "$GOLANG_BOOTSTRAP_SHA256 golang.tar.gz" | sha256sum -c - \ - && tar -C "$GOLANG_BOOTSTRAP_PATH" -xzf golang.tar.gz \ - && rm golang.tar.gz - -# Get and build Go tip -RUN export GOROOT_BOOTSTRAP=$GOLANG_BOOTSTRAP_PATH/go \ - && git clone "$GOLANG_SRC_REPO_URL" /usr/local/go \ - && cd /usr/local/go/src \ - && ./make.bash \ - && rm -rf "$GOLANG_BOOTSTRAP_PATH" /usr/local/go/pkg/bootstrap - -# Build Go workspace and environment -ENV GOPATH /go -ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH -RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" \ - && chmod -R 777 "$GOPATH" - -WORKDIR $GOPATH diff --git a/aws/internal/awstesting/sandbox/Dockerfile.test.gotip b/aws/internal/awstesting/sandbox/Dockerfile.test.gotip deleted file mode 100644 index d15207f..0000000 --- a/aws/internal/awstesting/sandbox/Dockerfile.test.gotip +++ /dev/null @@ -1,16 +0,0 @@ -FROM aws-golang:tip - -ENV GOPROXY=direct - -RUN apt-get update && apt-get install -y --no-install-recommends \ - software-properties-common \ - && wget -O- https://apt.corretto.aws/corretto.key | apt-key add - \ - && add-apt-repository 'deb https://apt.corretto.aws stable main' \ - && apt-get update && apt-get install -y --no-install-recommends \ - vim \ - java-17-amazon-corretto-jdk \ - && rm -rf /var/list/apt/lists/* - -ADD . /go/src/github.com/aws/aws-sdk-go-v2 -WORKDIR /go/src/github.com/aws/aws-sdk-go-v2 -CMD ["make", "unit"] diff --git a/aws/internal/awstesting/sandbox/Dockerfile.test.goversion b/aws/internal/awstesting/sandbox/Dockerfile.test.goversion deleted file mode 100644 index de06ecc..0000000 --- a/aws/internal/awstesting/sandbox/Dockerfile.test.goversion +++ /dev/null @@ -1,18 +0,0 @@ -ARG GO_VERSION -FROM golang:${GO_VERSION} - -ENV GOPROXY=direct - -RUN apt-get update && apt-get install -y --no-install-recommends \ - software-properties-common \ - && wget -O- https://apt.corretto.aws/corretto.key | apt-key add - \ - && add-apt-repository 'deb https://apt.corretto.aws stable main' \ - && apt-get update && apt-get install -y --no-install-recommends \ - vim \ - java-17-amazon-corretto-jdk \ - && rm -rf /var/list/apt/lists/* - -ADD . /go/src/github.com/aws/aws-sdk-go-v2 - -WORKDIR /go/src/github.com/aws/aws-sdk-go-v2 -CMD ["make", "unit"] diff --git a/aws/internal/awstesting/util.go b/aws/internal/awstesting/util.go deleted file mode 100644 index 6a04e49..0000000 --- a/aws/internal/awstesting/util.go +++ /dev/null @@ -1,201 +0,0 @@ -package awstesting - -import ( - "context" - "io" - "net/http" - "os" - "runtime" - "strings" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" -) - -// ZeroReader is a io.Reader which will always write zeros to the byte slice provided. -type ZeroReader struct{} - -// Read fills the provided byte slice with zeros returning the number of bytes written. -func (r *ZeroReader) Read(b []byte) (int, error) { - for i := 0; i < len(b); i++ { - b[i] = 0 - } - return len(b), nil -} - -// ReadCloser is a io.ReadCloser for unit testing. -// Designed to test for leaks and whether a handle has -// been closed -type ReadCloser struct { - Size int - Closed bool - set bool - FillData func(bool, []byte, int, int) -} - -// Read will call FillData and fill it with whatever data needed. -// Decrements the size until zero, then return io.EOF. -func (r *ReadCloser) Read(b []byte) (int, error) { - if r.Closed { - return 0, io.EOF - } - - delta := len(b) - if delta > r.Size { - delta = r.Size - } - r.Size -= delta - - for i := 0; i < delta; i++ { - b[i] = 'a' - } - - if r.FillData != nil { - r.FillData(r.set, b, r.Size, delta) - } - r.set = true - - if r.Size > 0 { - return delta, nil - } - return delta, io.EOF -} - -// Close sets Closed to true and returns no error -func (r *ReadCloser) Close() error { - r.Closed = true - return nil -} - -// A FakeContext provides a simple stub implementation of a Context -type FakeContext struct { - Error error - DoneCh chan struct{} -} - -// Deadline always will return not set -func (c *FakeContext) Deadline() (deadline time.Time, ok bool) { - return time.Time{}, false -} - -// Done returns a read channel for listening to the Done event -func (c *FakeContext) Done() <-chan struct{} { - return c.DoneCh -} - -// Err returns the error, is nil if not set. -func (c *FakeContext) Err() error { - return c.Error -} - -// Value ignores the Value and always returns nil -func (c *FakeContext) Value(key interface{}) interface{} { - return nil -} - -// StashEnv stashes the current environment variables except variables listed in envToKeepx -// Returns an function to pop out old environment -func StashEnv(envToKeep ...string) []string { - if runtime.GOOS == "windows" { - envToKeep = append(envToKeep, "ComSpec") - envToKeep = append(envToKeep, "SYSTEM32") - envToKeep = append(envToKeep, "SYSTEMROOT") - } - envToKeep = append(envToKeep, "PATH", "HOME", "USERPROFILE") - extraEnv := getEnvs(envToKeep) - originalEnv := os.Environ() - os.Clearenv() // clear env - for key, val := range extraEnv { - os.Setenv(key, val) - } - return originalEnv -} - -// PopEnv takes the list of the environment values and injects them into the -// process's environment variable data. Clears any existing environment values -// that may already exist. -func PopEnv(env []string) { - os.Clearenv() - - for _, e := range env { - p := strings.SplitN(e, "=", 2) - k, v := p[0], "" - if len(p) > 1 { - v = p[1] - } - os.Setenv(k, v) - } -} - -// MockCredentialsProvider is a type that can be used to mock out credentials -// providers -type MockCredentialsProvider struct { - RetrieveFn func(ctx context.Context) (aws.Credentials, error) - InvalidateFn func() -} - -// Retrieve calls the RetrieveFn -func (p MockCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) { - return p.RetrieveFn(ctx) -} - -// Invalidate calls the InvalidateFn -func (p MockCredentialsProvider) Invalidate() { - p.InvalidateFn() -} - -func getEnvs(envs []string) map[string]string { - extraEnvs := make(map[string]string) - for _, env := range envs { - if val, ok := os.LookupEnv(env); ok && len(val) > 0 { - extraEnvs[env] = val - } - } - return extraEnvs -} - -const ( - signaturePreambleSigV4 = "AWS4-HMAC-SHA256" - signaturePreambleSigV4A = "AWS4-ECDSA-P256-SHA256" -) - -// SigV4Signature represents a parsed sigv4 or sigv4a signature. -type SigV4Signature struct { - Preamble string // e.g. AWS4-HMAC-SHA256, AWS4-ECDSA-P256-SHA256 - SigningName string // generally the service name e.g. "s3" - SigningRegion string // for sigv4a this is the region-set header as-is - SignedHeaders []string // list of signed headers - Signature string // calculated signature -} - -// ParseSigV4Signature deconstructs a sigv4 or sigv4a signature from a set of -// request headers. -func ParseSigV4Signature(header http.Header) *SigV4Signature { - auth := header.Get("Authorization") - - preamble, after, _ := strings.Cut(auth, " ") - credential, after, _ := strings.Cut(after, ", ") - signedHeaders, signature, _ := strings.Cut(after, ", ") - - credentialParts := strings.Split(credential, "/") - - // sigv4 : AccessKeyID/DateString/SigningRegion/SigningName/SignatureID - // sigv4a : AccessKeyID/DateString/SigningName/SignatureID, region set on - // header - var signingName, signingRegion string - if preamble == signaturePreambleSigV4 { - signingName = credentialParts[3] - signingRegion = credentialParts[2] - } else if preamble == signaturePreambleSigV4A { - signingName = credentialParts[2] - signingRegion = header.Get("X-Amz-Region-Set") - } - - return &SigV4Signature{ - Preamble: preamble, - SigningName: signingName, - SigningRegion: signingRegion, - SignedHeaders: strings.Split(signedHeaders, ";"), - Signature: signature, - } -} diff --git a/aws/internal/awstesting/util_test.go b/aws/internal/awstesting/util_test.go deleted file mode 100644 index a226c97..0000000 --- a/aws/internal/awstesting/util_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package awstesting_test - -import ( - "io" - "testing" - - "github.com/versity/versitygw/aws/internal/awstesting" -) - -func TestReadCloserClose(t *testing.T) { - rc := awstesting.ReadCloser{Size: 1} - err := rc.Close() - - if err != nil { - t.Errorf("expect nil, got %v", err) - } - if !rc.Closed { - t.Errorf("expect closed, was not") - } - if e, a := rc.Size, 1; e != a { - t.Errorf("expect %v, got %v", e, a) - } -} - -func TestReadCloserRead(t *testing.T) { - rc := awstesting.ReadCloser{Size: 5} - b := make([]byte, 2) - - n, err := rc.Read(b) - - if err != nil { - t.Errorf("expect nil, got %v", err) - } - if e, a := n, 2; e != a { - t.Errorf("expect %v, got %v", e, a) - } - if rc.Closed { - t.Errorf("expect not to be closed") - } - if e, a := rc.Size, 3; e != a { - t.Errorf("expect %v, got %v", e, a) - } - - err = rc.Close() - if err != nil { - t.Errorf("expect nil, got %v", err) - } - n, err = rc.Read(b) - if e, a := err, io.EOF; e != a { - t.Errorf("expect %v, got %v", e, a) - } - if e, a := n, 0; e != a { - t.Errorf("expect %v, got %v", e, a) - } -} - -func TestReadCloserReadAll(t *testing.T) { - rc := awstesting.ReadCloser{Size: 5} - b := make([]byte, 5) - - n, err := rc.Read(b) - - if e, a := err, io.EOF; e != a { - t.Errorf("expect %v, got %v", e, a) - } - if e, a := n, 5; e != a { - t.Errorf("expect %v, got %v", e, a) - } - if rc.Closed { - t.Errorf("expect not to be closed") - } - if e, a := rc.Size, 0; e != a { - t.Errorf("expect %v, got %v", e, a) - } -} diff --git a/aws/internal/sdk/interfaces.go b/aws/internal/sdk/interfaces.go deleted file mode 100644 index 2b42cbe..0000000 --- a/aws/internal/sdk/interfaces.go +++ /dev/null @@ -1,9 +0,0 @@ -package sdk - -// Invalidator provides access to a type's invalidate method to make it -// invalidate it cache. -// -// e.g aws.SafeCredentialsProvider's Invalidate method. -type Invalidator interface { - Invalidate() -} diff --git a/aws/internal/sdk/time.go b/aws/internal/sdk/time.go deleted file mode 100644 index 8e8daba..0000000 --- a/aws/internal/sdk/time.go +++ /dev/null @@ -1,74 +0,0 @@ -package sdk - -import ( - "context" - "time" -) - -func init() { - NowTime = time.Now - Sleep = time.Sleep - SleepWithContext = sleepWithContext -} - -// NowTime is a value for getting the current time. This value can be overridden -// for testing mocking out current time. -var NowTime func() time.Time - -// Sleep is a value for sleeping for a duration. This value can be overridden -// for testing and mocking out sleep duration. -var Sleep func(time.Duration) - -// SleepWithContext will wait for the timer duration to expire, or the context -// is canceled. Which ever happens first. If the context is canceled the Context's -// error will be returned. -// -// This value can be overridden for testing and mocking out sleep duration. -var SleepWithContext func(context.Context, time.Duration) error - -// sleepWithContext will wait for the timer duration to expire, or the context -// is canceled. Which ever happens first. If the context is canceled the -// Context's error will be returned. -func sleepWithContext(ctx context.Context, dur time.Duration) error { - t := time.NewTimer(dur) - defer t.Stop() - - select { - case <-t.C: - break - case <-ctx.Done(): - return ctx.Err() - } - - return nil -} - -// noOpSleepWithContext does nothing, returns immediately. -func noOpSleepWithContext(context.Context, time.Duration) error { - return nil -} - -func noOpSleep(time.Duration) {} - -// TestingUseNopSleep is a utility for disabling sleep across the SDK for -// testing. -func TestingUseNopSleep() func() { - SleepWithContext = noOpSleepWithContext - Sleep = noOpSleep - - return func() { - SleepWithContext = sleepWithContext - Sleep = time.Sleep - } -} - -// TestingUseReferenceTime is a utility for swapping the time function across the SDK to return a specific reference time -// for testing purposes. -func TestingUseReferenceTime(referenceTime time.Time) func() { - NowTime = func() time.Time { - return referenceTime - } - return func() { - NowTime = time.Now - } -} diff --git a/aws/internal/sdk/time_test.go b/aws/internal/sdk/time_test.go deleted file mode 100644 index aabcd55..0000000 --- a/aws/internal/sdk/time_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package sdk - -import ( - "context" - "strings" - "testing" - "time" -) - -func TestSleepWithContext(t *testing.T) { - ctx, cancelFn := context.WithCancel(context.Background()) - defer cancelFn() - - err := sleepWithContext(ctx, 1*time.Millisecond) - if err != nil { - t.Errorf("expect context to not be canceled, got %v", err) - } -} - -func TestSleepWithContext_Canceled(t *testing.T) { - ctx, cancelFn := context.WithCancel(context.Background()) - cancelFn() - - err := sleepWithContext(ctx, 10*time.Second) - if err == nil { - t.Fatalf("expect error, did not get one") - } - - if e, a := "context canceled", err.Error(); !strings.Contains(a, e) { - t.Errorf("expect %v error, got %v", e, a) - } -} diff --git a/aws/internal/strings/strings.go b/aws/internal/strings/strings.go deleted file mode 100644 index d008ae2..0000000 --- a/aws/internal/strings/strings.go +++ /dev/null @@ -1,11 +0,0 @@ -package strings - -import ( - "strings" -) - -// HasPrefixFold tests whether the string s begins with prefix, interpreted as UTF-8 strings, -// under Unicode case-folding. -func HasPrefixFold(s, prefix string) bool { - return len(s) >= len(prefix) && strings.EqualFold(s[0:len(prefix)], prefix) -} diff --git a/aws/internal/strings/strings_test.go b/aws/internal/strings/strings_test.go deleted file mode 100644 index 7469b40..0000000 --- a/aws/internal/strings/strings_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package strings - -import ( - "strings" - "testing" -) - -func TestHasPrefixFold(t *testing.T) { - type args struct { - s string - prefix string - } - tests := map[string]struct { - args args - want bool - }{ - "empty strings and prefix": { - args: args{ - s: "", - prefix: "", - }, - want: true, - }, - "strings starts with prefix": { - args: args{ - s: "some string", - prefix: "some", - }, - want: true, - }, - "prefix longer then string": { - args: args{ - s: "some", - prefix: "some string", - }, - }, - "equal length string and prefix": { - args: args{ - s: "short string", - prefix: "short string", - }, - want: true, - }, - "different cases": { - args: args{ - s: "ShOrT StRING", - prefix: "short", - }, - want: true, - }, - "empty prefix not empty string": { - args: args{ - s: "ShOrT StRING", - prefix: "", - }, - want: true, - }, - "mixed-case prefixes": { - args: args{ - s: "SoMe String", - prefix: "sOme", - }, - want: true, - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - if got := HasPrefixFold(tt.args.s, tt.args.prefix); got != tt.want { - t.Errorf("HasPrefixFold() = %v, want %v", got, tt.want) - } - }) - } -} - -func BenchmarkHasPrefixFold(b *testing.B) { - HasPrefixFold("SoME string", "sOmE") -} - -func BenchmarkHasPrefix(b *testing.B) { - strings.HasPrefix(strings.ToLower("SoME string"), strings.ToLower("sOmE")) -} diff --git a/aws/signer/internal/v4/header_rules.go b/aws/signer/internal/v4/header_rules.go index 28750d4..5d931d1 100644 --- a/aws/signer/internal/v4/header_rules.go +++ b/aws/signer/internal/v4/header_rules.go @@ -1,7 +1,7 @@ package v4 import ( - sdkstrings "github.com/versity/versitygw/aws/internal/strings" + "strings" ) // Rules houses a set of Rule needed for validation of a @@ -61,7 +61,7 @@ type Patterns []string // been found func (p Patterns) IsValid(value string) bool { for _, pattern := range p { - if sdkstrings.HasPrefixFold(value, pattern) { + if hasPrefixFold(value, pattern) { return true } } @@ -80,3 +80,9 @@ func (r InclusiveRules) IsValid(value string) bool { } return true } + +// hasPrefixFold tests whether the string s begins with prefix, interpreted as UTF-8 strings, +// under Unicode case-folding. +func hasPrefixFold(s, prefix string) bool { + return len(s) >= len(prefix) && strings.EqualFold(s[0:len(prefix)], prefix) +} diff --git a/aws/signer/v4/middleware.go b/aws/signer/v4/middleware.go deleted file mode 100644 index cbb4673..0000000 --- a/aws/signer/v4/middleware.go +++ /dev/null @@ -1,443 +0,0 @@ -package v4 - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "io" - "net/http" - "strings" - - "github.com/aws/aws-sdk-go-v2/aws" - awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" - "github.com/aws/aws-sdk-go-v2/aws/middleware/private/metrics" - "github.com/aws/smithy-go/middleware" - smithyhttp "github.com/aws/smithy-go/transport/http" - internalauth "github.com/versity/versitygw/aws/internal/auth" - "github.com/versity/versitygw/aws/internal/sdk" - v4Internal "github.com/versity/versitygw/aws/signer/internal/v4" -) - -const computePayloadHashMiddlewareID = "ComputePayloadHash" - -// HashComputationError indicates an error occurred while computing the signing hash -type HashComputationError struct { - Err error -} - -// Error is the error message -func (e *HashComputationError) Error() string { - return fmt.Sprintf("failed to compute payload hash: %v", e.Err) -} - -// Unwrap returns the underlying error if one is set -func (e *HashComputationError) Unwrap() error { - return e.Err -} - -// SigningError indicates an error condition occurred while performing SigV4 signing -type SigningError struct { - Err error -} - -func (e *SigningError) Error() string { - return fmt.Sprintf("failed to sign request: %v", e.Err) -} - -// Unwrap returns the underlying error cause -func (e *SigningError) Unwrap() error { - return e.Err -} - -// UseDynamicPayloadSigningMiddleware swaps the compute payload sha256 middleware with a resolver middleware that -// switches between unsigned and signed payload based on TLS state for request. -// This middleware should not be used for AWS APIs that do not support unsigned payload signing auth. -// By default, SDK uses this middleware for known AWS APIs that support such TLS based auth selection . -// -// Usage example - -// S3 PutObject API allows unsigned payload signing auth usage when TLS is enabled, and uses this middleware to -// dynamically switch between unsigned and signed payload based on TLS state for request. -func UseDynamicPayloadSigningMiddleware(stack *middleware.Stack) error { - _, err := stack.Finalize.Swap(computePayloadHashMiddlewareID, &dynamicPayloadSigningMiddleware{}) - return err -} - -// dynamicPayloadSigningMiddleware dynamically resolves the middleware that computes and set payload sha256 middleware. -type dynamicPayloadSigningMiddleware struct { -} - -// ID returns the resolver identifier -func (m *dynamicPayloadSigningMiddleware) ID() string { - return computePayloadHashMiddlewareID -} - -// HandleFinalize delegates SHA256 computation according to whether the request -// is TLS-enabled. -func (m *dynamicPayloadSigningMiddleware) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - req, ok := in.Request.(*smithyhttp.Request) - if !ok { - return out, metadata, fmt.Errorf("unknown transport type %T", in.Request) - } - - if req.IsHTTPS() { - return (&UnsignedPayload{}).HandleFinalize(ctx, in, next) - } - return (&ComputePayloadSHA256{}).HandleFinalize(ctx, in, next) -} - -// UnsignedPayload sets the SigV4 request payload hash to unsigned. -// -// Will not set the Unsigned Payload magic SHA value, if a SHA has already been -// stored in the context. (e.g. application pre-computed SHA256 before making -// API call). -// -// This middleware does not check the X-Amz-Content-Sha256 header, if that -// header is serialized a middleware must translate it into the context. -type UnsignedPayload struct{} - -// AddUnsignedPayloadMiddleware adds unsignedPayload to the operation -// middleware stack -func AddUnsignedPayloadMiddleware(stack *middleware.Stack) error { - return stack.Finalize.Insert(&UnsignedPayload{}, "ResolveEndpointV2", middleware.After) -} - -// ID returns the unsignedPayload identifier -func (m *UnsignedPayload) ID() string { - return computePayloadHashMiddlewareID -} - -// HandleFinalize sets the payload hash magic value to the unsigned sentinel. -func (m *UnsignedPayload) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - if GetPayloadHash(ctx) == "" { - ctx = SetPayloadHash(ctx, v4Internal.UnsignedPayload) - } - return next.HandleFinalize(ctx, in) -} - -// ComputePayloadSHA256 computes SHA256 payload hash to sign. -// -// Will not set the Unsigned Payload magic SHA value, if a SHA has already been -// stored in the context. (e.g. application pre-computed SHA256 before making -// API call). -// -// This middleware does not check the X-Amz-Content-Sha256 header, if that -// header is serialized a middleware must translate it into the context. -type ComputePayloadSHA256 struct{} - -// AddComputePayloadSHA256Middleware adds computePayloadSHA256 to the -// operation middleware stack -func AddComputePayloadSHA256Middleware(stack *middleware.Stack) error { - return stack.Finalize.Insert(&ComputePayloadSHA256{}, "ResolveEndpointV2", middleware.After) -} - -// RemoveComputePayloadSHA256Middleware removes computePayloadSHA256 from the -// operation middleware stack -func RemoveComputePayloadSHA256Middleware(stack *middleware.Stack) error { - _, err := stack.Finalize.Remove(computePayloadHashMiddlewareID) - return err -} - -// ID is the middleware name -func (m *ComputePayloadSHA256) ID() string { - return computePayloadHashMiddlewareID -} - -// HandleFinalize computes the payload hash for the request, storing it to the -// context. This is a no-op if a caller has previously set that value. -func (m *ComputePayloadSHA256) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - if GetPayloadHash(ctx) != "" { - return next.HandleFinalize(ctx, in) - } - - req, ok := in.Request.(*smithyhttp.Request) - if !ok { - return out, metadata, &HashComputationError{ - Err: fmt.Errorf("unexpected request middleware type %T", in.Request), - } - } - - hash := sha256.New() - if stream := req.GetStream(); stream != nil { - _, err = io.Copy(hash, stream) - if err != nil { - return out, metadata, &HashComputationError{ - Err: fmt.Errorf("failed to compute payload hash, %w", err), - } - } - - if err := req.RewindStream(); err != nil { - return out, metadata, &HashComputationError{ - Err: fmt.Errorf("failed to seek body to start, %w", err), - } - } - } - - ctx = SetPayloadHash(ctx, hex.EncodeToString(hash.Sum(nil))) - - return next.HandleFinalize(ctx, in) -} - -// SwapComputePayloadSHA256ForUnsignedPayloadMiddleware replaces the -// ComputePayloadSHA256 middleware with the UnsignedPayload middleware. -// -// Use this to disable computing the Payload SHA256 checksum and instead use -// UNSIGNED-PAYLOAD for the SHA256 value. -func SwapComputePayloadSHA256ForUnsignedPayloadMiddleware(stack *middleware.Stack) error { - _, err := stack.Finalize.Swap(computePayloadHashMiddlewareID, &UnsignedPayload{}) - return err -} - -// ContentSHA256Header sets the X-Amz-Content-Sha256 header value to -// the Payload hash stored in the context. -type ContentSHA256Header struct{} - -// AddContentSHA256HeaderMiddleware adds ContentSHA256Header to the -// operation middleware stack -func AddContentSHA256HeaderMiddleware(stack *middleware.Stack) error { - return stack.Finalize.Insert(&ContentSHA256Header{}, computePayloadHashMiddlewareID, middleware.After) -} - -// RemoveContentSHA256HeaderMiddleware removes contentSHA256Header middleware -// from the operation middleware stack -func RemoveContentSHA256HeaderMiddleware(stack *middleware.Stack) error { - _, err := stack.Finalize.Remove((*ContentSHA256Header)(nil).ID()) - return err -} - -// ID returns the ContentSHA256HeaderMiddleware identifier -func (m *ContentSHA256Header) ID() string { - return "SigV4ContentSHA256Header" -} - -// HandleFinalize sets the X-Amz-Content-Sha256 header value to the Payload hash -// stored in the context. -func (m *ContentSHA256Header) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - req, ok := in.Request.(*smithyhttp.Request) - if !ok { - return out, metadata, &HashComputationError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)} - } - - req.Header.Set(v4Internal.ContentSHAKey, GetPayloadHash(ctx)) - return next.HandleFinalize(ctx, in) -} - -// SignHTTPRequestMiddlewareOptions is the configuration options for -// [SignHTTPRequestMiddleware]. -// -// Deprecated: [SignHTTPRequestMiddleware] is deprecated. -type SignHTTPRequestMiddlewareOptions struct { - CredentialsProvider aws.CredentialsProvider - Signer HTTPSigner - LogSigning bool -} - -// SignHTTPRequestMiddleware is a `FinalizeMiddleware` implementation for SigV4 -// HTTP Signing. -// -// Deprecated: AWS service clients no longer use this middleware. Signing as an -// SDK operation is now performed through an internal per-service middleware -// which opaquely selects and uses the signer from the resolved auth scheme. -type SignHTTPRequestMiddleware struct { - credentialsProvider aws.CredentialsProvider - signer HTTPSigner - logSigning bool -} - -// NewSignHTTPRequestMiddleware constructs a [SignHTTPRequestMiddleware] using -// the given [Signer] for signing requests. -// -// Deprecated: SignHTTPRequestMiddleware is deprecated. -func NewSignHTTPRequestMiddleware(options SignHTTPRequestMiddlewareOptions) *SignHTTPRequestMiddleware { - return &SignHTTPRequestMiddleware{ - credentialsProvider: options.CredentialsProvider, - signer: options.Signer, - logSigning: options.LogSigning, - } -} - -// ID is the SignHTTPRequestMiddleware identifier. -// -// Deprecated: SignHTTPRequestMiddleware is deprecated. -func (s *SignHTTPRequestMiddleware) ID() string { - return "Signing" -} - -// HandleFinalize will take the provided input and sign the request using the -// SigV4 authentication scheme. -// -// Deprecated: SignHTTPRequestMiddleware is deprecated. -func (s *SignHTTPRequestMiddleware) HandleFinalize(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - if !haveCredentialProvider(s.credentialsProvider) { - return next.HandleFinalize(ctx, in) - } - - req, ok := in.Request.(*smithyhttp.Request) - if !ok { - return out, metadata, &SigningError{Err: fmt.Errorf("unexpected request middleware type %T", in.Request)} - } - - signingName, signingRegion := awsmiddleware.GetSigningName(ctx), awsmiddleware.GetSigningRegion(ctx) - payloadHash := GetPayloadHash(ctx) - if len(payloadHash) == 0 { - return out, metadata, &SigningError{Err: fmt.Errorf("computed payload hash missing from context")} - } - - mctx := metrics.Context(ctx) - - if mctx != nil { - if attempt, err := mctx.Data().LatestAttempt(); err == nil { - attempt.CredentialFetchStartTime = sdk.NowTime() - } - } - - credentials, err := s.credentialsProvider.Retrieve(ctx) - - if mctx != nil { - if attempt, err := mctx.Data().LatestAttempt(); err == nil { - attempt.CredentialFetchEndTime = sdk.NowTime() - } - } - - if err != nil { - return out, metadata, &SigningError{Err: fmt.Errorf("failed to retrieve credentials: %w", err)} - } - - signerOptions := []func(o *SignerOptions){ - func(o *SignerOptions) { - o.Logger = middleware.GetLogger(ctx) - o.LogSigning = s.logSigning - }, - } - - // existing DisableURIPathEscaping is equivalent in purpose - // to authentication scheme property DisableDoubleEncoding - disableDoubleEncoding, overridden := internalauth.GetDisableDoubleEncoding(ctx) - if overridden { - signerOptions = append(signerOptions, func(o *SignerOptions) { - o.DisableURIPathEscaping = disableDoubleEncoding - }) - } - - if mctx != nil { - if attempt, err := mctx.Data().LatestAttempt(); err == nil { - attempt.SignStartTime = sdk.NowTime() - } - } - - err = s.signer.SignHTTP(ctx, credentials, req.Request, payloadHash, signingName, signingRegion, sdk.NowTime(), signerOptions...) - - if mctx != nil { - if attempt, err := mctx.Data().LatestAttempt(); err == nil { - attempt.SignEndTime = sdk.NowTime() - } - } - - if err != nil { - return out, metadata, &SigningError{Err: fmt.Errorf("failed to sign http request, %w", err)} - } - - ctx = awsmiddleware.SetSigningCredentials(ctx, credentials) - - return next.HandleFinalize(ctx, in) -} - -// StreamingEventsPayload signs input event stream messages. -type StreamingEventsPayload struct{} - -// AddStreamingEventsPayload adds the streamingEventsPayload middleware to the stack. -func AddStreamingEventsPayload(stack *middleware.Stack) error { - return stack.Finalize.Add(&StreamingEventsPayload{}, middleware.Before) -} - -// ID identifies the middleware. -func (s *StreamingEventsPayload) ID() string { - return computePayloadHashMiddlewareID -} - -// HandleFinalize marks the input stream to be signed with SigV4. -func (s *StreamingEventsPayload) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - contentSHA := GetPayloadHash(ctx) - if len(contentSHA) == 0 { - contentSHA = v4Internal.StreamingEventsPayload - } - - ctx = SetPayloadHash(ctx, contentSHA) - - return next.HandleFinalize(ctx, in) -} - -// GetSignedRequestSignature attempts to extract the signature of the request. -// Returning an error if the request is unsigned, or unable to extract the -// signature. -func GetSignedRequestSignature(r *http.Request) ([]byte, error) { - const authHeaderSignatureElem = "Signature=" - - if auth := r.Header.Get(authorizationHeader); len(auth) != 0 { - ps := strings.Split(auth, ", ") - for _, p := range ps { - if idx := strings.Index(p, authHeaderSignatureElem); idx >= 0 { - sig := p[len(authHeaderSignatureElem):] - if len(sig) == 0 { - return nil, fmt.Errorf("invalid request signature authorization header") - } - return hex.DecodeString(sig) - } - } - } - - if sig := r.URL.Query().Get("X-Amz-Signature"); len(sig) != 0 { - return hex.DecodeString(sig) - } - - return nil, fmt.Errorf("request not signed") -} - -func haveCredentialProvider(p aws.CredentialsProvider) bool { - if p == nil { - return false - } - - return !aws.IsCredentialsProvider(p, (*aws.AnonymousCredentials)(nil)) -} - -type payloadHashKey struct{} - -// GetPayloadHash retrieves the payload hash to use for signing -// -// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues -// to clear all stack values. -func GetPayloadHash(ctx context.Context) (v string) { - v, _ = middleware.GetStackValue(ctx, payloadHashKey{}).(string) - return v -} - -// SetPayloadHash sets the payload hash to be used for signing the request -// -// Scoped to stack values. Use github.com/aws/smithy-go/middleware#ClearStackValues -// to clear all stack values. -func SetPayloadHash(ctx context.Context, hash string) context.Context { - return middleware.WithStackValue(ctx, payloadHashKey{}, hash) -} diff --git a/aws/signer/v4/middleware_test.go b/aws/signer/v4/middleware_test.go deleted file mode 100644 index ccb9cba..0000000 --- a/aws/signer/v4/middleware_test.go +++ /dev/null @@ -1,415 +0,0 @@ -package v4 - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" - "github.com/aws/smithy-go/logging" - "github.com/aws/smithy-go/middleware" - smithyhttp "github.com/aws/smithy-go/transport/http" - "github.com/google/go-cmp/cmp" - "github.com/versity/versitygw/aws/internal/awstesting/unit" -) - -func TestComputePayloadHashMiddleware(t *testing.T) { - cases := []struct { - content io.Reader - expectedHash string - expectedErr interface{} - }{ - 0: { - content: func() io.Reader { - br := bytes.NewReader([]byte("some content")) - return br - }(), - expectedHash: "290f493c44f5d63d06b374d0a5abd292fae38b92cab2fae5efefe1b0e9347f56", - }, - 1: { - content: func() io.Reader { - return &nonSeeker{} - }(), - expectedErr: &HashComputationError{}, - }, - 2: { - content: func() io.Reader { - return &semiSeekable{} - }(), - expectedErr: &HashComputationError{}, - }, - } - - for i, tt := range cases { - t.Run(strconv.Itoa(i), func(t *testing.T) { - c := &ComputePayloadSHA256{} - - next := middleware.FinalizeHandlerFunc(func(ctx context.Context, in middleware.FinalizeInput) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) { - value := GetPayloadHash(ctx) - if len(value) == 0 { - t.Fatalf("expected payload hash value to be on context") - } - if e, a := tt.expectedHash, value; e != a { - t.Errorf("expected %v, got %v", e, a) - } - - return out, metadata, err - }) - - stream, err := smithyhttp.NewStackRequest().(*smithyhttp.Request).SetStream(tt.content) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - _, _, err = c.HandleFinalize(context.Background(), middleware.FinalizeInput{Request: stream}, next) - if err != nil && tt.expectedErr == nil { - t.Errorf("expected no error, got %v", err) - } else if err != nil && tt.expectedErr != nil { - e, a := tt.expectedErr, err - if !errors.As(a, &e) { - t.Errorf("expected error type %T, got %T", e, a) - } - } else if err == nil && tt.expectedErr != nil { - t.Errorf("expected error, got nil") - } - }) - } -} - -type httpSignerFunc func(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*SignerOptions)) error - -func (f httpSignerFunc) SignHTTP(ctx context.Context, credentials aws.Credentials, r *http.Request, payloadHash string, service string, region string, signingTime time.Time, optFns ...func(*SignerOptions)) error { - return f(ctx, credentials, r, payloadHash, service, region, signingTime, optFns...) -} - -func TestSignHTTPRequestMiddleware(t *testing.T) { - cases := map[string]struct { - creds aws.CredentialsProvider - hash string - logSigning bool - expectedErr interface{} - }{ - "success": { - creds: unit.StubCredentialsProvider{}, - hash: "0123456789abcdef", - }, - "error": { - creds: unit.StubCredentialsProvider{}, - hash: "", - expectedErr: &SigningError{}, - }, - "anonymous creds": { - creds: aws.AnonymousCredentials{}, - }, - "nil creds": { - creds: nil, - }, - "with log signing": { - creds: unit.StubCredentialsProvider{}, - hash: "0123456789abcdef", - logSigning: true, - }, - } - - const ( - signingName = "serviceId" - signingRegion = "regionName" - ) - - for name, tt := range cases { - t.Run(name, func(t *testing.T) { - c := &SignHTTPRequestMiddleware{ - credentialsProvider: tt.creds, - signer: httpSignerFunc( - func(ctx context.Context, - credentials aws.Credentials, r *http.Request, payloadHash string, - service string, region string, signingTime time.Time, - optFns ...func(*SignerOptions), - ) error { - var options SignerOptions - for _, fn := range optFns { - fn(&options) - } - if options.Logger == nil { - t.Errorf("expect logger, got none") - } - if options.LogSigning { - options.Logger.Logf(logging.Debug, t.Name()) - } - - expectCreds, _ := unit.StubCredentialsProvider{}.Retrieve(context.Background()) - if e, a := expectCreds, credentials; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := tt.hash, payloadHash; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := signingName, service; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := signingRegion, region; e != a { - t.Errorf("expected %v, got %v", e, a) - } - return nil - }), - logSigning: tt.logSigning, - } - - next := middleware.FinalizeHandlerFunc(func(ctx context.Context, in middleware.FinalizeInput) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) { - return out, metadata, err - }) - - ctx := awsmiddleware.SetSigningRegion( - awsmiddleware.SetSigningName(context.Background(), signingName), - signingRegion) - - var loggerBuf bytes.Buffer - logger := logging.NewStandardLogger(&loggerBuf) - ctx = middleware.SetLogger(ctx, logger) - - if len(tt.hash) != 0 { - ctx = SetPayloadHash(ctx, tt.hash) - } - - _, _, err := c.HandleFinalize(ctx, middleware.FinalizeInput{ - Request: &smithyhttp.Request{Request: &http.Request{}}, - }, next) - if err != nil && tt.expectedErr == nil { - t.Errorf("expected no error, got %v", err) - } else if err != nil && tt.expectedErr != nil { - e, a := tt.expectedErr, err - if !errors.As(a, &e) { - t.Errorf("expected error type %T, got %T", e, a) - } - } else if err == nil && tt.expectedErr != nil { - t.Errorf("expected error, got nil") - } - - if tt.logSigning { - if e, a := t.Name(), loggerBuf.String(); !strings.Contains(a, e) { - t.Errorf("expect %v logged in %v", e, a) - } - } else { - if loggerBuf.Len() != 0 { - t.Errorf("expect no log, got %v", loggerBuf.String()) - } - } - }) - } -} - -func TestSwapComputePayloadSHA256ForUnsignedPayloadMiddleware(t *testing.T) { - cases := map[string]struct { - InitStep func(*middleware.Stack) error - Mutator func(*middleware.Stack) error - ExpectErr string - ExpectIDs []string - }{ - "swap in place": { - InitStep: func(s *middleware.Stack) (err error) { - err = s.Finalize.Add(middleware.FinalizeMiddlewareFunc("before", nil), middleware.After) - if err != nil { - return err - } - err = AddComputePayloadSHA256Middleware(s) - if err != nil { - return err - } - err = s.Finalize.Add(middleware.FinalizeMiddlewareFunc("after", nil), middleware.After) - if err != nil { - return err - } - return nil - }, - Mutator: SwapComputePayloadSHA256ForUnsignedPayloadMiddleware, - ExpectIDs: []string{ - "ResolveEndpointV2", - computePayloadHashMiddlewareID, // should snap to after resolve endpoint - "before", - "after", - }, - }, - - "already unsigned payload exists": { - InitStep: func(s *middleware.Stack) (err error) { - err = s.Finalize.Add(middleware.FinalizeMiddlewareFunc("before", nil), middleware.After) - if err != nil { - return err - } - err = AddUnsignedPayloadMiddleware(s) - if err != nil { - return err - } - err = s.Finalize.Add(middleware.FinalizeMiddlewareFunc("after", nil), middleware.After) - if err != nil { - return err - } - return nil - }, - Mutator: SwapComputePayloadSHA256ForUnsignedPayloadMiddleware, - ExpectIDs: []string{ - "ResolveEndpointV2", - computePayloadHashMiddlewareID, - "before", - "after", - }, - }, - - "no compute payload": { - InitStep: func(s *middleware.Stack) (err error) { - err = s.Finalize.Add(middleware.FinalizeMiddlewareFunc("before", nil), middleware.After) - if err != nil { - return err - } - err = s.Finalize.Add(middleware.FinalizeMiddlewareFunc("after", nil), middleware.After) - if err != nil { - return err - } - return nil - }, - Mutator: SwapComputePayloadSHA256ForUnsignedPayloadMiddleware, - ExpectErr: "not found, " + computePayloadHashMiddlewareID, - }, - } - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - stack := middleware.NewStack(t.Name(), smithyhttp.NewStackRequest) - stack.Finalize.Add(&nopResolveEndpoint{}, middleware.After) - if err := c.InitStep(stack); err != nil { - t.Fatalf("expect no error, got %v", err) - } - - err := c.Mutator(stack) - if len(c.ExpectErr) != 0 { - if err == nil { - t.Fatalf("expect error, got none") - } - if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { - t.Fatalf("expect error to contain %v, got %v", e, a) - } - return - } - if err != nil { - t.Fatalf("expect no error, got %v", err) - } - - if diff := cmp.Diff(c.ExpectIDs, stack.Finalize.List()); len(diff) != 0 { - t.Errorf("expect match\n%v", diff) - } - }) - } -} - -func TestUseDynamicPayloadSigningMiddleware(t *testing.T) { - cases := map[string]struct { - content io.Reader - url string - expectedHash string - expectedErr interface{} - }{ - "TLS disabled": { - content: func() io.Reader { - br := bytes.NewReader([]byte("some content")) - return br - }(), - url: "http://localhost.com/", - expectedHash: "290f493c44f5d63d06b374d0a5abd292fae38b92cab2fae5efefe1b0e9347f56", - }, - "TLS enabled": { - content: func() io.Reader { - br := bytes.NewReader([]byte("some content")) - return br - }(), - url: "https://localhost.com/", - expectedHash: "UNSIGNED-PAYLOAD", - }, - } - - for name, tt := range cases { - t.Run(name, func(t *testing.T) { - c := &dynamicPayloadSigningMiddleware{} - - next := middleware.FinalizeHandlerFunc(func(ctx context.Context, in middleware.FinalizeInput) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) { - value := GetPayloadHash(ctx) - if len(value) == 0 { - t.Fatalf("expected payload hash value to be on context") - } - if e, a := tt.expectedHash, value; e != a { - t.Errorf("expected %v, got %v", e, a) - } - - return out, metadata, err - }) - - req := smithyhttp.NewStackRequest().(*smithyhttp.Request) - req.URL, _ = url.Parse(tt.url) - stream, err := req.SetStream(tt.content) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - _, _, err = c.HandleFinalize(context.Background(), middleware.FinalizeInput{Request: stream}, next) - if err != nil && tt.expectedErr == nil { - t.Errorf("expected no error, got %v", err) - } else if err != nil && tt.expectedErr != nil { - e, a := tt.expectedErr, err - if !errors.As(a, &e) { - t.Errorf("expected error type %T, got %T", e, a) - } - } else if err == nil && tt.expectedErr != nil { - t.Errorf("expected error, got nil") - } - }) - } -} - -type nonSeeker struct{} - -func (nonSeeker) Read(p []byte) (n int, err error) { - return 0, io.EOF -} - -type semiSeekable struct { - hasSeeked bool -} - -func (s *semiSeekable) Seek(offset int64, whence int) (int64, error) { - if !s.hasSeeked { - s.hasSeeked = true - return 0, nil - } - return 0, fmt.Errorf("io seek error") -} - -func (*semiSeekable) Read(p []byte) (n int, err error) { - return 0, io.EOF -} - -type nopResolveEndpoint struct{} - -func (*nopResolveEndpoint) ID() string { return "ResolveEndpointV2" } - -func (*nopResolveEndpoint) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - return out, metadata, err -} - -var ( - _ middleware.FinalizeMiddleware = &UnsignedPayload{} - _ middleware.FinalizeMiddleware = &ComputePayloadSHA256{} - _ middleware.FinalizeMiddleware = &ContentSHA256Header{} - _ middleware.FinalizeMiddleware = &SignHTTPRequestMiddleware{} -) diff --git a/aws/signer/v4/presign_middleware.go b/aws/signer/v4/presign_middleware.go deleted file mode 100644 index e936287..0000000 --- a/aws/signer/v4/presign_middleware.go +++ /dev/null @@ -1,127 +0,0 @@ -package v4 - -import ( - "context" - "fmt" - "net/http" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" - "github.com/aws/smithy-go/middleware" - smithyHTTP "github.com/aws/smithy-go/transport/http" - "github.com/versity/versitygw/aws/internal/sdk" -) - -// HTTPPresigner is an interface to a SigV4 signer that can sign create a -// presigned URL for a HTTP requests. -type HTTPPresigner interface { - PresignHTTP( - ctx context.Context, credentials aws.Credentials, r *http.Request, - payloadHash string, service string, region string, signingTime time.Time, - optFns ...func(*SignerOptions), - ) (url string, signedHeader http.Header, err error) -} - -// PresignedHTTPRequest provides the URL and signed headers that are included -// in the presigned URL. -type PresignedHTTPRequest struct { - URL string - Method string - SignedHeader http.Header -} - -// PresignHTTPRequestMiddlewareOptions is the options for the PresignHTTPRequestMiddleware middleware. -type PresignHTTPRequestMiddlewareOptions struct { - CredentialsProvider aws.CredentialsProvider - Presigner HTTPPresigner - LogSigning bool -} - -// PresignHTTPRequestMiddleware provides the Finalize middleware for creating a -// presigned URL for an HTTP request. -// -// Will short circuit the middleware stack and not forward onto the next -// Finalize handler. -type PresignHTTPRequestMiddleware struct { - credentialsProvider aws.CredentialsProvider - presigner HTTPPresigner - logSigning bool -} - -// NewPresignHTTPRequestMiddleware returns a new PresignHTTPRequestMiddleware -// initialized with the presigner. -func NewPresignHTTPRequestMiddleware(options PresignHTTPRequestMiddlewareOptions) *PresignHTTPRequestMiddleware { - return &PresignHTTPRequestMiddleware{ - credentialsProvider: options.CredentialsProvider, - presigner: options.Presigner, - logSigning: options.LogSigning, - } -} - -// ID provides the middleware ID. -func (*PresignHTTPRequestMiddleware) ID() string { return "PresignHTTPRequest" } - -// HandleFinalize will take the provided input and create a presigned url for -// the http request using the SigV4 presign authentication scheme. -// -// Since the signed request is not a valid HTTP request -func (s *PresignHTTPRequestMiddleware) HandleFinalize( - ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler, -) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, -) { - req, ok := in.Request.(*smithyHTTP.Request) - if !ok { - return out, metadata, &SigningError{ - Err: fmt.Errorf("unexpected request middleware type %T", in.Request), - } - } - - httpReq := req.Build(ctx) - if !haveCredentialProvider(s.credentialsProvider) { - out.Result = &PresignedHTTPRequest{ - URL: httpReq.URL.String(), - Method: httpReq.Method, - SignedHeader: http.Header{}, - } - - return out, metadata, nil - } - - signingName := awsmiddleware.GetSigningName(ctx) - signingRegion := awsmiddleware.GetSigningRegion(ctx) - payloadHash := GetPayloadHash(ctx) - if len(payloadHash) == 0 { - return out, metadata, &SigningError{ - Err: fmt.Errorf("computed payload hash missing from context"), - } - } - - credentials, err := s.credentialsProvider.Retrieve(ctx) - if err != nil { - return out, metadata, &SigningError{ - Err: fmt.Errorf("failed to retrieve credentials: %w", err), - } - } - - u, h, err := s.presigner.PresignHTTP(ctx, credentials, - httpReq, payloadHash, signingName, signingRegion, sdk.NowTime(), - func(o *SignerOptions) { - o.Logger = middleware.GetLogger(ctx) - o.LogSigning = s.logSigning - }) - if err != nil { - return out, metadata, &SigningError{ - Err: fmt.Errorf("failed to sign http request, %w", err), - } - } - - out.Result = &PresignedHTTPRequest{ - URL: u, - Method: httpReq.Method, - SignedHeader: h, - } - - return out, metadata, nil -} diff --git a/aws/signer/v4/presign_middleware_test.go b/aws/signer/v4/presign_middleware_test.go deleted file mode 100644 index 90ad9b3..0000000 --- a/aws/signer/v4/presign_middleware_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package v4 - -import ( - "bytes" - "context" - "net/http" - "net/url" - "strings" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" - "github.com/aws/smithy-go/logging" - "github.com/aws/smithy-go/middleware" - smithyhttp "github.com/aws/smithy-go/transport/http" - "github.com/google/go-cmp/cmp" - "github.com/versity/versitygw/aws/internal/awstesting/unit" -) - -type httpPresignerFunc func( - ctx context.Context, credentials aws.Credentials, r *http.Request, - payloadHash string, service string, region string, signingTime time.Time, - optFns ...func(*SignerOptions), -) (url string, signedHeader http.Header, err error) - -func (f httpPresignerFunc) PresignHTTP( - ctx context.Context, credentials aws.Credentials, r *http.Request, - payloadHash string, service string, region string, signingTime time.Time, - optFns ...func(*SignerOptions), -) ( - url string, signedHeader http.Header, err error, -) { - return f(ctx, credentials, r, payloadHash, service, region, signingTime, optFns...) -} - -func TestPresignHTTPRequestMiddleware(t *testing.T) { - cases := map[string]struct { - Request *http.Request - Creds aws.CredentialsProvider - PayloadHash string - LogSigning bool - ExpectResult *PresignedHTTPRequest - ExpectErr string - }{ - "success": { - Request: &http.Request{ - URL: func() *url.URL { - u, _ := url.Parse("https://example.aws/path?query=foo") - return u - }(), - Header: http.Header{}, - }, - Creds: unit.StubCredentialsProvider{}, - PayloadHash: "0123456789abcdef", - ExpectResult: &PresignedHTTPRequest{ - URL: "https://example.aws/path?query=foo", - SignedHeader: http.Header{}, - }, - }, - "error": { - Request: func() *http.Request { - return &http.Request{} - }(), - Creds: unit.StubCredentialsProvider{}, - PayloadHash: "", - ExpectErr: "failed to sign request", - }, - "anonymous creds": { - Request: &http.Request{ - URL: func() *url.URL { - u, _ := url.Parse("https://example.aws/path?query=foo") - return u - }(), - Header: http.Header{}, - }, - Creds: unit.StubCredentialsProvider{}, - PayloadHash: "", - ExpectErr: "failed to sign request", - ExpectResult: &PresignedHTTPRequest{ - URL: "https://example.aws/path?query=foo", - SignedHeader: http.Header{}, - }, - }, - "nil creds": { - Request: &http.Request{ - URL: func() *url.URL { - u, _ := url.Parse("https://example.aws/path?query=foo") - return u - }(), - Header: http.Header{}, - }, - Creds: nil, - ExpectResult: &PresignedHTTPRequest{ - URL: "https://example.aws/path?query=foo", - SignedHeader: http.Header{}, - }, - }, - "with log signing": { - Request: &http.Request{ - URL: func() *url.URL { - u, _ := url.Parse("https://example.aws/path?query=foo") - return u - }(), - Header: http.Header{}, - }, - Creds: unit.StubCredentialsProvider{}, - PayloadHash: "0123456789abcdef", - ExpectResult: &PresignedHTTPRequest{ - URL: "https://example.aws/path?query=foo", - SignedHeader: http.Header{}, - }, - - LogSigning: true, - }, - } - - const ( - signingName = "serviceId" - signingRegion = "regionName" - ) - - for name, c := range cases { - t.Run(name, func(t *testing.T) { - m := &PresignHTTPRequestMiddleware{ - credentialsProvider: c.Creds, - - presigner: httpPresignerFunc(func( - ctx context.Context, credentials aws.Credentials, r *http.Request, - payloadHash string, service string, region string, signingTime time.Time, - optFns ...func(*SignerOptions), - ) (url string, signedHeader http.Header, err error) { - var options SignerOptions - for _, fn := range optFns { - fn(&options) - } - if options.Logger == nil { - t.Errorf("expect logger, got none") - } - if options.LogSigning { - options.Logger.Logf(logging.Debug, t.Name()) - } - - if !haveCredentialProvider(c.Creds) { - t.Errorf("expect presigner not to be called for not credentials provider") - } - - expectCreds, _ := unit.StubCredentialsProvider{}.Retrieve(context.Background()) - if e, a := expectCreds, credentials; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := c.PayloadHash, payloadHash; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := signingName, service; e != a { - t.Errorf("expected %v, got %v", e, a) - } - if e, a := signingRegion, region; e != a { - t.Errorf("expected %v, got %v", e, a) - } - - return c.ExpectResult.URL, c.ExpectResult.SignedHeader, nil - }), - logSigning: c.LogSigning, - } - - next := middleware.FinalizeHandlerFunc( - func(ctx context.Context, in middleware.FinalizeInput) ( - out middleware.FinalizeOutput, metadata middleware.Metadata, err error, - ) { - t.Errorf("expect next handler not to be called") - return out, metadata, err - }) - - ctx := awsmiddleware.SetSigningRegion( - awsmiddleware.SetSigningName(context.Background(), signingName), - signingRegion) - - var loggerBuf bytes.Buffer - logger := logging.NewStandardLogger(&loggerBuf) - ctx = middleware.SetLogger(ctx, logger) - - if len(c.PayloadHash) != 0 { - ctx = SetPayloadHash(ctx, c.PayloadHash) - } - - result, _, err := m.HandleFinalize(ctx, middleware.FinalizeInput{ - Request: &smithyhttp.Request{ - Request: c.Request, - }, - }, next) - if len(c.ExpectErr) != 0 { - if err == nil { - t.Fatalf("expect error, got none") - } - if e, a := c.ExpectErr, err.Error(); !strings.Contains(a, e) { - t.Fatalf("expect error to contain %v, got %v", e, a) - } - return - } - if err != nil { - t.Fatalf("expect no error, got %v", err) - } - - if diff := cmp.Diff(c.ExpectResult, result.Result); len(diff) != 0 { - t.Errorf("expect result match\n%v", diff) - } - - if c.LogSigning { - if e, a := t.Name(), loggerBuf.String(); !strings.Contains(a, e) { - t.Errorf("expect %v logged in %v", e, a) - } - } else { - if loggerBuf.Len() != 0 { - t.Errorf("expect no log, got %v", loggerBuf.String()) - } - } - }) - } -} - -var ( - _ middleware.FinalizeMiddleware = &PresignHTTPRequestMiddleware{} -) diff --git a/aws/signer/v4/stream.go b/aws/signer/v4/stream.go deleted file mode 100644 index 6441252..0000000 --- a/aws/signer/v4/stream.go +++ /dev/null @@ -1,87 +0,0 @@ -package v4 - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "strings" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - v4Internal "github.com/versity/versitygw/aws/signer/internal/v4" -) - -// EventStreamSigner is an AWS EventStream protocol signer. -type EventStreamSigner interface { - GetSignature(ctx context.Context, headers, payload []byte, signingTime time.Time, optFns ...func(*StreamSignerOptions)) ([]byte, error) -} - -// StreamSignerOptions is the configuration options for StreamSigner. -type StreamSignerOptions struct{} - -// StreamSigner implements Signature Version 4 (SigV4) signing of event stream encoded payloads. -type StreamSigner struct { - options StreamSignerOptions - - credentials aws.Credentials - service string - region string - - prevSignature []byte - - signingKeyDeriver *v4Internal.SigningKeyDeriver -} - -// NewStreamSigner returns a new AWS EventStream protocol signer. -func NewStreamSigner(credentials aws.Credentials, service, region string, seedSignature []byte, optFns ...func(*StreamSignerOptions)) *StreamSigner { - o := StreamSignerOptions{} - - for _, fn := range optFns { - fn(&o) - } - - return &StreamSigner{ - options: o, - credentials: credentials, - service: service, - region: region, - signingKeyDeriver: v4Internal.NewSigningKeyDeriver(), - prevSignature: seedSignature, - } -} - -// GetSignature signs the provided header and payload bytes. -func (s *StreamSigner) GetSignature(ctx context.Context, headers, payload []byte, signingTime time.Time, optFns ...func(*StreamSignerOptions)) ([]byte, error) { - options := s.options - - for _, fn := range optFns { - fn(&options) - } - - prevSignature := s.prevSignature - - st := v4Internal.NewSigningTime(signingTime) - - sigKey := s.signingKeyDeriver.DeriveKey(s.credentials, s.service, s.region, st) - - scope := v4Internal.BuildCredentialScope(st, s.region, s.service) - - stringToSign := s.buildEventStreamStringToSign(headers, payload, prevSignature, scope, &st) - - signature := v4Internal.HMACSHA256(sigKey, []byte(stringToSign)) - s.prevSignature = signature - - return signature, nil -} - -func (s *StreamSigner) buildEventStreamStringToSign(headers, payload, previousSignature []byte, credentialScope string, signingTime *v4Internal.SigningTime) string { - hash := sha256.New() - return strings.Join([]string{ - "AWS4-HMAC-SHA256-PAYLOAD", - signingTime.TimeFormat(), - credentialScope, - hex.EncodeToString(previousSignature), - hex.EncodeToString(makeHash(hash, headers)), - hex.EncodeToString(makeHash(hash, payload)), - }, "\n") -} diff --git a/aws/signer/v4/v4_test.go b/aws/signer/v4/v4_test.go index fb5a318..fc188d5 100644 --- a/aws/signer/v4/v4_test.go +++ b/aws/signer/v4/v4_test.go @@ -7,7 +7,6 @@ import ( "encoding/hex" "fmt" "io" - "io/ioutil" "net/http" "net/url" "strings" @@ -208,7 +207,7 @@ func TestBuildCanonicalRequest(t *testing.T) { func TestSigner_SignHTTP_NoReplaceRequestBody(t *testing.T) { req, bodyHash := buildRequest("dynamodb", "us-east-1", "{}") - req.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) + req.Body = io.NopCloser(bytes.NewReader([]byte{})) s := NewSigner() diff --git a/aws/staticcheck.conf b/aws/staticcheck.conf deleted file mode 100644 index e7e254f..0000000 --- a/aws/staticcheck.conf +++ /dev/null @@ -1 +0,0 @@ -checks = []