From 1f29cb8f84f56f4051a30969598b1d57f66120fe Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Wed, 20 Mar 2024 14:51:53 +0100 Subject: [PATCH 1/2] signature: return access key when V4SignVerify is successful --- signature/signature-v4.go | 38 +++++++++++++++-------------- signature/signature-v4_test.go | 5 ++-- signature/streaming-signature-v4.go | 6 ++--- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/signature/signature-v4.go b/signature/signature-v4.go index 82b892b..dc69364 100644 --- a/signature/signature-v4.go +++ b/signature/signature-v4.go @@ -142,7 +142,7 @@ func getSigningKey(secretKey string, t time.Time, region string) []byte { return signingKey } -func authTypeSignedVerify(r *http.Request) ErrorCode { +func authTypeSignedVerify(r *http.Request) (string, ErrorCode) { // Copy request. req := *r hashedPayload := getContentSha256Cksum(r) @@ -153,32 +153,32 @@ func authTypeSignedVerify(r *http.Request) ErrorCode { // Parse signature version '4' header. signV4Values, Err := parseSignV4(v4Auth) if Err != ErrNone { - return Err + return "", Err } cred, _, Err := checkKeyValid(r, signV4Values.Credential.accessKey) if Err != ErrNone { - return Err + return "", Err } // Extract all the signed headers along with its values. extractedSignedHeaders, ErrCode := extractSignedHeaders(signV4Values.SignedHeaders, r) if ErrCode != ErrNone { - return ErrCode + return "", ErrCode } // Extract date, if not present throw Error. var date string if date = req.Header.Get(amzDate); date == "" { if date = r.Header.Get(headerDate); date == "" { - return errMissingDateHeader + return "", errMissingDateHeader } } // Parse date header. t, e := time.Parse(iso8601Format, date) if e != nil { - return errMalformedDate + return "", errMalformedDate } // Query string. @@ -198,49 +198,51 @@ func authTypeSignedVerify(r *http.Request) ErrorCode { // Verify if signature match. if !compareSignatureV4(newSignature, signV4Values.Signature) { - return errSignatureDoesNotMatch + return "", errSignatureDoesNotMatch } // Return Error none. - return ErrNone + return cred.AccessKey, ErrNone } -func authTypeStreamingVerify(r *http.Request, authType authType) ErrorCode { +func authTypeStreamingVerify(r *http.Request, authType authType) (string, ErrorCode) { var size int64 if sizeStr, ok := r.Header["X-Amz-Decoded-Content-Length"]; ok { if sizeStr[0] == "" { - return errMissingContentLength + return "", errMissingContentLength } var err error size, err = strconv.ParseInt(sizeStr[0], 10, 64) if err != nil { - return errMissingContentLength + return "", errMissingContentLength } } + var cred Credentials var rc io.ReadCloser var ec ErrorCode switch authType { case authTypeStreamingSigned, authTypeStreamingSignedTrailer: - rc, ec = newSignV4ChunkedReader(r, authType == authTypeStreamingSignedTrailer) + rc, cred, ec = newSignV4ChunkedReader(r, authType == authTypeStreamingSignedTrailer) case authTypeStreamingUnsignedTrailer: - return errUnsupportAlgorithm // not supported + return "", errUnsupportAlgorithm // not supported default: panic("can't call authTypeStreamingVerify with a non streaming auth type") } if ec != ErrNone { - return ec + return "", ec } r.Body = rc r.ContentLength = size r.Header.Set("Content-Length", fmt.Sprint(size)) - return ErrNone + return cred.AccessKey, ErrNone } // V4SignVerify - Verify authorization header with calculated header in accordance with // - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html // -// returns nil if signature matches. -func V4SignVerify(r *http.Request) ErrorCode { +// returns ErrNone if signature matches alongside the access key used for the +// authentication +func V4SignVerify(r *http.Request) (string, ErrorCode) { // Make sure the authentication type is supported. authType := getRequestAuthType(r) switch authType { @@ -249,6 +251,6 @@ func V4SignVerify(r *http.Request) ErrorCode { case authTypeSigned: return authTypeSignedVerify(r) default: - return errUnsupportAlgorithm + return "", errUnsupportAlgorithm } } diff --git a/signature/signature-v4_test.go b/signature/signature-v4_test.go index ac642cd..49a86ba 100644 --- a/signature/signature-v4_test.go +++ b/signature/signature-v4_test.go @@ -37,7 +37,6 @@ func RandString(n int) string { } func TestSignatureMatch(t *testing.T) { - Body := bytes.NewReader(nil) ak := RandString(32) @@ -58,7 +57,9 @@ func TestSignatureMatch(t *testing.T) { t.Error(err) } - if result := signature.V4SignVerify(req); result != signature.ErrNone { + if accessKey, result := signature.V4SignVerify(req); result != signature.ErrNone { t.Error(fmt.Errorf("invalid result: expect none but got %+v", signature.GetAPIError(result))) + } else if accessKey != ak { + t.Error(fmt.Errorf("invalid access key: expect %s but got %s", ak, accessKey)) } } diff --git a/signature/streaming-signature-v4.go b/signature/streaming-signature-v4.go index 0ba8136..59e9ae5 100644 --- a/signature/streaming-signature-v4.go +++ b/signature/streaming-signature-v4.go @@ -165,10 +165,10 @@ var errChunkTooBig = errors.New("chunk too big: choose chunk size <= 16MiB") // // NewChunkedReader is not needed by normal applications. The http package // automatically decodes chunking when reading response bodies. -func newSignV4ChunkedReader(req *http.Request, trailer bool) (io.ReadCloser, ErrorCode) { +func newSignV4ChunkedReader(req *http.Request, trailer bool) (io.ReadCloser, Credentials, ErrorCode) { cred, seedSignature, region, seedDate, errCode := calculateSeedSignature(req, trailer) if errCode != ErrNone { - return nil, errCode + return nil, Credentials{}, errCode } if trailer { @@ -191,7 +191,7 @@ func newSignV4ChunkedReader(req *http.Request, trailer bool) (io.ReadCloser, Err chunkSHA256Writer: sha256.New(), buffer: make([]byte, 64*1024), debug: false, - }, ErrNone + }, cred, ErrNone } // Represents the overall state that is required for decoding a From d01e417a22a5440665d1d221170db255b39500d4 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Wed, 20 Mar 2024 18:07:31 +0100 Subject: [PATCH 2/2] move routing into routeBase --- backend.go | 2 +- gofakes3.go | 5 ----- routing.go | 8 ++++++++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/backend.go b/backend.go index 298c57e..e7998a2 100644 --- a/backend.go +++ b/backend.go @@ -325,7 +325,7 @@ type MultipartBackend interface { } type AuthenticatedBackend interface { - AuthenticationMiddleware(http.Handler) http.Handler + AuthenticateRequest(w http.ResponseWriter, r *http.Request, bucket string) bool } // CopyObject is a helper function useful for quickly implementing CopyObject on diff --git a/gofakes3.go b/gofakes3.go index d0189b0..de9f524 100644 --- a/gofakes3.go +++ b/gofakes3.go @@ -98,11 +98,6 @@ func (g *GoFakeS3) Server() http.Handler { } else if g.hostBucket { handler = g.hostBucketMiddleware(handler) } - - ab, ok := g.storage.(AuthenticatedBackend) - if ok { - handler = ab.AuthenticationMiddleware(handler) - } return handler } diff --git a/routing.go b/routing.go index 870fadf..8305385 100644 --- a/routing.go +++ b/routing.go @@ -28,6 +28,14 @@ func (g *GoFakeS3) routeBase(w http.ResponseWriter, r *http.Request) { err error ) + // perform authentication if necessary + ab, ok := g.storage.(AuthenticatedBackend) + if ok { + if !ab.AuthenticateRequest(w, r, bucket) { + return // unable to authenticate + } + } + hdr := w.Header() id := fmt.Sprintf("%016X", g.nextRequestID())