forked from TrueCloudLab/frostfs-s3-gw
[#318] auth: Add context for logged errors
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
6e5bcaef97
commit
f1470bab4a
2 changed files with 42 additions and 32 deletions
|
@ -94,12 +94,12 @@ func New(creds tokens.Credentials, prefixes []string) *Center {
|
||||||
func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
||||||
submatches := c.reg.GetSubmatches(header)
|
submatches := c.reg.GetSubmatches(header)
|
||||||
if len(submatches) != authHeaderPartsNum {
|
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")
|
accessKey := strings.Split(submatches["access_key_id"], "0")
|
||||||
if len(accessKey) != accessKeyPartsNum {
|
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"], ";")
|
signedFields := strings.Split(submatches["signed_header_fields"], ";")
|
||||||
|
@ -114,11 +114,12 @@ func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthHeader) getAddress() (oid.Address, error) {
|
func getAddress(accessKeyID string) (oid.Address, error) {
|
||||||
var addr oid.Address
|
var addr oid.Address
|
||||||
if err := addr.DecodeString(strings.ReplaceAll(a.AccessKeyID, "0", "/")); err != nil {
|
if err := addr.DecodeString(strings.ReplaceAll(accessKeyID, "0", "/")); err != nil {
|
||||||
return addr, apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID)
|
return addr, fmt.Errorf("%w: %s", apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID), accessKeyID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return addr, nil
|
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") {
|
if strings.HasPrefix(r.Header.Get(ContentTypeHdr), "multipart/form-data") {
|
||||||
return c.checkFormData(r)
|
return c.checkFormData(r)
|
||||||
}
|
}
|
||||||
return nil, middleware.ErrNoAuthorizationHeader
|
return nil, fmt.Errorf("%w: %v", middleware.ErrNoAuthorizationHeader, authHeaderField)
|
||||||
}
|
}
|
||||||
authHdr, err = c.parseAuthHeader(authHeaderField[0])
|
authHdr, err = c.parseAuthHeader(authHeaderField[0])
|
||||||
if err != nil {
|
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)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := authHdr.getAddress()
|
addr, err := getAddress(authHdr.AccessKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
box, err := c.cli.GetBox(r.Context(), addr)
|
box, err := c.cli.GetBox(r.Context(), addr)
|
||||||
if err != nil {
|
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 {
|
if err = checkFormatHashContentSHA256(r.Header.Get(AmzContentSHA256)); err != nil {
|
||||||
|
@ -218,10 +219,11 @@ func checkFormatHashContentSHA256(hash string) error {
|
||||||
if !IsStandardContentSHA256(hash) {
|
if !IsStandardContentSHA256(hash) {
|
||||||
hashBinary, err := hex.DecodeString(hash)
|
hashBinary, err := hex.DecodeString(hash)
|
||||||
if err != nil {
|
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 {
|
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) {
|
func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
|
||||||
if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil {
|
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 {
|
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")
|
policy := MultipartFormValue(r, "policy")
|
||||||
if 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 {
|
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"))
|
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)
|
return nil, fmt.Errorf("failed to parse x-amz-date field: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var addr oid.Address
|
addr, err := getAddress(submatches["access_key_id"])
|
||||||
if err = addr.DecodeString(strings.ReplaceAll(submatches["access_key_id"], "0", "/")); err != nil {
|
if err != nil {
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidAccessKeyID)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
box, err := c.cli.GetBox(r.Context(), addr)
|
box, err := c.cli.GetBox(r.Context(), addr)
|
||||||
if err != nil {
|
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
|
secret := box.Gate.SecretKey
|
||||||
service, region := submatches["service"], submatches["region"]
|
service, region := submatches["service"], submatches["region"]
|
||||||
|
|
||||||
signature := signStr(secret, service, region, signatureDateTime, policy)
|
signature := signStr(secret, service, region, signatureDateTime, policy)
|
||||||
if signature != MultipartFormValue(r, "x-amz-signature") {
|
reqSignature := MultipartFormValue(r, "x-amz-signature")
|
||||||
return nil, apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch)
|
if signature != reqSignature {
|
||||||
|
return nil, fmt.Errorf("%w: %s != %s", apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch),
|
||||||
|
reqSignature, signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &middleware.Box{AccessBox: box}, nil
|
return &middleware.Box{AccessBox: box}, nil
|
||||||
|
@ -317,10 +322,12 @@ func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *
|
||||||
if authHeader.IsPresigned {
|
if authHeader.IsPresigned {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
if signatureDateTime.Add(authHeader.Expiration).Before(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) {
|
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 {
|
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)
|
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 {
|
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
|
return nil
|
||||||
|
|
|
@ -2,14 +2,15 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
stderrors "errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
"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"
|
"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-s3-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
@ -40,7 +41,7 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoAuthorizationHeader is returned for unauthenticated requests.
|
// 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 {
|
func Auth(center Center, log *zap.Logger) Func {
|
||||||
return func(h http.Handler) http.Handler {
|
return func(h http.Handler) http.Handler {
|
||||||
|
@ -50,12 +51,13 @@ func Auth(center Center, log *zap.Logger) Func {
|
||||||
reqInfo.User = "anon"
|
reqInfo.User = "anon"
|
||||||
box, err := center.Authenticate(r)
|
box, err := center.Authenticate(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == ErrNoAuthorizationHeader {
|
if errors.Is(err, ErrNoAuthorizationHeader) {
|
||||||
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed)
|
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))
|
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))
|
||||||
if _, ok := err.(errors.Error); !ok {
|
err = frostfsErrors.UnwrapErr(err)
|
||||||
err = errors.GetAPIError(errors.ErrAccessDenied)
|
if _, ok := err.(apiErrors.Error); !ok {
|
||||||
|
err = apiErrors.GetAPIError(apiErrors.ErrAccessDenied)
|
||||||
}
|
}
|
||||||
WriteErrorResponse(w, GetReqInfo(r.Context()), err)
|
WriteErrorResponse(w, GetReqInfo(r.Context()), err)
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue