From 6fea34acdac50be8e879b3258626cb65f2b8804e Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Tue, 13 Feb 2024 22:45:28 -0800 Subject: [PATCH] fix: request signature check with signed user-agent This is a hack to replace the ignored headers in the aws-sdk-go-v2 internal/v4 package. The headers in the default ignore list include User-Agent, but this is included is signed headers from some clients. fixes #396 --- s3api/utils/auth_test.go | 82 ++++++++++++++++++++++++++++++++++++++++ s3api/utils/sign_hack.go | 48 +++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 s3api/utils/sign_hack.go diff --git a/s3api/utils/auth_test.go b/s3api/utils/auth_test.go index c544ae21..13da88a9 100644 --- a/s3api/utils/auth_test.go +++ b/s3api/utils/auth_test.go @@ -1,7 +1,14 @@ package utils import ( + "net" "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/gofiber/fiber/v2" + "github.com/valyala/fasthttp/fasthttputil" ) func TestAuthParse(t *testing.T) { @@ -46,3 +53,78 @@ func TestAuthParse(t *testing.T) { }) } } + +// 2024/02/06 21:03:28 Request headers: +// 2024/02/06 21:03:28 Host: 172.21.0.160:11000 +// 2024/02/06 21:03:28 User-Agent: S3 Browser/11.5.7 (https://s3browser.com) +// 2024/02/06 21:03:28 Authorization: AWS4-HMAC-SHA256 Credential=access_key/20240206/us-east-1/s3/aws4_request,SignedHeaders=host;user-agent;x-amz-content-sha256;x-amz-date, Signature=37a35d96998d786113ad420c57c22c5433f6aca74f88f26566caa047fc3601c6 +// 2024/02/06 21:03:28 X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +// 2024/02/06 21:03:28 X-Amz-Date: 20240206T210328Z +func Test_Client_UserAgent(t *testing.T) { + signedHdrs := []string{"host", "user-agent", "x-amz-content-sha256", "x-amz-date"} + access := "access_key" + secret := "secret_key" + region := "us-east-1" + host := "172.21.0.160:11000" + agent := "S3 Browser/11.5.7 (https://s3browser.com)" + expectedSig := "37a35d96998d786113ad420c57c22c5433f6aca74f88f26566caa047fc3601c6" + dateStr := "20240206T210328Z" + + app := fiber.New(fiber.Config{DisableStartupMessage: true}) + + tdate, err := time.Parse(iso8601Format, dateStr) + if err != nil { + t.Fatal(err) + } + + app.Get("/", func(c *fiber.Ctx) error { + req, err := createHttpRequestFromCtx(c, signedHdrs, int64(c.Request().Header.ContentLength())) + if err != nil { + t.Fatal(err) + } + + req.Host = host + req.Header.Add("X-Amz-Content-Sha256", zeroLenSig) + + signer := v4.NewSigner() + + signErr := signer.SignHTTP(req.Context(), + aws.Credentials{ + AccessKeyID: access, + SecretAccessKey: secret, + }, + req, zeroLenSig, service, region, tdate, + func(options *v4.SignerOptions) { + options.DisableURIPathEscaping = true + }) + if signErr != nil { + t.Fatalf("sign generated http request: %v", err) + } + + genAuth, err := ParseAuthorization(req.Header.Get("Authorization")) + if err != nil { + return err + } + + if genAuth.Signature != expectedSig { + t.Errorf("SIG: %v\nexpected: %v\n", genAuth.Signature, expectedSig) + } + + return c.Send(c.Request().Header.UserAgent()) + }) + + ln := fasthttputil.NewInmemoryListener() + go func() { + err := app.Listener(ln) + if err != nil { + panic(err) + } + }() + + c := fiber.AcquireClient() + c.UserAgent = agent + a := c.Get("http://example.com") + a.HostClient.Dial = func(_ string) (net.Conn, error) { return ln.Dial() } + a.String() + fiber.ReleaseClient(c) +} diff --git a/s3api/utils/sign_hack.go b/s3api/utils/sign_hack.go new file mode 100644 index 00000000..7886563d --- /dev/null +++ b/s3api/utils/sign_hack.go @@ -0,0 +1,48 @@ +// 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 utils + +import ( + "reflect" + "unsafe" +) + +// This is a hack to replace the default IgnoredHeaders in the aws-sdk-go-v2 +// internal/v4 package. Some AWS applications +// (e.g. AWS Java SDK v1, Athena JDBC driver, s3 browser) sign the requests +// including the User-Agent header. The aws sdk doesn't allow directly +// modifying the ignored header list. Below is a hack to replace this list +// with our own. + +type Rule interface { + IsValid(value string) bool +} +type Rules []Rule + +//go:linkname __ignoredHeaders github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4.IgnoredHeaders +var __ignoredHeaders unsafe.Pointer + +func init() { + // Avoids "go.info.github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4.IgnoredHeaders: + // relocation target go.info.github.com/xxx/xxx/xxx.Rules not defined" + var ignoredHeaders = (*Rules)(unsafe.Pointer(&__ignoredHeaders)) + + // clear the map, and set just the ignored headers we want + reflect.ValueOf((*ignoredHeaders)[0]).FieldByName("Rule").Elem().Clear() + reflect.ValueOf((*ignoredHeaders)[0]).FieldByName("Rule").Elem().SetMapIndex( + reflect.ValueOf("Authorization"), reflect.ValueOf(struct{}{})) + reflect.ValueOf((*ignoredHeaders)[0]).FieldByName("Rule").Elem().SetMapIndex( + reflect.ValueOf("Expect"), reflect.ValueOf(struct{}{})) +}