interceptor

package
v0.1.0-develop.2026060... Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package interceptor provides the gRPC unary server interceptor chain that aligns the gRPC transport with the existing HTTP middleware stack (runtime/http/middleware): RequestID, CellAttribution, Tracing, AccessLog, Metrics, Auth, and Recovery.

The interceptors are composed by NewUnaryChain into a single grpc.ServerOption. bootstrap installs that option on the adapters/grpc server; this package owns no server lifecycle.

Chain order

NewUnaryChain composes the interceptors in this fixed order (outermost → innermost), where the first argument to grpc.ChainUnaryInterceptor is the outermost wrapper closest to the transport:

RequestID → CellAttribution → Tracing → AccessLog → Metrics → Auth → Recovery → handler

This mirrors the HTTP listener-root order (CellAttribution → Tracing → AccessLog → Metrics). CellAttribution runs before every interceptor that reads the cell label (AccessLog, Metrics) so the owning cell is in ctx when they observe it; AccessLog runs after Tracing (so a propagated trace_id is in ctx) and OUTER to Auth (so auth rejections are still logged).

Recovery is INNERMOST, not outermost. This deliberately diverges from the HTTP middleware order (where Recovery sits inside Tracing/Metrics but its placement is driven by the HTTP ResponseWriter commit-ordering constraint — Recovery must own the writer to emit a 500 body). gRPC has no such constraint: a status code is a return value, so a panic recovered by the innermost interceptor and converted to codes.Internal propagates outward as a normal (resp, err) return that the outer Metrics and Tracing interceptors observe accurately. Placing Recovery innermost is the grpc-ecosystem go-grpc-middleware consensus, which documents recovery-last so metrics and logging see the converted status.

ref: grpc-ecosystem/go-grpc-middleware interceptors/recovery/interceptors.go ref: go-kratos/kratos transport/grpc/interceptor.go

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewUnaryChain

func NewUnaryChain(deps Deps) grpc.ServerOption

NewUnaryChain composes the unary interceptors into a single grpc.ServerOption. This is the single authoritative composition point; the order is fixed here (RequestID outermost, Recovery innermost) and guarded by archtest GRPC-INTERCEPTOR-CHAIN-ORDER-01. See the package doc for the order rationale.

Registrar and CellIDClosedSet are required (fail-closed): a chain composed without them would silently relabel every RPC to the runtime sentinel.

func UnaryAccessLog

func UnaryAccessLog(clk clock.Clock) grpc.UnaryServerInterceptor

UnaryAccessLog returns an interceptor that logs one structured slog.Info line per RPC, mirroring the HTTP access-log middleware (runtime/http/middleware/ access_log.go). It carries the HTTP-parity field set:

method, code, duration_ms, cell_id, request_id, correlation_id, trace_id

Placement (chain order GRPC-INTERCEPTOR-CHAIN-ORDER-01): after RequestID + CellAttribution + Tracing (so request_id / cell_id / trace_id are already in ctx) and OUTER to Auth (so auth rejections are logged too). The auth Principal is deliberately NOT logged: UnaryAuth runs inner to this interceptor, so no auth.Principal is in ctx at log time — emitting it would be permanently dead code. Revisit only if auth ordering changes (e.g. device-token / per-method-public). trace_id is best-effort: UnaryTracing writes it only for a propagated/remote trace, same as the HTTP path. real_ip (an HTTP access-log field) is intentionally absent: gRPC has no X-Real-IP equivalent in this stack.

Redaction is handled fail-closed at the slog sink (SLOG-HANDLER-SEALED-FUNNEL-01); like the HTTP access log, this interceptor does not redact field-side.

clk is required (clock.MustHaveClock), matching the HTTP AccessLog contract.

func UnaryAuth

func UnaryAuth(verifier auth.IntentTokenVerifier, opts ...AuthOption) grpc.UnaryServerInterceptor

UnaryAuth returns an interceptor that extracts a bearer token from the incoming metadata, verifies it via runtime/auth.AuthenticateBearer (the shared transport-agnostic core), applies the password-reset gate, and on success forwards the principal-enriched context to the handler. Failures map directly to gRPC status codes (Unauthenticated / Unavailable / PermissionDenied), mirroring the HTTP handleAuthRequest classification.

The verifier is required: a nil verifier is a wiring bug that fails fast at construction (programmer-error panic). For a security interceptor this is correct — the server must refuse to start rather than boot and reject every request, which would be indistinguishable from an outage.

func UnaryCellAttribution

func UnaryCellAttribution(resolve CellResolver) grpc.UnaryServerInterceptor

UnaryCellAttribution returns the interceptor that attributes each RPC to its owning cell: it resolves info.FullMethod through resolve and, on a match, writes kernel/ctxkeys.CellID into the context BEFORE the access-log and metrics interceptors read it. It is the gRPC analog of the HTTP CellAttribution middleware (runtime/http/middleware/cell_id.go) and the writer for the otherwise-sentinel grpc_server_* cell label (closing #1383).

resolve is required: a nil resolver is a wiring bug that fails fast at construction (programmer-error panic, like UnaryMetrics' nil-collector guard) rather than silently leaving every RPC attributed to the runtime sentinel.

func UnaryMetrics

func UnaryMetrics(collector metrics.GRPCCollector, clk clock.Clock, validCellIDs map[string]struct{}) grpc.UnaryServerInterceptor

UnaryMetrics returns an interceptor that records grpc_server_requests_total and grpc_server_request_duration_seconds via the given collector. The metric labels are: method (info.FullMethod), code (the gRPC status code name derived from the handler's returned error), and cell. The cell label is resolved through the sealed metrics.ResolveCellLabel funnel against validCellIDs (the assembly cell-id set): the cell written by UnaryCellAttribution (which runs outer to this interceptor, #1152) is reflected when it is a member, and an absent or out-of-set cell degrades to metrics.RuntimeCellSentinel ("_runtime"). This mirrors runtime/http/middleware.Metrics.

collector and clk are required: a nil collector or clock is a wiring bug that fails fast at construction (programmer-error panic, like MustHaveClock) rather than nil-dereferencing on the first RPC where UnaryRecovery would mask it as a generic codes.Internal.

func UnaryRecovery

func UnaryRecovery() grpc.UnaryServerInterceptor

UnaryRecovery returns the innermost interceptor: it recovers panics raised by the handler, logs the redacted panic value plus stack via slog.Error, and converts the panic into a codes.Internal status returned to the client. It does NOT re-panic — the panic is collapsed into a return value so the outer Metrics and Tracing interceptors observe a clean codes.Internal. This mirrors runtime/http/middleware.Recovery (and the go-grpc-middleware / Kratos recovery convention); because it never calls panic() itself it is outside the PANIC-REGISTERED-01 funnel.

func UnaryRequestID

func UnaryRequestID() grpc.UnaryServerInterceptor

UnaryRequestID returns the outermost interceptor: it derives a request id (reused from incoming metadata when valid, otherwise freshly generated) and stores it under both ctxkeys.RequestID and ctxkeys.CorrelationID so that the downstream Tracing / Metrics / Recovery interceptors and any errcode emitted by the handler carry a stable correlation id. It mirrors runtime/http/middleware.RequestID; the ctxkeys helpers are shared and carry no funnel restriction.

func UnaryTracing

func UnaryTracing(tracer wrapper.Tracer) grpc.UnaryServerInterceptor

UnaryTracing returns an interceptor that starts a wrapper.Span per RPC named by the full method (leading slash stripped), records rpc.* attributes, and on error records the error and sets the span status. Error redaction is owned by the otelSpan sink (SPAN-RECORD-ERROR-SEAL-01) — call sites pass the raw error. Span attributes go through wrapper.Attr (not raw attribute.String), so the adapters/otel safeStringAttr redaction funnel applies automatically — this interceptor does not touch the SPAN-SETATTR-REDACT-01 callsite set. It mirrors runtime/http/middleware.Tracing. A nil tracer degrades to NoopTracer.

Types

type AuthOption

type AuthOption func(*authConfig)

AuthOption configures the auth interceptor.

func WithPasswordResetExempt

func WithPasswordResetExempt(pred func(fullMethod string) bool) AuthOption

WithPasswordResetExempt installs a predicate marking RPC methods exempt from the password-reset gate (the gRPC analog of the HTTP route-based exempt matcher). The default (nil predicate) is fail-closed — a reset-required token is rejected on every method. Passing a nil predicate is a no-op; any previously installed predicate is retained.

func WithPublicMethod

func WithPublicMethod(pred func(fullMethod string) bool) AuthOption

WithPublicMethod installs a predicate marking RPC methods that bypass authentication (e.g. health checks). The wiring of concrete public-method sets is a later-PR concern (tracked in backlog); the default (nil predicate) is fail-closed — every method requires authentication. Passing a nil predicate is a no-op; any previously installed predicate is retained.

type CellResolver

type CellResolver func(fullMethod string) (cellID string, ok bool)

CellResolver maps a gRPC fullMethod (e.g. "/pkg.Svc/Method") to the cell that owns it. The bool is false for an unregistered method (e.g. the native grpc-health service), so the cell label downstream degrades to the runtime sentinel. The composition root supplies the registrar's CellIDForMethod, which is the single method→cellID source shared by the chain, bootstrap, and adapter (Option 3, #1152).

type Deps

type Deps struct {
	// Tracer records one span per RPC. A nil Tracer degrades to NoopTracer.
	Tracer wrapper.Tracer
	// Collector records grpc_server_* metrics.
	Collector metrics.GRPCCollector
	// Clock times request duration for the metrics + access-log interceptors (required).
	Clock clock.Clock
	// Verifier authenticates bearer tokens (required; nil fails closed).
	Verifier auth.IntentTokenVerifier
	// AuthOptions configures the auth interceptor (public-method and
	// password-reset-exempt predicates).
	AuthOptions []AuthOption
	// Registrar is the gRPC service registrar whose CellIDForMethod feeds the
	// cell-attribution interceptor (Option 3, #1152). It MUST be the SAME
	// *runtimegrpc.ServiceRegistrar instance passed to adaptersgrpc.Config.Registrar
	// — a different registrar populates a different method map and silently degrades
	// attribution to _runtime. Taking the registrar object (not a detached
	// CellIDForMethod func value) makes the two consumers symmetric — both wire
	// `Registrar: reg` — so a mismatch is visible. (A compile-proof single-builder
	// that emits both the chain option and the adapter config is the Hard upgrade,
	// tracked at #1752.) Required: a nil Registrar is a wiring bug → NewUnaryChain
	// panics rather than leaving every RPC attributed to the sentinel.
	Registrar *runtimegrpc.ServiceRegistrar
	// CellIDClosedSet is the assembly's cell-id set (asm.CellIDs()) the metrics
	// interceptor validates the attributed cell against (M12b defense-in-depth: an
	// out-of-set cell degrades to the runtime sentinel, never pollutes the SLO
	// series). Required + non-empty: a half-wired chain (resolver set, closed set
	// empty) would relabel every cell _runtime — NewUnaryChain fails fast instead.
	CellIDClosedSet []string
}

Deps holds the dependencies the unary interceptor chain needs. They are supplied by the composition root (cmd/ or examples/); this package only composes them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL