package middleware import ( "crypto/elliptic" "errors" "fmt" "net/http" "time" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" frosterr "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" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "go.uber.org/zap" ) type ( // Box contains access box and additional info. Box struct { AccessBox *accessbox.Box ClientTime time.Time AuthHeaders *AuthHeader Attributes []object.Attribute } // Center is a user authentication interface. Center interface { // Authenticate validate and authenticate request. // Must return ErrNoAuthorizationHeader if auth header is missed. Authenticate(request *http.Request) (*Box, error) } //nolint:revive AuthHeader struct { AccessKeyID string Region string SignatureV4 string } ) // ErrNoAuthorizationHeader is returned for unauthenticated requests. var ErrNoAuthorizationHeader = errors.New("no authorization header") func Auth(center Center, log *zap.Logger) Func { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := GetReqInfo(ctx) reqInfo.User = "anon" box, err := center.Authenticate(r) if err != nil { if errors.Is(err, ErrNoAuthorizationHeader) { reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err)) } else { reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err)) err = frosterr.UnwrapErr(err) log.Info("error", zap.Error(err), zap.String("code", err.(apierr.Error).Code), zap.String("description", err.(apierr.Error).Description)) if _, ok := err.(apierr.Error); !ok { err = apierr.GetAPIError(apierr.ErrAccessDenied) } if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil { reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr)) } return } } else { ctx = SetBox(ctx, box) if box.AccessBox.Gate.BearerToken != nil { reqInfo.User = bearer.ResolveIssuer(*box.AccessBox.Gate.BearerToken).String() } reqLogOrDefault(ctx, log).Debug(logs.SuccessfulAuth, zap.String("accessKeyID", box.AuthHeaders.AccessKeyID)) } h.ServeHTTP(w, r.WithContext(ctx)) }) } } type FrostFSIDValidator interface { ValidatePublicKey(key *keys.PublicKey) error } func FrostfsIDValidation(frostfsID FrostFSIDValidator, log *zap.Logger) Func { return func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() bd, err := GetBoxData(ctx) if err != nil || bd.Gate.BearerToken == nil { reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipFrostfsIDValidation) h.ServeHTTP(w, r) return } if err = validateBearerToken(frostfsID, bd.Gate.BearerToken); err != nil { reqLogOrDefault(ctx, log).Error(logs.FrostfsIDValidationFailed, zap.Error(err)) if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil { reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr)) } return } h.ServeHTTP(w, r) }) } } func validateBearerToken(frostfsID FrostFSIDValidator, bt *bearer.Token) error { m := new(acl.BearerToken) bt.WriteToV2(m) pk, err := keys.NewPublicKeyFromBytes(m.GetSignature().GetKey(), elliptic.P256()) if err != nil { return fmt.Errorf("invalid bearer token public key: %w", err) } if err = frostfsID.ValidatePublicKey(pk); err != nil { return fmt.Errorf("validation data user key failed: %w", err) } return nil }