Module authz

Module authz 

Source
Expand description

Request authorization (DW-020, feature analysis sections 4.7 and 4.15).

Authorization answers “is this caller ALLOWED here?” after authentication (DW-019) has answered “who is this caller?”. One route’s rules live in crate::config::Authz; this module evaluates them against the request’s identity, consumer groups, and EFFECTIVE client IP.

§Semantics (frozen)

  • 403 vs 401: a DENIED authenticated (or anonymous-but- IP-gated) request answers 403 “forbidden”; an anonymous request on a route whose authorization carries identity rules answers 401 (authentication is implied by identity rules). The one anonymous-permitting case is an ip_acl-ONLY Authz — IP allow can grant anonymous access (documented exception; an operator writing ip_acl and nothing else wants an IP gate, not a login wall). auth_required on the route still independently forces authentication regardless of authorization.
  • Within one Authz, deny wins: denied_consumers / denied_groups beat allowed_* at the same level; in the IP ACL the deny list is checked before the allow list.
  • IP ACL against the EFFECTIVE client IP: the X-Forwarded-For-resolved client when the direct peer is inside gateway.trusted_proxies (the DW-009 chain — see effective_client_ip), otherwise the direct peer. A spoofed XFF from an untrusted peer never influences the decision.
  • Scopes: every required_scopes entry must appear in the JWT scope claim, which may be a space-separated string ("read write") or an array of strings (flattened by authn to the space-separated form). API-key/Basic identities carry no claims and never satisfy scope rules.
  • Claims: required_claims is exact string equality on the stringified claim value; a listed claim absent from the token fails the match. Only string- and number-valued claims are captured on the identity (authn drops bool/null/object claims), so such a claim can never satisfy a required_claims entry. ALL comparisons — consumers, groups, scopes, claims — are case-sensitive.

§Precedence chain (done-when of DW-020)

Rules attach at five levels with the frozen gateway precedence consumer > route > service > listener > global. authorize resolves the chain through AuthzChain:

  1. A deny at ANY level wins — a consumer-level deny beats a route-level allow, and vice versa. Denials are absolute.
  2. Otherwise the most specific level with rules governs: its own evaluation is the verdict (allow or deny under its rules), and less-specific levels are not consulted. A level without an Authz (or with an empty one) is transparent.

Live links (#123): every level has a config attachment — consumers[].authorization (applies once authentication identifies the consumer), routes[].authorization, services[].authorization, listeners[].authorization (the accepting listener), and the gateway-level authorization (global). Authorization runs on requests that resolved a route, per the documented request-path order; unrouted 404s never reach the chain.

Dry run (DW-041): any attachment may set dry_run: true — its denials are then evaluated, logged, and counted by the caller instead of enforced. The resolver (resolve) walks PAST a dry deny and stops only at a live one, so a live deny at ANY level still enforces: monitor mode observes what a block would reject without ever making enforcement more permissive.

§Failure posture

Authorization failures are logged server-side only (via the decision’s reason when warranted); clients receive a generic 403 body with no rule detail (which list matched, which claim was absent — none of it is their business).

Structs§

AuthzChain
The five authorization levels in frozen precedence order (most specific first). Levels without an Authz attachment today pass None; the chain structure is the DW-020 done-when — links activate as their config fields land.
AuthzContext
Everything one authorization evaluation needs to know about the request.
Resolved
One resolved chain evaluation (DW-041): the enforcement verdict plus, when a dry_run attachment would have denied, the observation the caller logs and counts instead of enforcing.

Enums§

AuthzLevel
Which chain level produced an authorization decision (DW-041): the proxy needs the denying link’s own config (its dry_run flag) to decide whether a deny is enforced or only observed.
Decision
The outcome of an authorization decision.

Functions§

authorize
Resolve the precedence chain. Merge semantics (documented, frozen):
effective_client_ip
Resolve the EFFECTIVE client IP (DW-009 trusted-proxy chain).
evaluate_one
Evaluate ONE Authz block. None means the block imposes nothing (no rules at all — a transparent level); Some(Allow) / Some(Deny) is this level’s verdict.
resolve
authorize with the dry-run observation attached (DW-041): the enforced verdict plus the would-have-denied attachment for the caller to log and count. See Resolved for why a dry deny never masks a live one.