package middleware import ( "context" "crypto/elliptic" "errors" "fmt" "net/http" "time" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" apierr "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/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl" "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 uses a separate context so that the authorization // span middleware does not contain all subsequent spans. Authenticate(ctx context.Context, 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) { reqCtx := r.Context() ctx, span := tracing.StartSpanFromContext(reqCtx, "middleware.Auth") reqInfo := GetReqInfo(reqCtx) reqInfo.User = "anon" box, err := center.Authenticate(ctx, r) if err != nil { if errors.Is(err, ErrNoAuthorizationHeader) { reqLogOrDefault(reqCtx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err), logs.TagField(logs.TagDatapath)) } else { reqLogOrDefault(reqCtx, log).Error(logs.FailedToPassAuthentication, zap.Error(err), logs.TagField(logs.TagDatapath)) err = apierr.TransformToS3Error(err) if err.(apierr.Error).ErrCode == apierr.ErrInternalError { err = apierr.GetAPIError(apierr.ErrAccessDenied) } if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil { reqLogOrDefault(reqCtx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr), logs.TagField(logs.TagDatapath)) } span.End() return } } else { reqCtx = SetBox(reqCtx, box) if box.AccessBox.Gate.BearerToken != nil { reqInfo.User = bearer.ResolveIssuer(*box.AccessBox.Gate.BearerToken).String() } reqLogOrDefault(reqCtx, log).Debug(logs.SuccessfulAuth, zap.String("accessKeyID", box.AuthHeaders.AccessKeyID), logs.TagField(logs.TagDatapath)) } span.End() h.ServeHTTP(w, r.WithContext(reqCtx)) }) } } 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, span := tracing.StartSpanFromContext(r.Context(), "middleware.FrostfsIDValidation") bd, err := GetBoxData(ctx) if err != nil || bd.Gate.BearerToken == nil { reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipFrostfsIDValidation, logs.TagField(logs.TagDatapath)) span.End() h.ServeHTTP(w, r) return } if err = validateBearerToken(frostfsID, bd.Gate.BearerToken); err != nil { reqLogOrDefault(ctx, log).Error(logs.FrostfsIDValidationFailed, zap.Error(err), logs.TagField(logs.TagDatapath)) if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil { reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr), logs.TagField(logs.TagDatapath)) } span.End() return } span.End() 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 }