Skip to content

Commit e4c01a0

Browse files
committed
Update Auth GEP with Implementable details
This commit adds the design rationale and API design for phase 1 of the Auth GEP, adding a Filter to HTTPRoute. Signed-off-by: Nick Young <[email protected]>
1 parent b4794ff commit e4c01a0

File tree

2 files changed

+379
-10
lines changed

2 files changed

+379
-10
lines changed

geps/gep-1494/index.md

Lines changed: 370 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# GEP-1494: HTTP Auth in Gateway API
22

33
* Issue: [#1494](https://github.com/kubernetes-sigs/gateway-api/issues/1494)
4-
* Status: Provisional
4+
* Status: Implementable
55

66
(See [status definitions](../overview.md#gep-states).)
77

@@ -130,20 +130,383 @@ From @ongy, some additional goals to keep in mind:
130130

131131
## API
132132

133-
(... details, can point to PR with changes)
133+
This GEP proposes a two-part solution to this problem:
134+
135+
* We introduce a new HTTPRoute Filter, `ExternalAuth`, that allows the
136+
specification of an external source to connect to using Envoy's `ext_auth` protocol.
137+
* We introduce a new Policy object that can be targeted at either the
138+
Gateway or HTTPRoute levels. In either of these cases, it _defaults_ the settings
139+
for the HTTPRoute Filter across all HTTPRoute matches that roll up to the object.
140+
141+
These two parts will be done in two separate changes - Filter first, then
142+
Policy after.
143+
144+
Both of these additions use the same underlying struct for the config, so that
145+
changes or additions in one place add them in the other as well.
146+
147+
This plan has some big things that need explaining before we get to the API details:
148+
149+
* Why a Filter plus Policy approach?
150+
* Why two changes?
151+
* Why Envoy's `ext_auth`?
152+
153+
### Why a Filter plus Policy approach?
154+
155+
We have two main requirements: Ana needs to be able to configure auth at least at
156+
the smallest possible scope, and Ana, Ian and Chihiro need to be able to configure
157+
defaults at larger scopes.
158+
159+
The smallest possible scope for this config is the HTTPRoute Rule level, where
160+
you can match a single set of matchers - like a path, or a path and header
161+
combination.
162+
163+
At this level, the inline tool we have available to perform changes is the HTTPRoute
164+
Filter, which also has the property that it's designed to _filter_ requests. This
165+
matches the overall pattern here, which is to _filter_ some requests, allowing
166+
or denying them based on the presence of Authentication and the passing of
167+
Authorization checks.
168+
169+
A Policy _can_ be targeted at this level, using the Route rule as a `sectionName`,
170+
but that leaves aside that Filters are exactly designed to handle this use case.
171+
172+
Policy attachment includes defaulting fields like Filters in its scope already,
173+
so we are allowed to use a combination in this way.
174+
175+
Using a Filter also has the advantage that, at the tightest possible scope (the
176+
object being defaulted) you can _explicitly_ override any configured defaults.
177+
178+
Using a Filter also includes ordering (because Filters are an ordered list),
179+
although this exact behavior is currently underspecified. This change will also
180+
need to clarify. Ordering is particularly important for Auth use cases, because
181+
sometimes Auth will expect certain properties that may need to be introduced
182+
by things like header modification.
183+
184+
Lastly, using a Filter means that, for the simplest possible case, where Ana
185+
wants to enable Auth* for a single path, then there is only a single object to
186+
edit, and a single place to configure.
187+
188+
Using a Policy for the simplest case immediately brings in all the discovery
189+
problems that Policy entails.
190+
191+
There are two important caveats here that must be addressed, however:
192+
* Firstly, whatever is in the filter either must be accepted, or the rule
193+
is not accepted. Overrides from anywhere else, including if we add an Override
194+
Policy later, must not override explicit config - that would
195+
violate one of the key declarative principles, that what is requested in the
196+
spec is either what ends up in the state, or that config is rejected.
197+
* Secondly, filter ordering is particularly important for Auth use cases, so we
198+
must ensure that when we add Policy defaulting we have a way to indicate at
199+
what position in a filter list the Auth policy should fit.
200+
201+
### Why two phases?
202+
203+
In short: In the interest of getting something, even if incomplete, into our
204+
user's hands as quickly as possible.
205+
206+
Policy design is complex, and needs to be done carefully. Doing a first
207+
pass using only a Filter to get the basic config correct while we discuss
208+
how to make the Policy handling work means that we can get some work out to the
209+
community without needing to complete the whole design.
210+
211+
In particular, the design for the Filter plus Policy will need to answer at
212+
least the following questions:
213+
214+
* How to set where in a list of Filters a defaulted Auth filter sits;
215+
and what happens if no Filters are specified in a HTTPRoute? Does it go first,
216+
last, or should there be a way to specify the order?
217+
* What Policy types are possible? Defaults? (Definitely useful for setting a
218+
baseline expectation for a cluster, which is desirable for security constructs
219+
like Auth) Overrides? (Also very useful for ensuring that exceptions meet
220+
certain requirements - like only allowing the disabling of Auth on `/healthz`
221+
endpoints or similar use cases.)
222+
* Should Policy have a way to describe rules around when it should take effect?
223+
That's in addition to the usual hierarchical rules, should the Policy have ways
224+
to include or exclude specific matches? This would require agreement in the
225+
Policy Attachment spec as well.
226+
227+
All of these changes have costs in complexity and troubleshooting difficulty, so
228+
it's important to ensure that the design consciously makes these tradeoffs.
229+
230+
In particular, the last two items in the above list seem likely to require a fair
231+
amount of discussion, and including a Policy in the initial release of this
232+
seems likely to make this change miss its current release window.
233+
234+
235+
### Why Envoy's ext_auth?
236+
237+
#### What is ext_auth?
238+
239+
Envoy's External Authorization filter is a filter that calls out to an authorization
240+
service to check if the incoming request is authorized or not. Note that, in
241+
order to check _authorization_, it must also be able to determine _authentication_ -
242+
this is one of the reasons why we've chosen this approach.
243+
244+
Envoy's implementation of this filter allows both a
245+
[gRPC, protobuf API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto)
246+
and configuration of a HTTP based API (which, as it's not defined using a
247+
specification like protobuf, requires more configuration).
248+
249+
The important thing to remember here is that the actual authentication and
250+
authorization processed are delegated to the authorization service - which is
251+
why the ext_auth approach allows handling many Auth methods - most of the
252+
work is performed by external services which implement various methods (like
253+
Basic Auth, OAuth, JWT validation, etc)
254+
255+
#### Why use it over other options?
256+
257+
The community discussed Auth extensively in-person at Kubecon London in early 2025,
258+
and got broad agreement from multiple dataplanes that:
259+
260+
* something like ext_auth was a good idea, because it's flexible and allows the
261+
implementation of many types of Auth without specific protocol implementation
262+
in upstream
263+
* Envoy's ext_auth protocol has no major problems that would stop us using it
264+
* Envoy-based implementations mostly already have support for it
265+
266+
At that meeting, those present agreed that ext_auth was a good place to start.
267+
268+
Most non-Envoy dataplanes also already have similar methods, so the maintainers
269+
of projects using other dataplanes were okay with this idea.
270+
271+
The alternative here would be to add a Filter type _per auth method_, which, given
272+
the large number of options, could quickly become very complex.
273+
274+
This GEP is, however, explicitly _not_ ruling out the possibility of adding
275+
specific Filters for specific Auth methods in the future, if users of this API
276+
find the overhead of running a compatible implementation to be too much.
277+
278+
### API Design
279+
280+
#### Phase 1: Adding a Filter
281+
282+
This config mainly takes inspiration from Envoy's ext_auth filter config, while
283+
also trying to maintain compatibility with other HTTP methods.
284+
285+
This design is also trying to start with a minimum feature set, and add things
286+
as required, rather than adding everything configurable in all implementations
287+
immediately.
288+
289+
There is some difference between data planes, based on the links above, but
290+
these fields should be broadly supportable across all the listed implementations.
291+
292+
Some design comments are included inline.
293+
294+
The intent for Phase 2 is that this struct will be included in an eventual Policy
295+
so that additions only need to be made in one place.
296+
297+
Additionally, as per other added Filters, the config is included in HTTPRoute,
298+
and is not an additional CRD.
299+
300+
##### Go Structs
301+
302+
```go
303+
304+
// HTTPRouteExtAuthProtcol specifies what protocol should be used
305+
// for communicating with an external authorization server.
306+
//
307+
// Valid values are supplied as constants below.
308+
type HTTPRouteExtAuthProtocol string
309+
310+
const (
311+
HTTPRouteExtAuthGRPCProtocol HTTPRouteExtAuthProtocol = "GRPC"
312+
HTTPRouteExtAuthHTTPProtocol HTTPRouteExtAuthProtocol = "HTTP"
313+
)
314+
// HTTPExtAuthFilter defines a filter that modifies requests by sending
315+
// request details to an external authorization server.
316+
//
317+
// Support: Extended
318+
// Feature Name: HTTPRouteExtAuth
319+
type HTTPExtAuthFilter struct {
320+
321+
// ExtAuthProtocol describes which protocol to use when communicating with an
322+
// ext_auth authorization server.
323+
//
324+
// When this is set to GRPC, each backend must use the Envoy ext_auth protocol
325+
// on the port specified in `backendRefs`. Requests and responses are defined
326+
// in the protobufs explained at:
327+
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto
328+
//
329+
// When this is set to HTTP, each backend must respond with a `200` status
330+
// code in on a successful authorization. Any other code is considered
331+
// an authorization failure.
332+
//
333+
// Feature Names:
334+
// GRPC Support - HTTPRouteExtAuthGRPC
335+
// HTTP Support - HTTPRouteExtAuthHTTP
336+
//
337+
// +unionDiscriminator
338+
// +kubebuilder:validation:Enum=HTTP;GRPC
339+
ExtAuthProtocol HTTPRouteExtAuthProtocol `json:"protocol"`
340+
341+
// BackendRefs is a list of references to backends to send authorization
342+
// requests to.
343+
//
344+
// If this list contains more than one entry, authorization requests must
345+
// be load-balanced across all the backends equally.
346+
//
347+
// The backends must speak the selected protocol (GRPC or HTTP) on the
348+
// referenced port.
349+
//
350+
// If the backend service requires TLS, use BackendTLSPolicy to tell the
351+
// implementation to supply the TLS details to be used to connect to that
352+
// backend.
353+
//
354+
// +kubebuilder:validation:MinLength=1
355+
// +kubebuilder:validation:MaxLength=8
356+
BackendRefs []BackendObjectReference `json:"backendRefs"`
357+
358+
// GRPCAuthConfig contains configuration for communication with ext_auth
359+
// protocol-speaking backends.
360+
//
361+
// If unset, implementations must assume the default behavior for each
362+
// included field is intended.
363+
//
364+
// +optional
365+
GRPCAuthConfig *GRPCAuthConfig `json:"grpc,omitempty"`
366+
367+
// HTTPAuthConfig contains configuration for communication with HTTP-speaking
368+
// backends.
369+
//
370+
// If unset, implementations must assume the default behavior for each
371+
// included field is intended.
372+
//
373+
// +optional
374+
HTTPAuthConfig *HTTPAuthConfig `json:"http,omitempty"`
375+
376+
// ForwardBody controls if requests to the authorization server should include
377+
// the body of the client request; and if so, how big that body is allowed
378+
// to be.
379+
//
380+
// It is expected that implementations will buffer the request body up to
381+
// `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a
382+
// 4xx series error (413 or 403 are common examples), and fail processing
383+
// of the filter.
384+
//
385+
// If unset, or `forwardBody.maxSize` is set to `0`, then the body will not
386+
// be forwarded.
387+
//
388+
// Feature Name: HTTPRouteExtAuthForwardBody
389+
//
390+
// GEP Review Notes:
391+
// Both Envoy and Traefik show support for this feature, but HAProxy and
392+
// ingress-nginx do not. So this has a separate feature flag for it.
393+
//
394+
// +optional
395+
ForwardBody *ForwardBodyConfig `json:"forwardBody,omitempty"`
396+
}
397+
398+
// GRPCAuthConfig contains configuration for communication with ext_auth
399+
// protocol-speaking backends.
400+
type GRPCAuthConfig struct {
401+
402+
// AllowedRequestHeaders specifies what headers from the client request
403+
// will be sent to the authorization server.
404+
//
405+
// If this list is empty, then all headers must be sent.
406+
//
407+
// +optional
408+
// +kubebuilder:validation:MaxLength=64
409+
AllowedRequestHeaders []string `json:"allowedHeaders,omitempty"`
410+
}
411+
412+
// HTTPAuthConfig contains configuration for communication with HTTP-speaking
413+
// backends.
414+
type HTTPAuthConfig struct {
415+
// Path sets the prefix that paths from the client request will have added
416+
// when forwarded to the authorization server.
417+
//
418+
// When empty or unspecified, no prefix is added.
419+
// +optional
420+
Path string `json:"path,omitempty"`
421+
422+
// AllowedRequestHeaders specifies what additional headers from the client request
423+
// will be sent to the authorization server.
424+
//
425+
// The following headers must always be sent to the authorization server,
426+
// regardless of this setting:
427+
//
428+
// * `Host`
429+
// * `Method`
430+
// * `Path`
431+
// * `Content-Length`
432+
// * `Authorization`
433+
//
434+
// If this list is empty, then only those headers must be sent.
435+
//
436+
// Note that `Content-Length` has a special behavior, in that the length
437+
// sent must be correct for the actual request to the external authorization
438+
// server - that is, it must reflect the actual number of bytes sent in the
439+
// body of the request to the authorization server.
440+
//
441+
// So if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set
442+
// to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set
443+
// to anything other than `0`, then the `Content-Length` of the authorization
444+
// request must be set to the actual number of bytes forwarded.
445+
//
446+
// +optional
447+
// +kubebuilder:validation:MaxLength=64
448+
AllowedRequestHeaders []string `json:"allowedHeaders,omitempty"`
449+
450+
// AllowedResponseHeaders specifies what headers from the authorization response
451+
// will be copied into the request to the backend.
452+
//
453+
// If this list is empty, then all headers from the authorization server
454+
// except Authority or Host must be copied.
455+
//
456+
// +optional
457+
// +kubebuilder:validation:MaxLength=64
458+
AllowedResponseHeaders []string `json:"allowedResponseHeaders,omitempty"`
459+
460+
}
461+
462+
// ForwardBody configures if requests to the authorization server should include
463+
// the body of the client request; and if so, how big that body is allowed
464+
// to be.
465+
//
466+
// If empty or unset, do not forward the body.
467+
type ForwardBodyConfig struct {
468+
469+
// MaxSize specifies how large in bytes the largest body that will be buffered
470+
// and sent to the authorization server. If the body size is larger than
471+
// `maxSize`, then the body sent to the authorization server must be
472+
// truncated to `maxSize` bytes.
473+
//
474+
// If 0, the body will not be sent to the authorization server.
475+
MaxSize uint16 `json:"maxSize,omitempty"`
476+
}
477+
478+
```
479+
#### YAML Examples
480+
481+
Coming soon.
482+
483+
#### Phase 2: Adding more complex configuration with Policy
484+
485+
This phase is currently undefined until we reach agreement on the Filter + Policy
486+
approach.
134487

135488
## Conformance Details
136489

137490
(from https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-2162/index.md#standardize-features-and-conformance-tests-names)
138491

139492
#### Feature Names
140493

141-
Every feature should:
494+
For this feature as a base:
495+
496+
`HTTPRouteExtAuth`
497+
498+
For supporting talking to ext_auth servers using the gRPC ext_auth protocol:
499+
500+
`HTTPRouteExtAuthGRPC`
501+
502+
For supporting talking to ext_auth servers using HTTP:
503+
504+
`HTTPRouteExtAuthHTTP`
505+
506+
For forwarding the body of the client request to the authorization server
507+
508+
`HTTPRouteExtAuthForwardBody`
142509

143-
1. Start with the resource name. i.e HTTPRouteXXX
144-
2. Follow the PascalCase convention. Note that the resource name in the string should come as is and not be converted to PascalCase, i.e HTTPRoutePortRedirect and not HttpRoutePortRedirect.
145-
3. Not exceed 128 characters.
146-
4. Contain only letters and numbers
147510

148511
### Conformance tests
149512

0 commit comments

Comments
 (0)