diff --git a/s3api/middlewares/authentication.go b/s3api/middlewares/authentication.go index e40a5d5f..f55345c3 100644 --- a/s3api/middlewares/authentication.go +++ b/s3api/middlewares/authentication.go @@ -53,6 +53,10 @@ func VerifyV4Signature(root RootUserConfig, iam auth.IAMService, region string, // Check X-Amz-Date header date := ctx.Get("X-Amz-Date") + if date == "" { + // Fall back to `Date` header if `X-Amz-Date` is not set + date = ctx.Get("Date") + } if date == "" { return s3err.GetAPIError(s3err.ErrMissingDateHeader) } diff --git a/s3api/middlewares/presign-auth.go b/s3api/middlewares/presign-auth.go index 8c1754b4..23de0aef 100644 --- a/s3api/middlewares/presign-auth.go +++ b/s3api/middlewares/presign-auth.go @@ -35,6 +35,10 @@ func VerifyPresignedV4Signature(root RootUserConfig, iam auth.IAMService, region if !utils.IsPresignedURLAuth(ctx) { return nil } + if utils.IsPresignedURLAuthV2(ctx) { + // SigV2 authorization is not supported by the gateway + return s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationMechanism) + } if ctx.Request().URI().QueryArgs().Has("X-Amz-Security-Token") { // OIDC Authorization with X-Amz-Security-Token is not supported diff --git a/s3api/utils/auth-reader.go b/s3api/utils/auth-reader.go index 9cf24a8a..e7a845bf 100644 --- a/s3api/utils/auth-reader.go +++ b/s3api/utils/auth-reader.go @@ -191,7 +191,10 @@ func ParseAuthorization(authorization string) (AuthData, error) { } algo := authParts[0] - + if algo == "AWS" { + // SigV2 authorization is not supported by the gateway + return a, s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationMechanism) + } if algo != "AWS4-HMAC-SHA256" { return a, s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationType) } diff --git a/s3api/utils/presign-auth-reader.go b/s3api/utils/presign-auth-reader.go index be3a3fb5..a7c040f3 100644 --- a/s3api/utils/presign-auth-reader.go +++ b/s3api/utils/presign-auth-reader.go @@ -254,11 +254,21 @@ func IsPresignedURLAuth(ctx *fiber.Ctx) bool { signedHeaders := ctx.Query("X-Amz-SignedHeaders") expires := ctx.Query("X-Amz-Expires") - return !isEmpty(algo, creds, signature, signedHeaders, expires) + return !allEmpty(algo, creds, signature, signedHeaders, expires) || IsPresignedURLAuthV2(ctx) } -// isEmpty checks if all the given strings are empty -func isEmpty(args ...string) bool { +// IsPresignedURLAuthV2 determines if the request is +// query-string signed with aws v2 signer +func IsPresignedURLAuthV2(ctx *fiber.Ctx) bool { + expires := ctx.Query("Expires") + access := ctx.Query("AWSAccessKeyId") + signature := ctx.Query("Signature") + + return anyNonEmpty(expires, access, signature) +} + +// allEmpty reports whether every given string is empty. +func allEmpty(args ...string) bool { for _, a := range args { if a != "" { return false @@ -267,3 +277,14 @@ func isEmpty(args ...string) bool { return true } + +// anyNonEmpty reports whether at least one given string is non-empty. +func anyNonEmpty(args ...string) bool { + for _, a := range args { + if a != "" { + return true + } + } + + return false +} diff --git a/s3err/s3err.go b/s3err/s3err.go index 14a35b90..3d0ea1e4 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -190,6 +190,7 @@ const ( ErrMetadataTooLarge ErrOnlyAws4HmacSha256 ErrInvalidDateHeader + ErrUnsupportedAuthorizationMechanism // Non-AWS errors ErrExistingObjectIsDirectory @@ -868,6 +869,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "X-Amz-Date must be formated via ISO8601 Long format", HTTPStatusCode: http.StatusBadRequest, }, + ErrUnsupportedAuthorizationMechanism: { + Code: "InvalidRequest", + Description: "The authorization mechanism you have provided is not supported. Please use AWS4-HMAC-SHA256.", + HTTPStatusCode: http.StatusBadRequest, + }, // non aws errors ErrExistingObjectIsDirectory: { diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index 74dbeb94..49e0e74a 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -38,6 +38,7 @@ func TestAuthentication(ts *TestState) { ts.Run(Authentication_invalid_sha256_payload_hash) ts.Run(Authentication_md5) ts.Run(Authentication_signature_error_incorrect_secret_key) + ts.Run(Authentication_sigv2_not_supported) ts.Run(Authentication_with_expect_header) } @@ -62,6 +63,7 @@ func TestPresignedAuthentication(ts *TestState) { ts.Run(PresignedAuth_exceeding_expiration_query_param) ts.Run(PresignedAuth_expired_request) ts.Run(PresignedAuth_incorrect_secret_key) + ts.Run(PresignedAuth_sigv2_not_supported) ts.Run(PresignedAuth_PutObject_success) ts.Run(PresignedAuth_Put_GetObject_with_data) if !ts.conf.azureTests { @@ -1293,6 +1295,7 @@ func GetIntTests() IntTests { "Authentication_invalid_sha256_payload_hash": Authentication_invalid_sha256_payload_hash, "Authentication_md5": Authentication_md5, "Authentication_signature_error_incorrect_secret_key": Authentication_signature_error_incorrect_secret_key, + "Authentication_sigv2_not_supported": Authentication_sigv2_not_supported, "Authentication_with_expect_header": Authentication_with_expect_header, "PresignedAuth_security_token_not_supported": PresignedAuth_security_token_not_supported, "PresignedAuth_unsupported_algorithm": PresignedAuth_unsupported_algorithm, @@ -1314,6 +1317,7 @@ func GetIntTests() IntTests { "PresignedAuth_exceeding_expiration_query_param": PresignedAuth_exceeding_expiration_query_param, "PresignedAuth_expired_request": PresignedAuth_expired_request, "PresignedAuth_incorrect_secret_key": PresignedAuth_incorrect_secret_key, + "PresignedAuth_sigv2_not_supported": PresignedAuth_sigv2_not_supported, "PresignedAuth_PutObject_success": PresignedAuth_PutObject_success, "PutObject_missing_object_lock_retention_config": PutObject_missing_object_lock_retention_config, "PutObject_name_too_long": PutObject_name_too_long, diff --git a/tests/integration/presigned_urls.go b/tests/integration/presigned_urls.go index bd410641..ac9597ca 100644 --- a/tests/integration/presigned_urls.go +++ b/tests/integration/presigned_urls.go @@ -665,6 +665,29 @@ func PresignedAuth_incorrect_secret_key(s *S3Conf) error { }) } +func PresignedAuth_sigv2_not_supported(s *S3Conf) error { + testName := "PresignedAuth_sigv2_not_supported" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + expires := time.Now().UTC().Add(time.Hour).Unix() + uri := fmt.Sprintf("%s/%s/object?AWSAccessKeyId=%s&Expires=%d&Signature=my-signature", s.endpoint, bucket, s.awsID, expires) + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, nil) + if err != nil { + cancel() + return err + } + + resp, err := s.httpClient.Do(req) + cancel() + if err != nil { + return err + } + + return checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationMechanism)) + }) +} + func PresignedAuth_PutObject_success(s *S3Conf) error { testName := "PresignedAuth_PutObject_success" return presignedAuthHandler(s, testName, func(client *s3.PresignClient, bucket string) error { diff --git a/tests/integration/sigv4_auth.go b/tests/integration/sigv4_auth.go index 90fb04d3..835f1e6f 100644 --- a/tests/integration/sigv4_auth.go +++ b/tests/integration/sigv4_auth.go @@ -564,6 +564,37 @@ func Authentication_signature_error_incorrect_secret_key(s *S3Conf) error { }) } +func Authentication_sigv2_not_supported(s *S3Conf) error { + testName := "Authentication_sigv2_not_supported" + bucket := getBucketName() + return authHandler(s, &authConfig{ + testName: testName, + method: http.MethodPut, + service: "s3", + date: time.Now(), + path: fmt.Sprintf("%s/object", bucket), + }, func(req *http.Request) error { + err := setup(s, bucket) + if err != nil { + return err + } + + req.Header.Del("Authorization") + req.Header.Set("Authorization", "AWS seed_signature") + + resp, err := s.httpClient.Do(req) + if err != nil { + return err + } + + if err := checkHTTPResponseApiErr(resp, s3err.GetAPIError(s3err.ErrUnsupportedAuthorizationMechanism)); err != nil { + return err + } + + return teardown(s, bucket) + }) +} + func Authentication_with_expect_header(s *S3Conf) error { testName := "Authentication_with_expect_header" bucket, object := getBucketName(), "object"