From f1470bab4adc3b9ccb2163fe7937bf3af3adf79c Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Wed, 21 Feb 2024 16:16:14 +0300 Subject: [PATCH] [#318] auth: Add context for logged errors Signed-off-by: Denis Kirillov --- api/auth/center.go | 58 ++++++++++++++++++++++++------------------ api/middleware/auth.go | 16 +++++++----- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/api/auth/center.go b/api/auth/center.go index b50dfc6..338088f 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -94,12 +94,12 @@ func New(creds tokens.Credentials, prefixes []string) *Center { func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) { submatches := c.reg.GetSubmatches(header) if len(submatches) != authHeaderPartsNum { - return nil, apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed) + return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed), header) } accessKey := strings.Split(submatches["access_key_id"], "0") if len(accessKey) != accessKeyPartsNum { - return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID) + return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID), accessKey) } signedFields := strings.Split(submatches["signed_header_fields"], ";") @@ -114,11 +114,12 @@ func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) { }, nil } -func (a *AuthHeader) getAddress() (oid.Address, error) { +func getAddress(accessKeyID string) (oid.Address, error) { var addr oid.Address - if err := addr.DecodeString(strings.ReplaceAll(a.AccessKeyID, "0", "/")); err != nil { - return addr, apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID) + if err := addr.DecodeString(strings.ReplaceAll(accessKeyID, "0", "/")); err != nil { + return addr, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID), accessKeyID) } + return addr, nil } @@ -161,7 +162,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) { if strings.HasPrefix(r.Header.Get(ContentTypeHdr), "multipart/form-data") { return c.checkFormData(r) } - return nil, middleware.ErrNoAuthorizationHeader + return nil, fmt.Errorf("%w: %v", middleware.ErrNoAuthorizationHeader, authHeaderField) } authHdr, err = c.parseAuthHeader(authHeaderField[0]) if err != nil { @@ -176,18 +177,18 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) { return nil, fmt.Errorf("failed to parse x-amz-date header field: %w", err) } - if err := c.checkAccessKeyID(authHdr.AccessKeyID); err != nil { + if err = c.checkAccessKeyID(authHdr.AccessKeyID); err != nil { return nil, err } - addr, err := authHdr.getAddress() + addr, err := getAddress(authHdr.AccessKeyID) if err != nil { return nil, err } box, err := c.cli.GetBox(r.Context(), addr) if err != nil { - return nil, fmt.Errorf("get box: %w", err) + return nil, fmt.Errorf("get box '%s': %w", addr, err) } if err = checkFormatHashContentSHA256(r.Header.Get(AmzContentSHA256)); err != nil { @@ -218,10 +219,11 @@ func checkFormatHashContentSHA256(hash string) error { if !IsStandardContentSHA256(hash) { hashBinary, err := hex.DecodeString(hash) if err != nil { - return apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch) + return fmt.Errorf("%w: decode hash: %s: %s", apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch), + hash, err.Error()) } if len(hashBinary) != sha256.Size && len(hash) != 0 { - return apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch) + return fmt.Errorf("%w: invalid hash size %d", apiErrors.GetAPIError(apiErrors.ErrContentSHA256Mismatch), len(hashBinary)) } } @@ -239,12 +241,12 @@ func (c Center) checkAccessKeyID(accessKeyID string) error { } } - return apiErrors.GetAPIError(apiErrors.ErrAccessDenied) + return fmt.Errorf("%w: accesskeyID prefix isn't allowed", apiErrors.GetAPIError(apiErrors.ErrAccessDenied)) } func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) { if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil { - return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidArgument) + return nil, fmt.Errorf("%w: parse multipart form with max size %d", apiErrors.GetAPIError(apiErrors.ErrInvalidArgument), maxFormSizeMemory) } if err := prepareForm(r.MultipartForm); err != nil { @@ -253,12 +255,13 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) { policy := MultipartFormValue(r, "policy") if policy == "" { - return nil, middleware.ErrNoAuthorizationHeader + return nil, fmt.Errorf("%w: missing policy", middleware.ErrNoAuthorizationHeader) } - submatches := c.postReg.GetSubmatches(MultipartFormValue(r, "x-amz-credential")) + creds := MultipartFormValue(r, "x-amz-credential") + submatches := c.postReg.GetSubmatches(creds) if len(submatches) != 4 { - return nil, apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed) + return nil, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed), creds) } signatureDateTime, err := time.Parse("20060102T150405Z", MultipartFormValue(r, "x-amz-date")) @@ -266,22 +269,24 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) { return nil, fmt.Errorf("failed to parse x-amz-date field: %w", err) } - var addr oid.Address - if err = addr.DecodeString(strings.ReplaceAll(submatches["access_key_id"], "0", "/")); err != nil { - return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID) + addr, err := getAddress(submatches["access_key_id"]) + if err != nil { + return nil, err } box, err := c.cli.GetBox(r.Context(), addr) if err != nil { - return nil, fmt.Errorf("get box: %w", err) + return nil, fmt.Errorf("get box '%s': %w", addr, err) } secret := box.Gate.SecretKey service, region := submatches["service"], submatches["region"] signature := signStr(secret, service, region, signatureDateTime, policy) - if signature != MultipartFormValue(r, "x-amz-signature") { - return nil, apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch) + reqSignature := MultipartFormValue(r, "x-amz-signature") + if signature != reqSignature { + return nil, fmt.Errorf("%w: %s != %s", apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch), + reqSignature, signature) } return &middleware.Box{AccessBox: box}, nil @@ -317,10 +322,12 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request * if authHeader.IsPresigned { now := time.Now() if signatureDateTime.Add(authHeader.Expiration).Before(now) { - return apiErrors.GetAPIError(apiErrors.ErrExpiredPresignRequest) + return fmt.Errorf("%w: expired: now %s, signature %s", apiErrors.GetAPIError(apiErrors.ErrExpiredPresignRequest), + now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339)) } if now.Before(signatureDateTime) { - return apiErrors.GetAPIError(apiErrors.ErrBadRequest) + return fmt.Errorf("%w: signature time from the future: now %s, signature %s", apiErrors.GetAPIError(apiErrors.ErrBadRequest), + now.Format(time.RFC3339), signatureDateTime.Format(time.RFC3339)) } if _, err := signer.Presign(request, nil, authHeader.Service, authHeader.Region, authHeader.Expiration, signatureDateTime); err != nil { return fmt.Errorf("failed to pre-sign temporary HTTP request: %w", err) @@ -334,7 +341,8 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request * } if authHeader.SignatureV4 != signature { - return apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch) + return fmt.Errorf("%w: %s != %s: headers %v", apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch), + authHeader.SignatureV4, signature, authHeader.SignedFields) } return nil diff --git a/api/middleware/auth.go b/api/middleware/auth.go index 7698402..2f5381f 100644 --- a/api/middleware/auth.go +++ b/api/middleware/auth.go @@ -2,14 +2,15 @@ package middleware import ( "crypto/elliptic" - stderrors "errors" + "errors" "fmt" "net/http" "time" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" - "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" + apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" + frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -40,7 +41,7 @@ type ( ) // ErrNoAuthorizationHeader is returned for unauthenticated requests. -var ErrNoAuthorizationHeader = stderrors.New("no authorization header") +var ErrNoAuthorizationHeader = errors.New("no authorization header") func Auth(center Center, log *zap.Logger) Func { return func(h http.Handler) http.Handler { @@ -50,12 +51,13 @@ func Auth(center Center, log *zap.Logger) Func { reqInfo.User = "anon" box, err := center.Authenticate(r) if err != nil { - if err == ErrNoAuthorizationHeader { - reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed) + if errors.Is(err, ErrNoAuthorizationHeader) { + reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err)) } else { reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err)) - if _, ok := err.(errors.Error); !ok { - err = errors.GetAPIError(errors.ErrAccessDenied) + err = frostfsErrors.UnwrapErr(err) + if _, ok := err.(apiErrors.Error); !ok { + err = apiErrors.GetAPIError(apiErrors.ErrAccessDenied) } WriteErrorResponse(w, GetReqInfo(r.Context()), err) return