|
1 | 1 | # GEP-1494: HTTP Auth in Gateway API
|
2 | 2 |
|
3 | 3 | * Issue: [#1494](https://github.com/kubernetes-sigs/gateway-api/issues/1494)
|
4 |
| -* Status: Provisional |
| 4 | +* Status: Implementable |
5 | 5 |
|
6 | 6 | (See [status definitions](../overview.md#gep-states).)
|
7 | 7 |
|
@@ -130,20 +130,343 @@ From @ongy, some additional goals to keep in mind:
|
130 | 130 |
|
131 | 131 | ## API
|
132 | 132 |
|
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 can't make explicit config not work - that would |
| 194 | + violate one of the key declarative principles, that what is requested in the |
| 195 | + spec is either what ends up in the state, or that config is rejected. |
| 196 | +* Secondly, filter ordering is particularly important for Auth use cases, so we |
| 197 | + must ensure that when we add Policy defaulting we have a way to indicate at |
| 198 | + what position in a filter list the Auth policy should fit. |
| 199 | + |
| 200 | +### Why two phases? |
| 201 | + |
| 202 | +In short: In the interest of getting something, even if incomplete, into our |
| 203 | +user's hands as quickly as possible. |
| 204 | + |
| 205 | +Policy design is complex, and needs to be done carefully. Doing a first |
| 206 | +pass using only a Filter to get the basic config correct while we discuss |
| 207 | +how to make the Policy handling work means that we can get some work out to the |
| 208 | +community without needing to complete the whole design. |
| 209 | + |
| 210 | +In particular, the design for the Filter plus Policy will need to answer at |
| 211 | +least the following questions: |
| 212 | + |
| 213 | +* How to set where in a list of Filters a defaulted Auth filter sits; |
| 214 | + and what happens if no Filters are specified in a HTTPRoute? Does it go first, |
| 215 | + last, or should there be a way to specify the order? |
| 216 | +* What Policy types are possible? Defaults? (Definitely useful for setting a |
| 217 | + baseline expectation for a cluster, which is desirable for security constructs |
| 218 | + like Auth) Overrides? (Also very useful for ensuring that exceptions meet |
| 219 | + certain requirements - like only allowing the disabling of Auth on `/healthz` |
| 220 | + endpoints or similar use cases.) |
| 221 | +* Should Policy have a way to describe rules around when it should take effect? |
| 222 | + That's in addition to the usual hierarchical rules, should the Policy have ways |
| 223 | + to include or exclude specific matches? This would require agreement in the |
| 224 | + Policy Attachment spec as well. |
| 225 | + |
| 226 | +All of these changes have costs in complexity and troubleshooting difficulty, so |
| 227 | +it's important to ensure that the design consciously makes these tradeoffs. |
| 228 | + |
| 229 | +In particular, the last two items in the above list seem likely to require a fair |
| 230 | +amount of discussion, and including a Policy in the initial release of this |
| 231 | +seems likely to make this change miss its current release window. |
| 232 | + |
| 233 | + |
| 234 | +### Why Envoy's ext_auth? |
| 235 | + |
| 236 | +#### What is ext_auth? |
| 237 | + |
| 238 | +Envoy's External Authorization filter is a filter that calls out to an authorization |
| 239 | +service to check if the incoming request is authorized or not. Note that, in |
| 240 | +order to check _authorization_, it must also be able to determine _authentication_ - |
| 241 | +this is one of the reasons why we've chosen this approach. |
| 242 | + |
| 243 | +Envoy's implementation of this filter allows both a |
| 244 | +[gRPC, protobuf API](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ext_authz/v3/ext_authz.proto) |
| 245 | +and configuration of a HTTP based API (which, as it's not defined using a |
| 246 | +specification like protobuf, requires more configuration). |
| 247 | + |
| 248 | +The important thing to remember here is that the actual authentication and |
| 249 | +authorization processed are delegated to the authorization service - which is |
| 250 | +why the ext_auth approach allows handling many Auth methods - most of the |
| 251 | +work is performed by external services which implement various methods (like |
| 252 | +Basic Auth, OAuth, JWT validation, etc) |
| 253 | + |
| 254 | +#### Why use it over other options? |
| 255 | + |
| 256 | +The community discussed Auth extensively in-person at Kubecon London in early 2025, |
| 257 | +and got broad agreement from multiple dataplanes that: |
| 258 | + |
| 259 | +* something like ext_auth was a good idea, because it's flexible and allows the |
| 260 | + implementation of many types of Auth without specific protocol implementation |
| 261 | + in upstream |
| 262 | +* Envoy's ext_auth protocol has no major problems that would stop us using it |
| 263 | +* Envoy-based implementations mostly already have support for it |
| 264 | + |
| 265 | +At that meeting, those present agreed that ext_auth was a good place to start. |
| 266 | + |
| 267 | +Most non-Envoy dataplanes also already have similar methods, so the maintainers |
| 268 | +of projects using other dataplanes were okay with this idea. |
| 269 | + |
| 270 | +The alternative here would be to add a Filter type _per auth method_, which, given |
| 271 | +the large number of options, could quickly become very complex. |
| 272 | + |
| 273 | +This GEP is, however, explicitly _not_ ruling out the possibility of adding |
| 274 | +specific Filters for specific Auth methods in the future, if users of this API |
| 275 | +find the overhead of running a compatible implementation to be too much. |
| 276 | + |
| 277 | +### API Design |
| 278 | + |
| 279 | +#### Phase 1: Adding a Filter |
| 280 | + |
| 281 | +This config mainly takes inspiration from Envoy's ext_auth filter config, while |
| 282 | +also trying to maintain compatibility with other HTTP methods. |
| 283 | + |
| 284 | +This design is also trying to start with a minimum feature set, and add things |
| 285 | +as required, rather than adding everything configurable in all implementations |
| 286 | +immediately. |
| 287 | + |
| 288 | +There is some difference between data planes, based on the links above, but |
| 289 | +these fields should be broadly supportable across all the listed implementations. |
| 290 | + |
| 291 | +Some design comments are included inline. |
| 292 | + |
| 293 | +##### Go Structs |
| 294 | + |
| 295 | +```go |
| 296 | +// HTTPExtAuthFilter defines a filter that modifies requests by sending |
| 297 | +// request details to an external authorization server. |
| 298 | +// |
| 299 | +// Support: Extended |
| 300 | +// Feature Name: HTTPRouteExtAuth |
| 301 | +type HTTPExtAuthFilter struct { |
| 302 | + |
| 303 | + // ExtAuthProtocol describes which protocol to use when communicating with an |
| 304 | + // ext_auth authorization server. |
| 305 | + // |
| 306 | + // When this is set to GRPC, each backend must use the Envoy ext_auth protocol |
| 307 | + // on the port specified in `backendRefs`. Requests and responses are defined |
| 308 | + // in the protobufs: <link> |
| 309 | + // |
| 310 | + // When this is set to HTTP, each backend must respond with a status code in |
| 311 | + // the `2xx` range on a successful authorization. Any other code is considered |
| 312 | + // an authorization failure. |
| 313 | + // |
| 314 | + // +unionDiscriminator |
| 315 | + // +kubebuilder:validation:Enum=HTTP;GRPC |
| 316 | + ExtAuthProtocol string `json:"protocol"` |
| 317 | + |
| 318 | + // BackendRefs is a list of references to backends to send authorization |
| 319 | + // requests to. |
| 320 | + // |
| 321 | + // If this list contains more than one entry, authorization requests must |
| 322 | + // be load-balanced across all the backends equally. |
| 323 | + // |
| 324 | + // The backends must speak the selected protocol (GRPC or HTTP) on the |
| 325 | + // referenced port. |
| 326 | + // |
| 327 | + // If the backend service requires TLS, use BackendTLSPolicy to tell the |
| 328 | + // implementation to supply the TLS details to be used to connect to that |
| 329 | + // backend. |
| 330 | + // |
| 331 | + // +kubebuilder:validation:MinLength=1 |
| 332 | + // +kubebuilder:validation:MaxLength=8 |
| 333 | + BackendRefs []BackendObjectReference `json:"backendRefs"` |
| 334 | + |
| 335 | + // GRPCAuthConfig contains configuration for communication with ext_auth |
| 336 | + // protocol-speaking backends. |
| 337 | + // |
| 338 | + // If unset, implementations must assume the default behavior for each |
| 339 | + // included field is intended. |
| 340 | + // |
| 341 | + // +optional |
| 342 | + GRPCAuthConfig *GRPCAuthConfig `json:"grpc,omitempty"` |
| 343 | + |
| 344 | + // HTTPAuthConfig contains configuration for communication with HTTP-speaking |
| 345 | + // backends. |
| 346 | + // |
| 347 | + // If unset, implementations must assume the default behavior for each |
| 348 | + // included field is intended. |
| 349 | + // |
| 350 | + // +optional |
| 351 | + HTTPAuthConfig *HTTPAuthConfig `json:"http,omitempty"` |
| 352 | + |
| 353 | + // ForwardBody controls if requests to the authorization server should include |
| 354 | + // the body of the client request; and if so, how big that body is allowed |
| 355 | + // to be. |
| 356 | + // |
| 357 | + // Feature Name: HTTPRouteExtAuthForwardBody |
| 358 | + // |
| 359 | + // GEP Review Notes: |
| 360 | + // Both Envoy and Traefik show support for this feature, but HAProxy and |
| 361 | + // ingress-nginx do not. So this has a separate feature flag for it. |
| 362 | + // |
| 363 | + // +optional |
| 364 | + ForwardBody *ForwardBodyConfig `json:"body,omitempty"` |
| 365 | +} |
| 366 | + |
| 367 | +// GRPCAuthConfig contains configuration for communication with ext_auth |
| 368 | +// protocol-speaking backends. |
| 369 | +type GRPCAuthConfig struct { |
| 370 | + |
| 371 | + // AllowedRequestHeaders specifies what headers from the client request |
| 372 | + // will be sent to the authorization server. |
| 373 | + // |
| 374 | + // If this list is empty, then all headers must be sent. |
| 375 | + // |
| 376 | + // +optional |
| 377 | + // +kubebuilder:validation:MaxLength=64 |
| 378 | + AllowedRequestHeaders []string `json:"allowedHeaders,omitempty"` |
| 379 | +} |
| 380 | + |
| 381 | +// HTTPAuthConfig contains configuration for communication with HTTP-speaking |
| 382 | +// backends. |
| 383 | +type HTTPAuthConfig struct { |
| 384 | + // Path sets the prefix that paths from the client request will have added |
| 385 | + // when forwarrded to the authorization server. |
| 386 | + // |
| 387 | + // When empty or unspecified, no prefix is added. |
| 388 | + // +optional |
| 389 | + Path string `json:"path,omitempty"` |
| 390 | + |
| 391 | + // AllowedRequestHeaders specifies what additional headers from the client request |
| 392 | + // will be sent to the authorization server. |
| 393 | + // |
| 394 | + // The following headers must always be sent to the authorization server, |
| 395 | + // regardless of this setting: |
| 396 | + // |
| 397 | + // * `Host` |
| 398 | + // * `Method` |
| 399 | + // * `Path` |
| 400 | + // * `Content-Length` |
| 401 | + // * `Authorization` |
| 402 | + // |
| 403 | + // If this list is empty, then only those headers must be sent. |
| 404 | + // |
| 405 | + // Nick's GEP Review Notes: |
| 406 | + // Envoy actually does not allow sending all headers to HTTP authorization |
| 407 | + // servers. If the equivalent Envoy field is unspecified, then only |
| 408 | + // `Host`, `Method`, `Path`, `Content-Length` and `Authorization` are included. |
| 409 | + // This is kept for backwards compatibility in Envoy. |
| 410 | + // |
| 411 | + // Traefik includes _all_ headers when the equivalent setting is empty. |
| 412 | + // |
| 413 | + // This definition is the only compromise I can see between the two; Envoy |
| 414 | + // can set this to empty, and Traefik can always explicitly specify those headers |
| 415 | + // at a minimum. |
| 416 | + // |
| 417 | + // +optional |
| 418 | + // +kubebuilder:validation:MaxLength=64 |
| 419 | + AllowedRequestHeaders []string `json:"allowedHeaders,omitempty"` |
| 420 | + |
| 421 | + // AllowedResponseHeaders specifies what headers from the authorization response |
| 422 | + // will be copied into the request to the backend. |
| 423 | + // |
| 424 | + // If this list is empty, then all headers from the authorization server |
| 425 | + // except Authority or Host must be copied. |
| 426 | + // |
| 427 | + // +optional |
| 428 | + // +kubebuilder:validation:MaxLength=64 |
| 429 | + AllowedResponseHeaders []string `json:"allowedResponseHeaders,omitempty"` |
| 430 | + |
| 431 | +} |
| 432 | + |
| 433 | +// ForwardBody configures if requests to the authorization server should include |
| 434 | +// the body of the client request; and if so, how big that body is allowed |
| 435 | +// to be. |
| 436 | +type ForwardBodyConfig struct { |
| 437 | + |
| 438 | + // ForwardBody specifies if the body should be forwarded to the authorization |
| 439 | + // server. If not specified, the body will not be forwarded. |
| 440 | + ForwardBody bool `json:"forward,omitempty"` |
| 441 | + // MaxSize specifies how large the largest body that will be buffered and |
| 442 | + // sent to the authorization |
| 443 | + MaxSize uint16 `json:"maxSize,omitempty"` |
| 444 | +} |
| 445 | + |
| 446 | +``` |
| 447 | +#### YAML Examples |
| 448 | + |
| 449 | +Coming soon. |
| 450 | + |
| 451 | +#### Phase 2: Adding more complex configuration with Policy |
| 452 | + |
| 453 | +This phase is currently undefined until we reach agreement on the Filter + Policy |
| 454 | +approach. |
134 | 455 |
|
135 | 456 | ## Conformance Details
|
136 | 457 |
|
137 | 458 | (from https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-2162/index.md#standardize-features-and-conformance-tests-names)
|
138 | 459 |
|
139 | 460 | #### Feature Names
|
140 | 461 |
|
141 |
| -Every feature should: |
| 462 | +For this feature as a base: |
| 463 | + |
| 464 | +`HTTPRouteExtAuth` |
| 465 | + |
| 466 | +For forwarding the body of the client request to the authorization server |
| 467 | + |
| 468 | +`HTTPRouteExtAuthForwardBody` |
142 | 469 |
|
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 |
147 | 470 |
|
148 | 471 | ### Conformance tests
|
149 | 472 |
|
|
0 commit comments