From dac69caac33353120cf28fc018125b9d5b7b7653 Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Mon, 18 Mar 2024 08:35:15 -0700 Subject: [PATCH 1/2] fix: escape path and query for presign signature validation fixes #462 --- s3api/utils/utils.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/s3api/utils/utils.go b/s3api/utils/utils.go index f926bf3..f0241a3 100644 --- a/s3api/utils/utils.go +++ b/s3api/utils/utils.go @@ -20,11 +20,13 @@ import ( "fmt" "io" "net/http" + "net/url" "regexp" "strconv" "strings" "time" + "github.com/aws/smithy-go/encoding/httpbinding" "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp" "github.com/versity/versitygw/s3err" @@ -114,16 +116,18 @@ func createPresignedHttpRequestFromCtx(ctx *fiber.Ctx, signedHdrs []string, cont } uri := string(ctx.Request().URI().Path()) + uri = httpbinding.EscapePath(uri, false) isFirst := true ctx.Request().URI().QueryArgs().VisitAll(func(key, value []byte) { _, ok := signedQueryArgs[string(key)] if !ok { + escapeValue := url.QueryEscape(string(value)) if isFirst { - uri += fmt.Sprintf("?%s=%s", key, value) + uri += fmt.Sprintf("?%s=%s", key, escapeValue) isFirst = false } else { - uri += fmt.Sprintf("&%s=%s", key, value) + uri += fmt.Sprintf("&%s=%s", key, escapeValue) } } }) From b9ed7cb8f0d352e3afc60029f9490301a9668c2e Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Wed, 20 Mar 2024 14:45:48 -0400 Subject: [PATCH 2/2] feat: Added a presigned v4 authentication integration test case to put/get object containing utf-8 characters --- tests/integration/group-tests.go | 2 + tests/integration/tests.go | 68 +++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index c09fecb..b4ef500 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -43,6 +43,7 @@ func TestPresignedAuthentication(s *S3Conf) { PresignedAuth_incorrect_secret_key(s) PresignedAuth_PutObject_success(s) PresignedAuth_Put_GetObject_with_data(s) + PresignedAuth_Put_GetObject_with_UTF8_chars(s) PresignedAuth_UploadPart(s) } @@ -329,6 +330,7 @@ func GetIntTests() IntTests { "PresignedAuth_incorrect_secret_key": PresignedAuth_incorrect_secret_key, "PresignedAuth_PutObject_success": PresignedAuth_PutObject_success, "PresignedAuth_Put_GetObject_with_data": PresignedAuth_Put_GetObject_with_data, + "PresignedAuth_Put_GetObject_with_UTF8_chars": PresignedAuth_Put_GetObject_with_UTF8_chars, "PresignedAuth_UploadPart": PresignedAuth_UploadPart, "CreateBucket_invalid_bucket_name": CreateBucket_invalid_bucket_name, "CreateBucket_existing_bucket": CreateBucket_existing_bucket, diff --git a/tests/integration/tests.go b/tests/integration/tests.go index d8fb45b..7d2d4de 100644 --- a/tests/integration/tests.go +++ b/tests/integration/tests.go @@ -1448,8 +1448,6 @@ func PresignedAuth_Put_GetObject_with_data(s *S3Conf) error { return fmt.Errorf("read get object response body %w", err) } - fmt.Println(resp.Request.Method, resp.ContentLength, string(respBody)) - if string(respBody) != data { return fmt.Errorf("expected get object response body to be %v, instead got %s", data, respBody) } @@ -1463,6 +1461,72 @@ func PresignedAuth_Put_GetObject_with_data(s *S3Conf) error { }) } +func PresignedAuth_Put_GetObject_with_UTF8_chars(s *S3Conf) error { + testName := "PresignedAuth_Put_GetObject_with_data" + return presignedAuthHandler(s, testName, func(client *s3.PresignClient) error { + bucket, obj := getBucketName(), "my-$%^&*;" + err := setup(s, bucket) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + v4req, err := client.PresignPutObject(ctx, &s3.PutObjectInput{Bucket: &bucket, Key: &obj}) + cancel() + if err != nil { + return err + } + + httpClient := http.Client{ + Timeout: shortTimeout, + } + + req, err := http.NewRequest(v4req.Method, v4req.URL, nil) + if err != nil { + return err + } + + req.Header = v4req.SignedHeader + + resp, err := httpClient.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected my-obj to be successfully uploaded and get %v response status, instead got %v", http.StatusOK, resp.StatusCode) + } + + ctx, cancel = context.WithTimeout(context.Background(), shortTimeout) + v4GetReq, err := client.PresignGetObject(ctx, &s3.GetObjectInput{Bucket: &bucket, Key: &obj}) + cancel() + if err != nil { + return err + } + + req, err = http.NewRequest(v4GetReq.Method, v4GetReq.URL, nil) + if err != nil { + return err + } + + resp, err = httpClient.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected get object response status to be %v, instead got %v", http.StatusOK, resp.StatusCode) + } + + err = teardown(s, bucket) + if err != nil { + return err + } + + return nil + }) +} + func PresignedAuth_UploadPart(s *S3Conf) error { testName := "PresignedAuth_UploadPart" return presignedAuthHandler(s, testName, func(client *s3.PresignClient) error {