Access control design
Access control answers "who can call what". The gateway combines three layers of control points: access keys (identity) → key groups (model/LB authorization) → Header ACL (request header rules).
Three control points
| Layer | Governs | Configured at |
|---|---|---|
| Access key | Caller identity | Console → Access Keys |
| Key group | Model reachability + LB reachability | Console → Key Groups (models + load_balancers) |
| Header ACL | Match by request header to allow/deny | Console → Header ACL rules |
Allowlist mode (default deny + single allow)
The regex engine does not support lookahead, so "allow only if the value does not contain X" cannot be written as a single regex. When you need default-deny and only allow specific requests, orchestrate the other way:
- Set
default_actiontodeny(default deny). - Use a "if contains X then
allow" rule to allow matching items. - Or split into multiple rules covering each allow condition.
This "allowlist + explicit allow" is safer than a denylist — new unknown requests are denied by default. For a config example, see the allowlist mode in Configure Header ACL.
Scope and priority
Header ACL rules have three scopes: global → key group → access key. Matching is first-match-wins, with the more specific scope taking priority. See Header ACL rule fields for the fields.
Expressiveness limits and rule orchestration
Regex does not support lookahead / backreferences
The regex engine is a linear-time, ReDoS-resistant implementation, so it does not support lookahead ((?=...) / (?!...)) or backreferences (\1). The trade-off is that negation conditions like "allow only if the value does not contain X" cannot be expressed as a single regex — use the allowlist mode above instead (default deny + explicit allow).
Rules that fail to load (syntax errors, etc.) are skipped and logged as a warning, while the remaining rules still take effect — so test rules before deploying, and don't rely on a single possibly-failing-to-load rule for a critical allow.
Key groups: two independent dimensions
A key group's models and load_balancers are two independent lists:
models: ["*"](all models) does not carry LB permission.load_balancers: ["*"]does not carry ordinary model permission either.- The two dimensions must be checked separately.
And an LB is an authorization unit — once an LB is allowed, keys in that group can reach every entry of that LB, without being validated against models one by one. For strict isolation, don't use LBs; see Multi-tenant isolation.
Behavior when rules fail
- Rule syntax error: ignored at load time, logged as a warning, other rules take effect.
- Regex no match: follows
default_action. - Multiple scopes: first-match-wins, more specific scope takes priority.
FAQ
Q: Can a key group's models use an alias? No. It only matches the canonical name (name field), aliases are not resolved. See "Model aliases and hidden names" in Upstream and model fields.
Q: How do I authorize a hide_name model? Write its canonical name (not the alias) in the key group's models. hide_name=true only affects whether clients can access it by canonical name; authorization still uses the canonical name. See Upstream and model fields.
Q: How do I rate-limit per tenant? Each tenant has its own access key, and rate limits are counted per key. In multi-instance, configure Redis so rate limits are shared across instances. See Multi-tenant isolation.
Next: Multi-tenant isolation for isolation; Header ACL rule fields for fields; Configure Header ACL for examples.
