Skip to content

feat(guards)!: scoped guards with rejections in the request's protocol #120

Description

@polaz

Part of #118.

Problem

The proxy's guards (rate limits, JWT, ext_authz, the auth decider, maintenance) cover only the requests its own routes serve. Native gRPC and the fallback pass them all, so an embedder cannot rate-limit or authenticate its gRPC API through the proxy, and cannot choose which traffic a guard covers at all. When a guard rejects, it answers in whatever shape it was written with: JSON {"error","message"} from JWT and the rate limiter, plain text from maintenance, the decider's own body. A gRPC client reading that gets a protocol error instead of a status.

Solution

  • Scope per guard. Each guard takes a scope: which traffic it covers (transcoded, endpoints, grpc, fallback), optionally narrowed by path globs and methods. Traffic classes are structural: a guard is mounted only on the classes it covers, so a request pays nothing for a guard outside its scope; a path or method narrowing is one match inside the guard.
    • shield.scope, auth.scope (JWT), auth.authz.scope, maintenance.scope, and ProxyServer::with_auth_decider_scope for the injected decider.
    • Defaults keep today's coverage: JWT, rate limits and maintenance on transcoded + endpoints, ext_authz and the decider on transcoded. The forward-auth /verify endpoint is never behind JWT, since it answers that gate.
  • Concurrency limit guard. concurrency: { max_in_flight, scope } sheds requests past the limit at once instead of queueing them; a request holds its slot until its response body ends, so streams count for their whole life.
  • Rejections in the request's protocol. Every guard rejects through one path that knows the gRPC code: a REST client gets the google.rpc.Status JSON body the transcoder already uses (error, code, message, details) with the mapped HTTP status, a gRPC or gRPC-Web client gets a trailers-only response with that code (UNAUTHENTICATED, PERMISSION_DENIED, RESOURCE_EXHAUSTED, UNAVAILABLE) and the guard's headers (Retry-After, RateLimit-*, WWW-Authenticate, Location) as metadata. A decider's HTTP status maps to its code by the google.rpc.Code HTTP mapping.
  • Build-time checks. A scope that covers no traffic, an invalid path glob or an unknown method fails when the proxy is built.

Acceptance criteria

  • Each guard applies to the traffic its scope names and to nothing else, native gRPC and the fallback included.
  • A guard rejection reaches a REST client as a google.rpc.Status JSON body and a gRPC / gRPC-Web client as a trailers-only status with the same code.
  • The concurrency guard sheds excess requests and releases a slot when the response body ends.
  • Defaults keep today's coverage; existing tests pass unchanged apart from the rejection body shape.
  • README documents the scopes, the concurrency guard and the rejection formats.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions