diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index a52c1715..f9e377fe 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -17,9 +17,7 @@ package middlewares import ( "crypto/sha256" "encoding/hex" - "fmt" "io" - "net/http" "strconv" "time" @@ -52,9 +50,27 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, region string) return nil } + // Check X-Amz-Date header + date := ctx.Get("X-Amz-Date") + if date == "" { + return s3err.GetAPIError(s3err.ErrMissingDateHeader) + } + + // Parse the date and check the date validity + tdate, err := time.Parse(iso8601Format, date) + if err != nil { + return s3err.GetAPIError(s3err.ErrMissingDateHeader) + } + + // Validate the dates difference + err = utils.ValidateDate(tdate) + if err != nil { + return err + } + authorization := ctx.Get("Authorization") if authorization == "" { - return s3err.GetAPIError(s3err.ErrAuthHeaderEmpty) + return s3err.GetAPIError(s3err.ErrInvalidAuthHeader) } authData, err := utils.ParseAuthorization(authorization) @@ -63,11 +79,7 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, region string) } if authData.Region != region { - return s3err.APIError{ - Code: "SignatureDoesNotMatch", - Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", authData.Region), - HTTPStatusCode: http.StatusForbidden, - } + return s3err.MalformedAuth.IncorrectRegion(region, authData.Region) } utils.ContextKeyIsRoot.Set(ctx, authData.Access == root.Access) @@ -80,29 +92,11 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, region string) return err } - utils.ContextKeyAccount.Set(ctx, account) - - // Check X-Amz-Date header - date := ctx.Get("X-Amz-Date") - if date == "" { - return s3err.GetAPIError(s3err.ErrMissingDateHeader) - } - - // Parse the date and check the date validity - tdate, err := time.Parse(iso8601Format, date) - if err != nil { - return s3err.GetAPIError(s3err.ErrMalformedDate) - } - if date[:8] != authData.Date { - return s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch) + return s3err.MalformedAuth.DateMismatch() } - // Validate the dates difference - err = utils.ValidateDate(tdate) - if err != nil { - return err - } + utils.ContextKeyAccount.Set(ctx, account) var contentLength int64 contentLengthStr := ctx.Get("Content-Length") diff --git a/s3api/middlewares/presign-auth.go b/s3api/middlewares/presign-auth.go index 13a0fb22..dd6eacdb 100644 --- a/s3api/middlewares/presign-auth.go +++ b/s3api/middlewares/presign-auth.go @@ -32,10 +32,15 @@ func VerifyPresignedV4Signature(root RootUserConfig, iam auth.IAMService, region if utils.ContextKeyPublicBucket.IsSet(ctx) { return nil } - if ctx.Query("X-Amz-Signature") == "" { + if !utils.IsPresignedURLAuth(ctx) { return nil } + if ctx.Request().URI().QueryArgs().Has("X-Amz-Security-Token") { + // OIDC Authorization with X-Amz-Security-Token is not supported + return s3err.QueryAuthErrors.SecurityTokenNotSupported() + } + // Set in the context the "authenticated" key, in case the authentication succeeds, // otherwise the middleware will return the caucht error utils.ContextKeyAuthenticated.Set(ctx, true) diff --git a/s3api/middlewares/public-bucket.go b/s3api/middlewares/public-bucket.go index 11a62ecb..94fe6089 100644 --- a/s3api/middlewares/public-bucket.go +++ b/s3api/middlewares/public-bucket.go @@ -31,7 +31,7 @@ import ( func AuthorizePublicBucketAccess(be backend.Backend, s3action string, policyPermission auth.Action, permission auth.Permission) fiber.Handler { return func(ctx *fiber.Ctx) error { // skip for authenticated requests - if ctx.Query("X-Amz-Algorithm") != "" || ctx.Get("Authorization") != "" { + if utils.IsPresignedURLAuth(ctx) || ctx.Get("Authorization") != "" { return nil } diff --git a/s3api/utils/auth-reader.go b/s3api/utils/auth-reader.go index 9eaf3286..9b2cff08 100644 --- a/s3api/utils/auth-reader.go +++ b/s3api/utils/auth-reader.go @@ -103,7 +103,7 @@ func (ar *AuthReader) validateSignature() error { // Parse the date and check the date validity tdate, err := time.Parse(iso8601Format, date) if err != nil { - return s3err.GetAPIError(s3err.ErrMalformedDate) + return s3err.GetAPIError(s3err.ErrMissingDateHeader) } return CheckValidSignature(ar.ctx, ar.auth, ar.secret, hashPayload, tdate, int64(ar.size)) @@ -184,54 +184,61 @@ func ParseAuthorization(authorization string) (AuthData, error) { } if len(authParts) < 2 { - return a, s3err.GetAPIError(s3err.ErrMissingFields) + return a, s3err.GetAPIError(s3err.ErrInvalidAuthHeader) } algo := authParts[0] if algo != "AWS4-HMAC-SHA256" { - return a, s3err.GetAPIError(s3err.ErrSignatureVersionNotSupported) + return a, s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationType) } kvData := authParts[1] kvPairs := strings.Split(kvData, ",") // we are expecting at least Credential, SignedHeaders, and Signature // key value pairs here - if len(kvPairs) < 3 { - return a, s3err.GetAPIError(s3err.ErrMissingFields) + if len(kvPairs) != 3 { + return a, s3err.MalformedAuth.MissingComponents() } var access, region, signedHeaders, signature, date string - for _, kv := range kvPairs { + for i, kv := range kvPairs { keyValue := strings.Split(kv, "=") if len(keyValue) != 2 { - switch { - case strings.HasPrefix(kv, "Credential"): - return a, s3err.GetAPIError(s3err.ErrCredMalformed) - case strings.HasPrefix(kv, "SignedHeaders"): - return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams) - } - return a, s3err.GetAPIError(s3err.ErrMissingFields) + return a, s3err.MalformedAuth.MalformedComponent(kv) + } + key, value := keyValue[0], keyValue[1] + switch i { + case 0: + if key != "Credential" { + return a, s3err.MalformedAuth.MissingCredential() + } + case 1: + if key != "SignedHeaders" { + return a, s3err.MalformedAuth.MissingSignedHeaders() + } + case 2: + if key != "Signature" { + return a, s3err.MalformedAuth.MissingSignature() + } } - key := strings.TrimSpace(keyValue[0]) - value := strings.TrimSpace(keyValue[1]) switch key { case "Credential": creds := strings.Split(value, "/") if len(creds) != 5 { - return a, s3err.GetAPIError(s3err.ErrCredMalformed) + return a, s3err.MalformedAuth.MalformedCredential() } if creds[3] != "s3" { - return a, s3err.GetAPIError(s3err.ErrSignatureIncorrService) + return a, s3err.MalformedAuth.IncorrectService(creds[3]) } if creds[4] != "aws4_request" { - return a, s3err.GetAPIError(s3err.ErrSignatureTerminationStr) + return a, s3err.MalformedAuth.InvalidTerminal(creds[4]) } _, err := time.Parse(yyyymmdd, creds[1]) if err != nil { - return a, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch) + return a, s3err.MalformedAuth.InvalidDateFormat(creds[1]) } access = creds[0] date = creds[1] diff --git a/s3api/utils/presign-auth-reader.go b/s3api/utils/presign-auth-reader.go index ce18b51f..083b9a4b 100644 --- a/s3api/utils/presign-auth-reader.go +++ b/s3api/utils/presign-auth-reader.go @@ -18,7 +18,6 @@ import ( "errors" "fmt" "io" - "net/http" "net/url" "os" "strconv" @@ -35,6 +34,9 @@ import ( const ( unsignedPayload string = "UNSIGNED-PAYLOAD" + + algoHMAC string = "AWS4-HMAC-SHA256" + algoECDSA string = "AWS4-ECDSA-P256-SHA256" ) // PresignedAuthReader is an io.Reader that validates presigned request authorization @@ -136,65 +138,70 @@ func ParsePresignedURIParts(ctx *fiber.Ctx) (AuthData, error) { // Get and verify algorithm query parameter algo := ctx.Query("X-Amz-Algorithm") - if algo == "" { - return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams) - } - if algo != "AWS4-HMAC-SHA256" { - return a, s3err.GetAPIError(s3err.ErrInvalidQuerySignatureAlgo) + err := validateAlgorithm(algo) + if err != nil { + return a, err } // Parse and validate credentials query parameter credsQuery := ctx.Query("X-Amz-Credential") if credsQuery == "" { - return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams) + return a, s3err.QueryAuthErrors.MissingRequiredParams() } creds := strings.Split(credsQuery, "/") if len(creds) != 5 { - return a, s3err.GetAPIError(s3err.ErrCredMalformed) + return a, s3err.QueryAuthErrors.MalformedCredential() } + + // validate the service if creds[3] != "s3" { - return a, s3err.GetAPIError(s3err.ErrSignatureIncorrService) + return a, s3err.QueryAuthErrors.IncorrectService(creds[3]) } + + // validate the terminal if creds[4] != "aws4_request" { - return a, s3err.GetAPIError(s3err.ErrSignatureTerminationStr) + return a, s3err.QueryAuthErrors.IncorrectTerminal(creds[4]) } - _, err := time.Parse(yyyymmdd, creds[1]) + + // validate the date + _, err = time.Parse(yyyymmdd, creds[1]) if err != nil { - return a, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch) + return a, s3err.QueryAuthErrors.InvalidDateFormat(creds[1]) + } + + region, ok := ContextKeyRegion.Get(ctx).(string) + if !ok { + region = "" + } + // validate the region + if creds[2] != region { + return a, s3err.QueryAuthErrors.IncorrectRegion(region, creds[2]) } // Parse and validate Date query param date := ctx.Query("X-Amz-Date") if date == "" { - return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams) + return a, s3err.QueryAuthErrors.MissingRequiredParams() } tdate, err := time.Parse(iso8601Format, date) if err != nil { - return a, s3err.GetAPIError(s3err.ErrMalformedDate) + return a, s3err.QueryAuthErrors.InvalidXAmzDateFormat() } if date[:8] != creds[1] { - return a, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch) - } - - if ContextKeyRegion.Get(ctx) != creds[2] { - return a, s3err.APIError{ - Code: "SignatureDoesNotMatch", - Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", creds[2]), - HTTPStatusCode: http.StatusForbidden, - } + return a, s3err.QueryAuthErrors.DateMismatch(creds[1], date[:8]) } signature := ctx.Query("X-Amz-Signature") if signature == "" { - return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams) + return a, s3err.QueryAuthErrors.MissingRequiredParams() } signedHdrs := ctx.Query("X-Amz-SignedHeaders") if signedHdrs == "" { - return a, s3err.GetAPIError(s3err.ErrInvalidQueryParams) + return a, s3err.QueryAuthErrors.MissingRequiredParams() } // Validate X-Amz-Expires query param and check if request is expired @@ -215,20 +222,20 @@ func ParsePresignedURIParts(ctx *fiber.Ctx) (AuthData, error) { func validateExpiration(str string, date time.Time) error { if str == "" { - return s3err.GetAPIError(s3err.ErrInvalidQueryParams) + return s3err.QueryAuthErrors.MissingRequiredParams() } exp, err := strconv.Atoi(str) if err != nil { - return s3err.GetAPIError(s3err.ErrMalformedExpires) + return s3err.QueryAuthErrors.ExpiresNumber() } if exp < 0 { - return s3err.GetAPIError(s3err.ErrNegativeExpires) + return s3err.QueryAuthErrors.ExpiresNegative() } if exp > 604800 { - return s3err.GetAPIError(s3err.ErrMaximumExpires) + return s3err.QueryAuthErrors.ExpiresTooLarge() } now := time.Now() @@ -240,3 +247,43 @@ func validateExpiration(str string, date time.Time) error { return nil } + +// validateAlgorithm validates the algorithm +// for AWS4-ECDSA-P256-SHA256 it returns a custom non AWS error +// currently only AWS4-HMAC-SHA256 algorithm is supported +func validateAlgorithm(algo string) error { + switch algo { + case "": + return s3err.QueryAuthErrors.MissingRequiredParams() + case algoHMAC: + return nil + case algoECDSA: + return s3err.QueryAuthErrors.OnlyHMACSupported() + default: + // all other algorithms are considerd as invalid + return s3err.QueryAuthErrors.UnsupportedAlgorithm() + } +} + +// IsPresignedURLAuth determines if the request is presigned: +// which is authorization with query params +func IsPresignedURLAuth(ctx *fiber.Ctx) bool { + algo := ctx.Query("X-Amz-Algorithm") + creds := ctx.Query("X-Amz-Credential") + signature := ctx.Query("X-Amz-Signature") + signedHeaders := ctx.Query("X-Amz-SignedHeaders") + expires := ctx.Query("X-Amz-Expires") + + return !isEmpty(algo, creds, signature, signedHeaders, expires) +} + +// isEmpty checks if all the given strings are empty +func isEmpty(args ...string) bool { + for _, a := range args { + if a != "" { + return false + } + } + + return true +} diff --git a/s3api/utils/presign-auth-reader_test.go b/s3api/utils/presign-auth-reader_test.go index f4767b41..92571a23 100644 --- a/s3api/utils/presign-auth-reader_test.go +++ b/s3api/utils/presign-auth-reader_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/versity/versitygw/s3err" ) @@ -37,7 +38,7 @@ func Test_validateExpiration(t *testing.T) { str: "", date: time.Now(), }, - err: s3err.GetAPIError(s3err.ErrInvalidQueryParams), + err: s3err.QueryAuthErrors.MissingRequiredParams(), }, { name: "invalid-expiration", @@ -45,7 +46,7 @@ func Test_validateExpiration(t *testing.T) { str: "invalid_expiration", date: time.Now(), }, - err: s3err.GetAPIError(s3err.ErrMalformedExpires), + err: s3err.QueryAuthErrors.ExpiresNumber(), }, { name: "negative-expiration", @@ -53,7 +54,7 @@ func Test_validateExpiration(t *testing.T) { str: "-320", date: time.Now(), }, - err: s3err.GetAPIError(s3err.ErrNegativeExpires), + err: s3err.QueryAuthErrors.ExpiresNegative(), }, { name: "exceeding-expiration", @@ -61,7 +62,7 @@ func Test_validateExpiration(t *testing.T) { str: "6048000", date: time.Now(), }, - err: s3err.GetAPIError(s3err.ErrMaximumExpires), + err: s3err.QueryAuthErrors.ExpiresTooLarge(), }, { name: "expired value", @@ -98,3 +99,22 @@ func Test_validateExpiration(t *testing.T) { }) } } + +func Test_validateAlgorithm(t *testing.T) { + tests := []struct { + name string + algo string + err error + }{ + {"empty", "", s3err.QueryAuthErrors.MissingRequiredParams()}, + {"AWS4-HMAC-SHA256", "AWS4-HMAC-SHA256", nil}, + {"AWS4-ECDSA-P256-SHA256", "AWS4-ECDSA-P256-SHA256", s3err.QueryAuthErrors.OnlyHMACSupported()}, + {"invalid", "invalid algo", s3err.QueryAuthErrors.UnsupportedAlgorithm()}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateAlgorithm(tt.algo) + assert.EqualValues(t, tt.err, err) + }) + } +} diff --git a/s3err/presigned-urls.go b/s3err/presigned-urls.go new file mode 100644 index 00000000..788d01b0 --- /dev/null +++ b/s3err/presigned-urls.go @@ -0,0 +1,95 @@ +// Copyright 2023 Versity Software +// This file is licensed under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package s3err + +import ( + "fmt" + "net/http" +) + +// Factory for building AuthorizationQueryParametersError errors. +func authQueryParamError(format string, args ...any) APIError { + return APIError{ + Code: "AuthorizationQueryParametersError", + Description: fmt.Sprintf(format, args...), + HTTPStatusCode: http.StatusBadRequest, + } +} + +var QueryAuthErrors = struct { + UnsupportedAlgorithm func() APIError + MalformedCredential func() APIError + IncorrectService func(string) APIError + IncorrectRegion func(expected, actual string) APIError + IncorrectTerminal func(string) APIError + InvalidDateFormat func(string) APIError + DateMismatch func(expected, actual string) APIError + ExpiresTooLarge func() APIError + ExpiresNegative func() APIError + ExpiresNumber func() APIError + MissingRequiredParams func() APIError + InvalidXAmzDateFormat func() APIError + RequestNotYetValid func() APIError + RequestExpired func() APIError + InvalidAccessKeyId func() APIError + // a custom non-AWS error + OnlyHMACSupported func() APIError + SecurityTokenNotSupported func() APIError +}{ + + UnsupportedAlgorithm: func() APIError { + return authQueryParamError(`X-Amz-Algorithm only supports "AWS4-HMAC-SHA256 and AWS4-ECDSA-P256-SHA256"`) + }, + MalformedCredential: func() APIError { + return authQueryParamError(`Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting "/YYYYMMDD/REGION/SERVICE/aws4_request".`) + }, + IncorrectService: func(s string) APIError { + return authQueryParamError(`Error parsing the X-Amz-Credential parameter; incorrect service %q. This endpoint belongs to "s3".`, s) + }, + IncorrectRegion: func(expected, actual string) APIError { + return authQueryParamError(`Error parsing the X-Amz-Credential parameter; the region %q is wrong; expecting %q`, actual, expected) + }, + IncorrectTerminal: func(s string) APIError { + return authQueryParamError(`Error parsing the X-Amz-Credential parameter; incorrect terminal %q. This endpoint uses "aws4_request".`, s) + }, + InvalidDateFormat: func(s string) APIError { + return authQueryParamError(`Error parsing the X-Amz-Credential parameter; incorrect date format %q. This date in the credential must be in the format "yyyyMMdd".`, s) + }, + DateMismatch: func(expected, actual string) APIError { + return authQueryParamError(`Invalid credential date %q. This date is not the same as X-Amz-Date: %q.`, expected, actual) + }, + ExpiresTooLarge: func() APIError { + return authQueryParamError("X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds") + }, + ExpiresNegative: func() APIError { + return authQueryParamError("X-Amz-Expires must be non-negative") + }, + ExpiresNumber: func() APIError { + return authQueryParamError("X-Amz-Expires should be a number") + }, + MissingRequiredParams: func() APIError { + return authQueryParamError("Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.") + }, + InvalidXAmzDateFormat: func() APIError { + return authQueryParamError(`X-Amz-Date must be in the ISO8601 Long Format "yyyyMMdd'T'HHmmss'Z'"`) + }, + // a custom non-AWS error + OnlyHMACSupported: func() APIError { + return authQueryParamError("X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\"") + }, + SecurityTokenNotSupported: func() APIError { + return authQueryParamError("Authorization with X-Amz-Security-Token is not supported") + }, +} diff --git a/s3err/s3err.go b/s3err/s3err.go index ed65968b..85fb111f 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -98,8 +98,8 @@ const ( ErrBucketTaggingLimited ErrObjectTaggingLimited ErrInvalidURLEncodedTagging - ErrAuthHeaderEmpty - ErrSignatureVersionNotSupported + ErrInvalidAuthHeader + ErrUnsupportedAuthorizationType ErrMalformedPOSTRequest ErrPOSTFileRequired ErrPostPolicyConditionInvalidFormat @@ -107,24 +107,13 @@ const ( ErrEntityTooLarge ErrMissingFields ErrMissingCredTag - ErrCredMalformed ErrMalformedXML - ErrMalformedDate - ErrMalformedPresignedDate ErrMalformedCredentialDate ErrMissingSignHeadersTag ErrMissingSignTag ErrUnsignedHeaders - ErrInvalidQueryParams - ErrInvalidQuerySignatureAlgo ErrExpiredPresignRequest - ErrMalformedExpires - ErrNegativeExpires - ErrMaximumExpires ErrSignatureDoesNotMatch - ErrSignatureDateDoesNotMatch - ErrSignatureTerminationStr - ErrSignatureIncorrService ErrContentSHA256Mismatch ErrInvalidSHA256Paylod ErrMissingContentLength @@ -402,14 +391,14 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The XML you provided was not well-formed or did not validate against our published schema.", HTTPStatusCode: http.StatusBadRequest, }, - ErrAuthHeaderEmpty: { + ErrInvalidAuthHeader: { Code: "InvalidArgument", Description: "Authorization header is invalid -- one and only one ' ' (space) required.", HTTPStatusCode: http.StatusBadRequest, }, - ErrSignatureVersionNotSupported: { - Code: "InvalidRequest", - Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.", + ErrUnsupportedAuthorizationType: { + Code: "InvalidArgument", + Description: "Unsupported Authorization Type", HTTPStatusCode: http.StatusBadRequest, }, ErrMalformedPOSTRequest: { @@ -447,21 +436,6 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "Missing Credential field for this request.", HTTPStatusCode: http.StatusBadRequest, }, - ErrCredMalformed: { - Code: "AuthorizationQueryParametersError", - Description: "Error parsing the X-Amz-Credential parameter; the Credential is mal-formed; expecting \"/YYYYMMDD/REGION/SERVICE/aws4_request\".", - HTTPStatusCode: http.StatusBadRequest, - }, - ErrMalformedDate: { - Code: "MalformedDate", - Description: "Invalid date format header, expected to be in ISO8601, RFC1123 or RFC1123Z time format.", - HTTPStatusCode: http.StatusBadRequest, - }, - ErrMalformedPresignedDate: { - Code: "AuthorizationQueryParametersError", - Description: "X-Amz-Date must be in the ISO8601 Long Format \"yyyyMMdd'T'HHmmss'Z'\".", - HTTPStatusCode: http.StatusBadRequest, - }, ErrMissingSignHeadersTag: { Code: "InvalidArgument", Description: "Signature header missing SignedHeaders field.", @@ -477,36 +451,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "There were headers present in the request which were not signed.", HTTPStatusCode: http.StatusBadRequest, }, - ErrInvalidQueryParams: { - Code: "AuthorizationQueryParametersError", - Description: "Query-string authentication version 4 requires the X-Amz-Algorithm, X-Amz-Credential, X-Amz-Signature, X-Amz-Date, X-Amz-SignedHeaders, and X-Amz-Expires parameters.", - HTTPStatusCode: http.StatusBadRequest, - }, - ErrInvalidQuerySignatureAlgo: { - Code: "AuthorizationQueryParametersError", - Description: "X-Amz-Algorithm only supports \"AWS4-HMAC-SHA256\".", - HTTPStatusCode: http.StatusBadRequest, - }, ErrExpiredPresignRequest: { Code: "AccessDenied", Description: "Request has expired.", HTTPStatusCode: http.StatusForbidden, }, - ErrMalformedExpires: { - Code: "AuthorizationQueryParametersError", - Description: "X-Amz-Expires should be a number.", - HTTPStatusCode: http.StatusBadRequest, - }, - ErrNegativeExpires: { - Code: "AuthorizationQueryParametersError", - Description: "X-Amz-Expires must be non-negative.", - HTTPStatusCode: http.StatusBadRequest, - }, - ErrMaximumExpires: { - Code: "AuthorizationQueryParametersError", - Description: "X-Amz-Expires must be less than a week (in seconds); that is, the given X-Amz-Expires must be less than 604800 seconds.", - HTTPStatusCode: http.StatusBadRequest, - }, ErrInvalidAccessKeyID: { Code: "InvalidAccessKeyId", Description: "The AWS Access Key Id you provided does not exist in our records.", @@ -522,21 +471,6 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "The request signature we calculated does not match the signature you provided. Check your key and signing method.", HTTPStatusCode: http.StatusForbidden, }, - ErrSignatureDateDoesNotMatch: { - Code: "SignatureDoesNotMatch", - Description: "Date in Credential scope does not match YYYYMMDD from ISO-8601 version of date from HTTP.", - HTTPStatusCode: http.StatusForbidden, - }, - ErrSignatureTerminationStr: { - Code: "SignatureDoesNotMatch", - Description: "Credential should be scoped with a valid terminator: 'aws4_request'.", - HTTPStatusCode: http.StatusForbidden, - }, - ErrSignatureIncorrService: { - Code: "SignatureDoesNotMatch", - Description: "Credential should be scoped to correct service: s3.", - HTTPStatusCode: http.StatusForbidden, - }, ErrContentSHA256Mismatch: { Code: "XAmzContentSHA256Mismatch", Description: "The provided 'x-amz-content-sha256' header does not match what was computed.", @@ -659,7 +593,7 @@ var errorCodeResponse = map[ErrorCode]APIError{ }, ErrRequestTimeTooSkewed: { Code: "RequestTimeTooSkewed", - Description: "The difference between the request time and the server's time is too large.", + Description: "The difference between the request time and the current time is too large.", HTTPStatusCode: http.StatusForbidden, }, ErrInvalidBucketAclWithObjectOwnership: { diff --git a/s3err/sigv4.go b/s3err/sigv4.go new file mode 100644 index 00000000..e8aff841 --- /dev/null +++ b/s3err/sigv4.go @@ -0,0 +1,77 @@ +// Copyright 2023 Versity Software +// This file is licensed under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package s3err + +import ( + "fmt" + "net/http" +) + +// Factory for building AuthorizationHeaderMalformed errors. +func malformedAuthError(format string, args ...any) APIError { + return APIError{ + Code: "AuthorizationHeaderMalformed", + Description: fmt.Sprintf("The authorization header is malformed; %s", fmt.Sprintf(format, args...)), + HTTPStatusCode: http.StatusForbidden, + } +} + +var MalformedAuth = struct { + InvalidDateFormat func(string) APIError + MalformedCredential func() APIError + MissingCredential func() APIError + MissingSignature func() APIError + MissingSignedHeaders func() APIError + InvalidTerminal func(string) APIError + IncorrectRegion func(expected, actual string) APIError + IncorrectService func(string) APIError + MalformedComponent func(string) APIError + MissingComponents func() APIError + DateMismatch func() APIError +}{ + InvalidDateFormat: func(s string) APIError { + return malformedAuthError("incorrect date format %q. This date in the credential must be in the format \"yyyyMMdd\".", s) + }, + MalformedCredential: func() APIError { + return malformedAuthError("the Credential is mal-formed; expecting \"/YYYYMMDD/REGION/SERVICE/aws4_request\".") + }, + MissingCredential: func() APIError { + return malformedAuthError("missing Credential.") + }, + MissingSignature: func() APIError { + return malformedAuthError("missing Signature.") + }, + MissingSignedHeaders: func() APIError { + return malformedAuthError("missing SignedHeaders.") + }, + InvalidTerminal: func(s string) APIError { + return malformedAuthError("incorrect terminal %q. This endpoint uses \"aws4_request\".", s) + }, + IncorrectRegion: func(expected, actual string) APIError { + return malformedAuthError("the region %q is wrong; expecting %q", actual, expected) + }, + IncorrectService: func(s string) APIError { + return malformedAuthError("incorrect service %q. This endpoint belongs to \"s3\".", s) + }, + MalformedComponent: func(s string) APIError { + return malformedAuthError("the authorization component %q is malformed.", s) + }, + MissingComponents: func() APIError { + return malformedAuthError("the authorization header requires three components: Credential, SignedHeaders, and Signature.") + }, + DateMismatch: func() APIError { + return malformedAuthError("The authorization header is malformed; Invalid credential date. Date is not the same as X-Amz-Date.") + }, +} diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index 818ac6df..d2cb6855 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -17,16 +17,20 @@ package integration func TestAuthentication(s *S3Conf) { Authentication_invalid_auth_header(s) Authentication_unsupported_signature_version(s) - Authentication_malformed_credentials(s) - Authentication_malformed_credentials_invalid_parts(s) - Authentication_credentials_terminated_string(s) + Authentication_missing_components(s) + Authentication_malformed_component(s) + Authentication_missing_credentials(s) + Authentication_missing_signedheaders(s) + Authentication_missing_signature(s) + Authentication_malformed_credential(s) + Authentication_credentials_invalid_terminal(s) Authentication_credentials_incorrect_service(s) Authentication_credentials_incorrect_region(s) Authentication_credentials_invalid_date(s) Authentication_credentials_future_date(s) Authentication_credentials_past_date(s) Authentication_credentials_non_existing_access_key(s) - Authentication_invalid_signed_headers(s) + //TODO: handle the case with signed headers Authentication_missing_date_header(s) Authentication_invalid_date_header(s) Authentication_date_mismatch(s) @@ -37,10 +41,13 @@ func TestAuthentication(s *S3Conf) { } func TestPresignedAuthentication(s *S3Conf) { + PresignedAuth_security_token_not_supported(s) PresignedAuth_unsupported_algorithm(s) + PresignedAuth_ECDSA_not_supported(s) + PresignedAuth_missing_signature_query_param(s) PresignedAuth_missing_credentials_query_param(s) PresignedAuth_malformed_creds_invalid_parts(s) - PresignedAuth_malformed_creds_invalid_parts(s) + PresignedAuth_creds_invalid_terminal(s) PresignedAuth_creds_incorrect_service(s) PresignedAuth_creds_incorrect_region(s) PresignedAuth_creds_invalid_date(s) @@ -1020,16 +1027,19 @@ func GetIntTests() IntTests { return IntTests{ "Authentication_invalid_auth_header": Authentication_invalid_auth_header, "Authentication_unsupported_signature_version": Authentication_unsupported_signature_version, - "Authentication_malformed_credentials": Authentication_malformed_credentials, - "Authentication_malformed_credentials_invalid_parts": Authentication_malformed_credentials_invalid_parts, - "Authentication_credentials_terminated_string": Authentication_credentials_terminated_string, + "Authentication_missing_components": Authentication_missing_components, + "Authentication_malformed_component": Authentication_malformed_component, + "Authentication_missing_credentials": Authentication_missing_credentials, + "Authentication_missing_signedheaders": Authentication_missing_signedheaders, + "Authentication_missing_signature": Authentication_missing_signature, + "Authentication_malformed_credential": Authentication_malformed_credential, + "Authentication_credentials_invalid_terminal": Authentication_credentials_invalid_terminal, "Authentication_credentials_incorrect_service": Authentication_credentials_incorrect_service, "Authentication_credentials_incorrect_region": Authentication_credentials_incorrect_region, "Authentication_credentials_invalid_date": Authentication_credentials_invalid_date, "Authentication_credentials_future_date": Authentication_credentials_future_date, "Authentication_credentials_past_date": Authentication_credentials_past_date, "Authentication_credentials_non_existing_access_key": Authentication_credentials_non_existing_access_key, - "Authentication_invalid_signed_headers": Authentication_invalid_signed_headers, "Authentication_missing_date_header": Authentication_missing_date_header, "Authentication_invalid_date_header": Authentication_invalid_date_header, "Authentication_date_mismatch": Authentication_date_mismatch, @@ -1037,10 +1047,13 @@ func GetIntTests() IntTests { "Authentication_invalid_sha256_payload_hash": Authentication_invalid_sha256_payload_hash, "Authentication_incorrect_md5": Authentication_incorrect_md5, "Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key, + "PresignedAuth_security_token_not_supported": PresignedAuth_security_token_not_supported, "PresignedAuth_unsupported_algorithm": PresignedAuth_unsupported_algorithm, + "PresignedAuth_ECDSA_not_supported": PresignedAuth_ECDSA_not_supported, + "PresignedAuth_missing_signature_query_param": PresignedAuth_missing_signature_query_param, "PresignedAuth_missing_credentials_query_param": PresignedAuth_missing_credentials_query_param, "PresignedAuth_malformed_creds_invalid_parts": PresignedAuth_malformed_creds_invalid_parts, - "PresignedAuth_creds_invalid_terminator": PresignedAuth_creds_invalid_terminator, + "PresignedAuth_creds_invalid_terminal": PresignedAuth_creds_invalid_terminal, "PresignedAuth_creds_incorrect_service": PresignedAuth_creds_incorrect_service, "PresignedAuth_creds_incorrect_region": PresignedAuth_creds_incorrect_region, "PresignedAuth_creds_invalid_date": PresignedAuth_creds_invalid_date, diff --git a/tests/integration/tests.go b/tests/integration/tests.go index 617cd288..9c7e9aa0 100644 --- a/tests/integration/tests.go +++ b/tests/integration/tests.go @@ -68,11 +68,7 @@ func Authentication_invalid_auth_header(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingFields)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAuthHeader)) }) } @@ -94,16 +90,117 @@ func Authentication_unsupported_signature_version(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureVersionNotSupported)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationType)) }) } -func Authentication_malformed_credentials(s *S3Conf) error { - testName := "Authentication_malformed_credentials" +func Authentication_missing_components(s *S3Conf) error { + testName := "Authentication_missing_components" + return authHandler(s, &authConfig{ + testName: testName, + method: http.MethodGet, + body: nil, + service: "s3", + date: time.Now(), + }, func(req *http.Request) error { + // missing SignedHeaders component + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=access/20250912/us-east-1/s3/aws4_request,Signature=5fb279ae552098ea7c5c807df54cdb159e74939e19449b29831552639ec34b29") + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.MissingComponents()) + }) +} + +func Authentication_malformed_component(s *S3Conf) error { + testName := "Authentication_malformed_component" + return authHandler(s, &authConfig{ + testName: testName, + method: http.MethodGet, + body: nil, + service: "s3", + date: time.Now(), + }, func(req *http.Request) error { + // malformed SignedHeaders + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=access/20250912/us-east-1/s3/aws4_request,SignedHeaders-Content-Length,Signature=signature") + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.MalformedComponent("SignedHeaders-Content-Length")) + }) +} + +func Authentication_missing_credentials(s *S3Conf) error { + testName := "Authentication_missing_credentials" + return authHandler(s, &authConfig{ + testName: testName, + method: http.MethodGet, + body: nil, + service: "s3", + date: time.Now(), + }, func(req *http.Request) error { + // missing Credentials + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 missing_creds=access/20250912/us-east-1/s3/aws4_request,SignedHeaders=content-length;x-amz-date,Signature=5fb279ae552098ea7c5c807df54cdb159e74939e19449b29831552639ec34b29") + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.MissingCredential()) + }) +} + +func Authentication_missing_signedheaders(s *S3Conf) error { + testName := "Authentication_missing_signedheaders" + return authHandler(s, &authConfig{ + testName: testName, + method: http.MethodGet, + body: nil, + service: "s3", + date: time.Now(), + }, func(req *http.Request) error { + // missing SignedHeaders + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=access/20250912/us-east-1/s3/aws4_request,missing=content-length;x-amz-date,Signature=5fb279ae552098ea7c5c807df54cdb159e74939e19449b29831552639ec34b29") + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.MissingSignedHeaders()) + }) +} + +func Authentication_missing_signature(s *S3Conf) error { + testName := "Authentication_missing_signature" + return authHandler(s, &authConfig{ + testName: testName, + method: http.MethodGet, + body: nil, + service: "s3", + date: time.Now(), + }, func(req *http.Request) error { + // missing Signature + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=access/20250912/us-east-1/s3/aws4_request,SignedHeaders=content-length;x-amz-date,missing=5fb279ae552098ea7c5c807df54cdb159e74939e19449b29831552639ec34b29") + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.MissingSignature()) + }) +} + +func Authentication_malformed_credential(s *S3Conf) error { + testName := "Authentication_malformed_credential" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, @@ -113,7 +210,7 @@ func Authentication_malformed_credentials(s *S3Conf) error { }, func(req *http.Request) error { authHdr := req.Header.Get("Authorization") regExp := regexp.MustCompile("Credential=[^,]+,") - hdr := regExp.ReplaceAllString(authHdr, "Credential-access/32234/us-east-1/s3/aws4_request,") + hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/s3/extra/things,") req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) @@ -121,43 +218,12 @@ func Authentication_malformed_credentials(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.MalformedCredential()) }) } -func Authentication_malformed_credentials_invalid_parts(s *S3Conf) error { - testName := "Authentication_malformed_credentials_invalid_parts" - return authHandler(s, &authConfig{ - testName: testName, - method: http.MethodGet, - body: nil, - service: "s3", - date: time.Now(), - }, func(req *http.Request) error { - authHdr := req.Header.Get("Authorization") - regExp := regexp.MustCompile("Credential=[^,]+,") - hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/s3,") - req.Header.Set("Authorization", hdr) - - resp, err := s.httpClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil { - return err - } - - return nil - }) -} - -func Authentication_credentials_terminated_string(s *S3Conf) error { - testName := "Authentication_credentials_terminated_string" +func Authentication_credentials_invalid_terminal(s *S3Conf) error { + testName := "Authentication_credentials_invalid_terminal" return authHandler(s, &authConfig{ testName: testName, method: http.MethodGet, @@ -175,11 +241,7 @@ func Authentication_credentials_terminated_string(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureTerminationStr)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.InvalidTerminal("aws_request")) }) } @@ -189,20 +251,20 @@ func Authentication_credentials_incorrect_service(s *S3Conf) error { testName: testName, method: http.MethodGet, body: nil, - service: "ec2", + service: "s3", date: time.Now(), }, func(req *http.Request) error { + authHdr := req.Header.Get("Authorization") + regExp := regexp.MustCompile("Credential=[^,]+,") + hdr := regExp.ReplaceAllString(authHdr, "Credential=access/32234/us-east-1/ec2/aws4_request,") + req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureIncorrService)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.IncorrectService("ec2")) }) } @@ -221,22 +283,12 @@ func Authentication_credentials_incorrect_region(s *S3Conf) error { service: "s3", date: time.Now(), }, func(req *http.Request) error { - apiErr := s3err.APIError{ - Code: "SignatureDoesNotMatch", - Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", cfg.awsRegion), - HTTPStatusCode: http.StatusForbidden, - } - resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, apiErr); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.IncorrectRegion(s.awsRegion, cfg.awsRegion)) }) } @@ -259,11 +311,7 @@ func Authentication_credentials_invalid_date(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.InvalidDateFormat("3223423234")) }) } @@ -360,38 +408,7 @@ func Authentication_credentials_non_existing_access_key(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)); err != nil { - return err - } - - return nil - }) -} - -func Authentication_invalid_signed_headers(s *S3Conf) error { - testName := "Authentication_invalid_signed_headers" - return authHandler(s, &authConfig{ - testName: testName, - method: http.MethodGet, - body: nil, - service: "s3", - date: time.Now(), - }, func(req *http.Request) error { - authHdr := req.Header.Get("Authorization") - regExp := regexp.MustCompile("SignedHeaders=[^,]+,") - hdr := regExp.ReplaceAllString(authHdr, "SignedHeaders-host;x-amz-content-sha256;x-amz-date,") - req.Header.Set("Authorization", hdr) - - resp, err := s.httpClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)) }) } @@ -411,11 +428,7 @@ func Authentication_missing_date_header(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingDateHeader)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingDateHeader)) }) } @@ -435,11 +448,7 @@ func Authentication_invalid_date_header(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMalformedDate)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMissingDateHeader)) }) } @@ -452,18 +461,22 @@ func Authentication_date_mismatch(s *S3Conf) error { service: "s3", date: time.Now(), }, func(req *http.Request) error { - req.Header.Set("X-Amz-Date", "20220830T095525Z") + err := createUsers(s, []user{testuser1}) + if err != nil { + return err + } + + authHdr := req.Header.Get("Authorization") + regExp := regexp.MustCompile("Credential=[^,]+,") + hdr := regExp.ReplaceAllString(authHdr, "Credential=grt1/20250912/us-east-1/s3/aws4_request,") + req.Header.Set("Authorization", hdr) resp, err := s.httpClient.Do(req) if err != nil { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.MalformedAuth.DateMismatch()) }) } @@ -483,11 +496,7 @@ func Authentication_invalid_sha256_payload_hash(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidSHA256Paylod)) }) } @@ -508,11 +517,7 @@ func Authentication_incorrect_payload_hash(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrContentSHA256Mismatch)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrContentSHA256Mismatch)) }) } @@ -533,11 +538,7 @@ func Authentication_incorrect_md5(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidDigest)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidDigest)) }) } @@ -558,11 +559,33 @@ func Authentication_signature_error_incorrect_secret_key(s *S3Conf) error { return err } defer resp.Body.Close() - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)); err != nil { + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDoesNotMatch)) + }) +} + +func PresignedAuth_security_token_not_supported(s *S3Conf) error { + testName := "PresignedAuth_security_token_not_supported" + return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) + cancel() + if err != nil { return err } - return nil + uri := v4req.URL + "&X-Amz-Security-Token=my_token" + + req, err := http.NewRequest(v4req.Method, uri, nil) + if err != nil { + return err + } + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.SecurityTokenNotSupported()) }) } @@ -588,11 +611,66 @@ func PresignedAuth_unsupported_algorithm(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQuerySignatureAlgo)); err != nil { + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.UnsupportedAlgorithm()) + }) +} + +func PresignedAuth_ECDSA_not_supported(s *S3Conf) error { + testName := "PresignedAuth_ECDSA_not_supported" + return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) + cancel() + if err != nil { return err } - return nil + uri := strings.Replace(v4req.URL, "AWS4-HMAC-SHA256", "AWS4-ECDSA-P256-SHA256", 1) + + req, err := http.NewRequest(v4req.Method, uri, nil) + if err != nil { + return err + } + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.OnlyHMACSupported()) + }) +} + +func PresignedAuth_missing_signature_query_param(s *S3Conf) error { + testName := "PresignedAuth_missing_signature_query_param" + return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) + cancel() + if err != nil { + return err + } + + urlParsed, err := url.Parse(v4req.URL) + if err != nil { + return err + } + + queries := urlParsed.Query() + queries.Del("X-Amz-Signature") + urlParsed.RawQuery = queries.Encode() + + req, err := http.NewRequest(v4req.Method, urlParsed.String(), nil) + if err != nil { + return err + } + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.MissingRequiredParams()) }) } @@ -625,11 +703,7 @@ func PresignedAuth_missing_credentials_query_param(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.MissingRequiredParams()) }) } @@ -662,16 +736,12 @@ func PresignedAuth_malformed_creds_invalid_parts(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrCredMalformed)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.MalformedCredential()) }) } -func PresignedAuth_creds_invalid_terminator(s *S3Conf) error { - testName := "PresignedAuth_creds_invalid_terminator" +func PresignedAuth_creds_invalid_terminal(s *S3Conf) error { + testName := "PresignedAuth_creds_invalid_terminal" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) v4req, err := client.PresignDeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucket}) @@ -695,11 +765,7 @@ func PresignedAuth_creds_invalid_terminator(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureTerminationStr)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.IncorrectTerminal("aws5_request")) }) } @@ -728,11 +794,7 @@ func PresignedAuth_creds_incorrect_service(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureIncorrService)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.IncorrectService("sns")) }) } @@ -766,15 +828,7 @@ func PresignedAuth_creds_incorrect_region(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.APIError{ - Code: "SignatureDoesNotMatch", - Description: fmt.Sprintf("Credential should be scoped to a valid Region, not %v", cfg.awsRegion), - HTTPStatusCode: http.StatusForbidden, - }); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.IncorrectRegion(s.awsRegion, cfg.awsRegion)) }) } @@ -803,11 +857,7 @@ func PresignedAuth_creds_invalid_date(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.InvalidDateFormat("32234Z34")) }) } @@ -836,11 +886,7 @@ func PresignedAuth_non_existing_access_key_id(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidAccessKeyID)) }) } @@ -873,11 +919,7 @@ func PresignedAuth_missing_date_query(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.MissingRequiredParams()) }) } @@ -891,6 +933,14 @@ func PresignedAuth_dates_mismatch(s *S3Conf) error { return err } + urlParsed, err := url.Parse(v4req.URL) + if err != nil { + return err + } + + tDate := urlParsed.Query().Get("X-Amz-Date") + date := tDate[:8] + uri, err := changeAuthCred(v4req.URL, "20060102", credDate) if err != nil { return err @@ -906,11 +956,7 @@ func PresignedAuth_dates_mismatch(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrSignatureDateDoesNotMatch)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.DateMismatch("20060102", date)) }) } @@ -943,11 +989,7 @@ func PresignedAuth_missing_signed_headers_query_param(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.MissingRequiredParams()) }) } @@ -980,11 +1022,7 @@ func PresignedAuth_missing_expiration_query_param(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrInvalidQueryParams)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.MissingRequiredParams()) }) } @@ -1017,11 +1055,7 @@ func PresignedAuth_invalid_expiration_query_param(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMalformedExpires)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.ExpiresNumber()) }) } @@ -1054,11 +1088,7 @@ func PresignedAuth_negative_expiration_query_param(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrNegativeExpires)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.ExpiresNegative()) }) } @@ -1091,11 +1121,7 @@ func PresignedAuth_exceeding_expiration_query_param(s *S3Conf) error { return err } - if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrMaximumExpires)); err != nil { - return err - } - - return nil + return checkHTTPResponseApiErr(resp, s3err.QueryAuthErrors.ExpiresTooLarge()) }) }