diff --git a/cmd/vgwrdma/main.go b/cmd/vgwrdma/main.go index 4cf78035..bbb9d963 100644 --- a/cmd/vgwrdma/main.go +++ b/cmd/vgwrdma/main.go @@ -1287,13 +1287,19 @@ func runGateway(ctx context.Context, be backend.Backend) error { // nil on success without doing so. Wrap it so a verified // request reaches the route handler, while errors end the // chain as usual. + rcH := rcroutes.New(rcSvc, be, iamSvc, readonly, disableACLs) rcAuth := func(ctx fiber.Ctx) error { + // The RC routes run before the default-values + // middleware sets the request locals; the access + // logger and event schema read the region from + // there, so set it for every verified request. + utils.ContextKeyRegion.Set(ctx, region) if err := rcVerify(ctx); err != nil { + rcH.PublishAuthFailure(ctx, err) return rcroutes.WriteRouteError(ctx, err) } return ctx.Next() } - rcH := rcroutes.New(rcSvc, be, iamSvc, readonly, disableACLs) // The gateway builds the access logger, metrics manager, // and event sender inside RunVersityGW; hand them to the // RC routes as soon as they exist so finished transfers diff --git a/rdma/rcroutes/ops_linux.go b/rdma/rcroutes/ops_linux.go index e89ad6ae..341ec7ad 100644 --- a/rdma/rcroutes/ops_linux.go +++ b/rdma/rcroutes/ops_linux.go @@ -18,7 +18,6 @@ package rcroutes import ( - "errors" "fmt" "sync" "time" @@ -30,7 +29,6 @@ import ( "github.com/versity/versitygw/metrics" "github.com/versity/versitygw/rdma/rcserver" "github.com/versity/versitygw/s3api/utils" - "github.com/versity/versitygw/s3err" "github.com/versity/versitygw/s3event" "github.com/versity/versitygw/s3log" ) @@ -118,16 +116,14 @@ func (e *opsEmitter) publish(err error, bytes int64) { } // httpStatusFromError maps an operation error to the HTTP status -// the S3 surface would have answered with. +// the S3 surface would have answered with, using the same route +// error mapping as the wire response so operational records never +// disagree with what the client saw. func httpStatusFromError(err error) int { if err == nil { return 200 } - var serr s3err.S3Error - if errors.As(err, &serr) { - return serr.StatusCode() - } - return 500 + return routeError(err).HTTPStatusCode } // sessionRecord is one tracked session with its captured context. @@ -248,14 +244,15 @@ func (t *opsTracker) publishClaimed(sessionID string, err error, bytes ...int64) // publishRequest emits an operation record for a request that ended // before any session existed (authentication, authorization, or // header failures): no tracking table entry, single emission. -func (t *opsTracker) publishRequest(ctx fiber.Ctx, err error, - bucket, key string, isPut bool) { +func (t *opsTracker) publishRequest(ctx fiber.Ctx, acct auth.Account, + err error, bucket, key string, isPut bool) { if t == nil { return } emit := &opsEmitter{ ops: t.ops, app: t.app, + acct: acct, region: regionFromCtx(ctx), bucket: bucket, key: key, diff --git a/rdma/rcroutes/routes_linux.go b/rdma/rcroutes/routes_linux.go index b770708b..cb896f2d 100644 --- a/rdma/rcroutes/routes_linux.go +++ b/rdma/rcroutes/routes_linux.go @@ -83,6 +83,14 @@ type Handler struct { ops *opsTracker } +// PublishAuthFailure emits an operation record for a request whose +// authentication failed before any route logic ran. The gateway +// auth adapter calls it so signature failures appear in the access +// log like they do on the S3 surface. +func (h *Handler) PublishAuthFailure(ctx fiber.Ctx, err error) { + h.ops.publishRequest(ctx, auth.Account{}, err, "", "", false) +} + // SetOpsServices injects the operational service instances once the // gateway has created them, and wires the native teardown callback // that publishes sessions no request path ever completed. @@ -142,43 +150,53 @@ func (h *Handler) prepareCore(ctx fiber.Ctx) error { } defer h.svc.Leave() - if proto := ctx.Get(hdrProtocol); proto != protocolValue { - return invalidHeader(hdrProtocol, proto) - } - acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) + // Header parse failures end the request before authorization; + // publish them as request records too, with whatever object + // identity the malformed headers still carried. + publishHeaderErr := func(err error) error { + target := ctx.Get(hdrTarget) + bucket, key, _ := splitTarget(target) + h.ops.publishRequest(ctx, acct, err, bucket, key, false) + return err + } + + if proto := ctx.Get(hdrProtocol); proto != protocolValue { + return publishHeaderErr(invalidHeader(hdrProtocol, proto)) + } + op := strings.ToUpper(ctx.Get(hdrOp)) if op != "GET" && op != "PUT" { - return invalidHeader(hdrOp, ctx.Get(hdrOp)) + return publishHeaderErr(invalidHeader(hdrOp, ctx.Get(hdrOp))) } + isPut := op == "PUT" target := ctx.Get(hdrTarget) bucket, key, ok := splitTarget(target) if !ok { - return invalidHeader(hdrTarget, target) + return publishHeaderErr(invalidHeader(hdrTarget, target)) } size, err := parseUint(ctx.Get(hdrSize), 10, 64) if err != nil || size == 0 { - return invalidHeader(hdrSize, ctx.Get(hdrSize)) + return publishHeaderErr(invalidHeader(hdrSize, ctx.Get(hdrSize))) } offset, err := parseUint(ctx.Get(hdrOffset), 10, 64) if err != nil { - return invalidHeader(hdrOffset, ctx.Get(hdrOffset)) + return publishHeaderErr(invalidHeader(hdrOffset, ctx.Get(hdrOffset))) } psn, err := parseUint(ctx.Get(hdrPsn), 16, 32) if err != nil || psn == 0 || psn > 0xffffff { - return invalidHeader(hdrPsn, ctx.Get(hdrPsn)) + return publishHeaderErr(invalidHeader(hdrPsn, ctx.Get(hdrPsn))) } cookie, err := parseUint(ctx.Get(hdrCookie), 16, 32) if err != nil || cookie == 0 { - return invalidHeader(hdrCookie, ctx.Get(hdrCookie)) + return publishHeaderErr(invalidHeader(hdrCookie, ctx.Get(hdrCookie))) } - isPut := op == "PUT" // Authorize through the regular object-access chain. if err := h.authorize(ctx, acct, isRoot, bucket, key, isPut); err != nil { - h.ops.publishRequest(ctx, err, bucket, key, isPut) + h.ops.publishRequest(ctx, acct, err, bucket, key, isPut) return err } @@ -193,7 +211,7 @@ func (h *Handler) prepareCore(ctx fiber.Ctx) error { ClientToken: ctx.Get(hdrToken), }) if err != nil { - h.ops.publishRequest(ctx, mapRcError(err), bucket, key, isPut) + h.ops.publishRequest(ctx, acct, mapRcError(err), bucket, key, isPut) return mapRcError(err) } @@ -202,12 +220,12 @@ func (h *Handler) prepareCore(ctx fiber.Ctx) error { if !isPut { if err := h.stageGet(ctx, resp.SessionID, bucket, key, offset, size); err != nil { _ = h.svc.FinishPrepare(resp.SessionID, false) - h.ops.publishRequest(ctx, err, bucket, key, isPut) + h.ops.publishRequest(ctx, acct, err, bucket, key, isPut) return err } } if err := h.svc.FinishPrepare(resp.SessionID, true); err != nil { - h.ops.publishRequest(ctx, mapRcError(err), bucket, key, isPut) + h.ops.publishRequest(ctx, acct, mapRcError(err), bucket, key, isPut) return mapRcError(err) } diff --git a/rdma/rcroutes/routes_stub.go b/rdma/rcroutes/routes_stub.go index bace981b..6a48adab 100644 --- a/rdma/rcroutes/routes_stub.go +++ b/rdma/rcroutes/routes_stub.go @@ -61,3 +61,6 @@ type OpsServices struct { // SetOpsServices is a stub: without RDMA support there is nothing // to publish into. func (h *Handler) SetOpsServices(ops OpsServices) {} + +// PublishAuthFailure is a stub mirror of the linux handler. +func (h *Handler) PublishAuthFailure(ctx fiber.Ctx, err error) {}