|
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,383 @@ 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 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. |
134 | 487 |
|
135 | 488 | ## Conformance Details
|
136 | 489 |
|
137 | 490 | (from https://github.com/kubernetes-sigs/gateway-api/blob/main/geps/gep-2162/index.md#standardize-features-and-conformance-tests-names)
|
138 | 491 |
|
139 | 492 | #### Feature Names
|
140 | 493 |
|
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` |
142 | 509 |
|
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 | 510 |
|
148 | 511 | ### Conformance tests
|
149 | 512 |
|
|
0 commit comments