envoyproxy / envoyproxy/envoy

feat: oauth2 credential injection (better) supports mTLS client credential grants

Open
#39,183 6 comments 0 reactions 0 assignees View on GitHub
area/oauth enhancement no stalebot
Dominant language
C++
Stars
28.9k
Forks
5.6k
Avg merge
1d 22h
Merged PRs (30d)
430

Description

*Title*: oauth2 credential injection (better) supports mTLS client credential grants

*Description*:

With [RFC8705](https://datatracker.ietf.org/doc/html/rfc8705), mTLS can be used as a form of client credentials to be granted an access token. Our use case is we want to use Envoy to inject OAuth granted access tokens in reverse and/or forward proxied requests, but the access tokens can only be granted via a mTLS authenticated client credentials per the security requirements of the (external) upstream services.

In our testing with Keycloak as the OAuth provider support mTLS client credential grants, this works with the following example forward proxy configuration:
```yaml
static_resources:
listeners:
- name: https_listener
address:
socket_address:
protocol: tcp
address: 0.0.0.0
port_value: 80
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_https
access_log:
- name: envoy.access_loggers.stdout
typed_config:
"@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
upgrade_configs:
upgrade_type: CONNECT
route_config:
name: local_route
virtual_hosts:
- name: catch_all
domains: ["*"]
routes:
- match:
connect_matcher: {}
route:
cluster: dynamic_forward_proxy_cluster
upgrade_configs:
upgrade_type: CONNECT
connect_config: {}
- match:
prefix: "/"
route:
cluster: dynamic_forward_proxy_cluster
host_rewrite_header: ":authority"
http_filters:
- name: envoy.filters.http.dynamic_forward_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
dns_cache_config:
name: dynamic_forward_proxy_cache
dns_lookup_family: V4_ONLY
- name: envoy.filters.http.credential_injector
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.credential_injector.v3.CredentialInjector
overwrite: true
allow_request_without_credential: false
credential:
name: envoy.http.injected_credentials.oauth2
typed_config:
"@type": type.googleapis.com/envoy.extensions.http.injected_credentials.oauth2.v3.OAuth2
token_endpoint:
cluster: oauth
uri: https://keycloak.example.com/realms/master/protocol/openid-connect/token
timeout: 3s
client_credentials:
client_id: "test-client"
client_secret:
name: oauth_client_secret
auth_type: URL_ENCODED_BODY
scopes: ["openid"]
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
secrets:
- name: oauth_client_secret
generic_secret:
secret:
inline_string: "unused"
clusters:
- name: oauth
connect_timeout: 2s
type: LOGICAL_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: oauth
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: keycloak.example.com
port_value: 443
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
sni: keycloak.example.com
common_tls_context:
tls_certificates:
- private_key:
filename: /etc/http-proxy/tls/tls.key
certificate_chain:
filename: /etc/http-proxy/tls/tls.crt
- name: dynamic_forward_proxy_cluster
connect_timeout: 2s
lb_policy: cluster_provided
cluster_type:
name: envoy.clusters.dynamic_forward_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
dns_cache_config:
name: dynamic_forward_proxy_cache
dns_lookup_family: V4_ONLY
```

Where `client_secret` is set to an essentially ignored value, and the client `tls_certificate` for mTLS authentication is defined on the TLS transport of the `oauth` cluster.

While this works - `client_secret` being _required_ is awkward and creates for a more difficult UX. Additionally, its possible that an OAuth provider may deny a grant if `client_secret` is set, which is why this feature would be required in order for mTLS grants to be officially supported. Other users with similar needs would also benefit from the documentation / examples required to implement this feature.

I propose a new `MTLS_AUTH` type be introduced for the OAuth credential injector to formally acknowledge this client credential grant use case. Per the RFC, it would include the `client_id` and `grant_type` in the `application/x-www-form-urlencoded` encoded body, but `client_secret` would not be set.

How `client_secret` should be handled within the configuration is a bit unclear to me in terms of whats consistent/idiomatic relative to other Envoy configuration and extensions that require TLS contexts. The config could then either:

1. Have `client_secret` be conditionally required, where it's enforced via code rather than protobuf validation that when `auth_type != MTLS_AUTH` it must be provided. And then when `auth_type == MTLS_AUTH`, it is assumed that a TLS transport with a client certificate (whether statically or dynamically provided) is defined for the underlying cluster - bc otherwise the async access token grant will fail.
2. OR, since `client_secret` is of type `extensions.transport_sockets.tls.v3.SdsSecretConfig`, it could remain required and be that the client certificate is defined within the secret instead - but that feels counterintuitive to how TLS transports are handled within Envoy configuration. I received an error when trying to refer to static secret that had a `tls_certificate` defined rather than a `generic_secret`.

Either way, the `auth_type: "MTLS_AUTH"` feature would provide a clearer configuration and UX for users wanting to inject credentials via this client authentication mechanism. And it would presumably require little additional code to the credential injector since this already "works".

*Relevant Links*:
* https://datatracker.ietf.org/doc/html/rfc8705
* https://github.com/envoyproxy/envoy/issues/21851
* https://github.com/envoyproxy/envoy/issues/25954
* semi-related, but this was more for configuring mTLS as a credential optional on an oauth security filter, rather than credential injector as I've understood.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.