[Proposal] Approach for attaching backend/upstream security across RestApi, LlmProvider, LlmProxy, and Mcp #2846
Thenujan-Nagaratnam
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Why we need this
RestApi has no dedicated field for upstream/backend authentication today — the only way to get any auth-like effect is to manually attach a generic policy (e.g.
set-headers) via thepolicies:list and scope it by hand, with no structural guarantee it stays scoped to the right backend. LlmProvider, LlmProxy, and Mcp each already have their own dedicatedauthfield, but they aren't consistent with each other (see below), and RestApi is now gaining per-operation, multi-backend routing (multiple namedupstreamDefinitions, picked per operation), which makes "how do we attach credentials to a specific backend safely" a real gap to close rather than a nice-to-have.The core question this discussion is trying to answer: should backend/upstream auth be a first-class thing in the API definition — a dedicated
authfield, as LlmProvider/LlmProxy/Mcp already have — or should it stay something achieved indirectly through the existing genericpolicies:mechanism, with no dedicated field at all? The options below split along this line: A/B/C make it first-class (in different shapes); D/E keep it policy-only.Current state
spec.upstream.auth)provider.auth+additionalProviders[].auth)spec.upstream.auth)set-headers) via thepolicies:list and scope it by handauthsub-field directly onspec.upstreamauthsub-field onproviderand on eachadditionalProviders[]entryauthsub-field directly onspec.upstreamset-headersset-headersset-headersExample configs
To ground the discussion, here's what a config looks like for each kind with the proposed
authfield (shown in the Option B shape below) added where it's new.RestApi — auth attaches to a named backend in the
upstreamDefinitionspool, and operations pick which backend (and therefore which auth) they use by referencing it:LlmProvider / Mcp — typically a single upstream, so auth applies unconditionally in the common case:
LlmProxy — auth on the primary provider and on each additional provider, applied depending on which one is selected for a given request:
Options
Options A, B, and C make auth first-class — a dedicated
authfield, in different shapes. Options D and E don't — auth stays something you attach as a regular policy, with no dedicated field.Option A — Typed shape:
{ type, header, value }The existing LlmProvider/Mcp shape, extended with more fields as new auth mechanisms are added (e.g.
clientId,clientSecret,tokenUrlfor OAuth2).Pros: minimal change from what exists today; self-documenting in the schema.
Cons: doesn't scale — every new mechanism needs new fields and new gateway-controller code; params validation has to live in the gateway.
Option B — Policy-reference model:
{ type, name, version, params }api-key/basic/oauth2are shorthand for well-known built-in policies.otherreferences any policy by name/version directly.versionis always required, for every type.paramsis passed through to the resolved policy unexamined — validated by that policy's own schema, not by the gateway.Pros: one shape reused across all four kinds; new auth mechanisms need no schema change, just a new policy definition; gateway never needs to know what a valid
oauth2/custom config looks like.Cons: less self-documenting than Option A (the schema doesn't describe what
oauth2/custom params look like); a typo inname/versiononly fails at validation time.Option C — Fully generic:
{ name, version, params }, no predefined typesEven
api-key/basicbecome "just another named policy" — notypeshorthand at all.Pros: simplest possible schema; zero special-casing in gateway-controller for any mechanism.
Cons: worse ergonomics for the common case — every config needs to know an exact policy name and version; no fixed enum to drive a console/UI picker.
Option D — No dedicated field; rely on the generic
policieslistDon't add a schema field at all — attach a normal policy (e.g.
set-headers) via the existingpolicies:list and scope it by hand. This is what's already possible today for RestApi.Pros: no new schema surface.
Cons: no structural guarantee that a manually-attached policy only applies to the backend it was meant for; poor discoverability — an auth policy looks like any other policy in the list.
Option E — Leave the existing
authfields as they are; add anything new as a policyDon't change LlmProvider/LlmProxy/Mcp's current
authshape at all — it keeps working exactly as it does today, with no migration or compatibility work needed for existing configs. For RestApi (which has no auth today) and for any future auth mechanism, don't extend a typedauthfield — attach it as a normal policy via the existingpolicies:list instead.Pros: zero migration risk for the three kinds that already have a working auth field, since nothing about them changes; no new schema surface needed for RestApi either.
Cons: doesn't reconcile the differences between how LlmProvider/LlmProxy/Mcp each already validate/enforce auth; RestApi's auth would have the same scoping-safety gap as Option D.
Where we're leaning, and what we'd like feedback on
The first question we need to settle is the core one above: first-class field, or policy-only? We're currently leaning towards making it first-class, via Option B: it keeps a simple, discoverable shape for the common cases (api-key/basic/oauth2) while making new or custom auth mechanisms a policy-registry concern rather than a recurring schema change, and lets us use one shared mechanism across all four kinds instead of four separate ones.
That said, this is genuinely open — we'd like feedback on whether first-class is the right call at all, and if so, whether B is the right shape for it. In particular:
api-key/basic/oauth2shorthand in Option B is worth the added schema complexity over the fully generic Option C.authshould be reachable from a single top-levelupstreamslot (as it is for LlmProvider/Mcp today) or only from namedupstreamDefinitionsentries.All reactions