[Feature]: Gateway API `ExternalAuth` Compatibility
Motivation
In a recent release Cilium has gained support for the ExternalAuth filter (defined in GEP-1494). The filter is a forward-auth style filter for the HTTPRoute resource in the Kubernetes Gateway API. In Cilium it is implemented via Envoy's ext_authz. Today I tried setting up the filter with oauth2-proxy.
Unfortunately I wasn't able to achieve the setup I wanted: I wanted a single oauth2-proxy that I could then use via the ExternalAuth filter on multiple HTTPRoutes.
values.yaml & Example HTTPRoutereplicaCount: 2
config:
existingSecret: oauth2-proxy
configFile: |-
redirect_url = "https://auth.public.host/oauth2/callback"
skip_provider_button = true
email_domains = [ "*" ]
cookie_domains = [ ".public.host" ]
whitelist_domains = [ ".public.host" ]
# Envoy pods run with hostNetwork, so the source IP range is the node IP range
reverse_proxy = true
real_client_ip_header = "X-Forwarded-For"
trusted_proxy_ips = [ "10.0.10.0/24" ]
# Logging
silence_ping_logging = true
alphaConfig:
enabled: true
configData:
providers:
- id: keycloak
provider: keycloak-oidc
clientID: ${OAUTH2_PROXY_CLIENT_ID}
clientSecret: ${OAUTH2_PROXY_CLIENT_SECRET}
oidcConfig:
issuerURL: https://login.public.host/realms/myrealm
upstreamConfig:
upstreams:
- id: ok
path: /
static: true
injectResponseHeaders:
- name: X-Auth-Request-User
values: [claimSource: { claim: user }]
- name: X-Auth-Request-Email
values: [claimSource: { claim: email }]
gatewayApi:
enabled: true
hostnames: [auth.public.host]
gatewayRef:
group: gateway.networking.k8s.io
kind: Gateway
name: publicapiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: myroute }
spec:
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: internal
hostnames: [my.internal.host]
rules:
- backendRefs:
- name: mysvc
port: 8080
filters:
- type: ExternalAuth
externalAuth:
protocol: HTTP
backendRef:
name: oauth2-proxy
namespace: ingress
port: 443
http:
path: /oauth2/auth
allowedHeaders:
- Cookie
- X-Forwarded-Proto
- X-Forwarded-Host
- X-Forwarded-Uri
allowedResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- Set-Cookie
The Problem
The core problem is that Cilium's ExternalAuth implementation does not send X-Forwarded-* headers (or any other headers that oauth2-proxy understands). The solution in other issues (e.g. #2832, #862) was to modify the Envoy config to include such headers. But in this case the problem is not necessarily implementation-specific. The Gateway API defines the ExternalAuth interface and does not currently include a way to configure the required headers. Although my specific use case involves Cilium, this feature request is related more to compatibility with the Gateway API design.
Possible solution
Context
Gateway API defines the ExternalAuth filter here. The definition includes:
The following headers must always be sent to the authorization server, regardless of this setting: Host, Method, Path, Content-Length, Authorization
This means that the gateway will send basically the same request it received to oauth2-proxy (including the original Host, HTTP method and request path). No headers added.
This is almost compatible with oauth2-proxy when running with reverse_proxy = false. In this mode oauth2-proxy will successfully trigger the OAuth flow and prompt the user to authenticate. The IdP will successfully redirect to the redirect-uri of oauth2-proxy. But now oauth2-proxy assumes (because of reverse_proxy = false) that the final target resource lives on the same host as itself. In practice this means:
- User requests
GET host.example.com/foobar ExternalAuthsendsGET host.example.com/foobartooauth2-proxyoauth2-proxyresponds with 302 redirect to IdPExternalAuthreceives 302 and forwards it to the user.- The user completes authentication and is redirected to
oauth2-proxy.example.com/oauth2/callback oauth2-proxyfinishes the OAuth token exchange and redirects tooauth2-proxy.example.com/foobar
Except for the very last step this all works as expected.
Proposed Solution
I traced the redirection behavior down to appDirector.GetRedirect and from there to
In reverse_proxy = false mode, the redirect URI will be set to the path of the request. I propose the following change:
if redirect == "" {
- redirect = req.URL.RequestURI()
+ redirect = req.URL.String()
}This includes the full original request URL in the OAuth state and would allow oauth2-proxy to redirect to the correct URL for ExternalAuth. Including a full URL is consistent with the X-Forwarded-Uri header which can also include a full URL.
Additional Thoughts:
- I haven't thought about the security implications of the proposed change. It may make sense to guard this behind a config flag. This flag would basically enable
oauth2-proxyto be a proxy for arbitrary hosts, not just one single host (related: #749) - Also from a security POV, maybe
fmt.Sprintf("%s://%s%s", req.URL.Scheme, req.URL.Host, req.URL. RequestURI())may make more sense compared toreq.URL.String() - An alternative solution to my specific problem could of course also be to open a feature request with Cilium and/or the Gateway API. I decided against that for now because I feel that it's unlikely the
ExternalAuthwill change in that way. - A different alternative could be to run one
oauth2-proxyperHTTPRoute(or rather: per host). This is impractical in my case because of a relatively large number of hosts.
Provider
None
Source: oauth2-proxy/oauth2-proxy