GEP-0012: OIDC Webhook Authenticator ​
Problem ​
In Kubernetes, you can authenticate via several authentication strategies:
- x509 Client Certificates
- Static Token Files
- Bootstrap Tokens
- Static Password File (Basic authentication - deprecated and removed in 1.19)
- Service Account Tokens
- OpenID Connect Tokens
- Webhook Token Authentication
- Authenticating Proxy
End-users should use OpenID Connect (OIDC) Tokens created by OIDC-compatible Identity Provider (IDP) and present id_token to the kube-apiserver. If the kube-apiserver is configured to trust the IDP and the token is valid, then the user is authenticated and the UserInfo is sent to the authorization stack.
Ideally, operators of the Gardener cluster should be able to authenticate to end-user shoot clusters with id_token generated by OIDC IDP, but in many cases, end-users might have already configured OIDC for their cluster and more than one OIDC configurations are not allowed.
Another interesting application of multiple OIDC providers would be per Project OIDC provider, where end-users of Gardener can add their own OIDC-compatible IDPs.
To workaround the one OIDC per kube-apiserver limitation, a new OIDC Webhook Authenticator (OWA) could be implemented.
Goals ​
- Dynamic registrations of OpenID Connect configurations.
- Close as possible to the Kubernetes build-in OIDC Authenticator.
- Build as an optional extension and not required for functional shoot or Gardener cluster.
Non-Goals ​
- Dynamic Authorization is out of scope.
Proposal ​
The kube-apiserver can use Webhook Token Authentication to send a Bearer Tokens (id_token) to the external webhook for validation:
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"spec": {
"token": "(BEARERTOKEN)"
}
}Where upon verification, the remote webhook returns the identity of the user (if authentication succeeds):
{
"apiVersion": "authentication.k8s.io/v1beta1",
"kind": "TokenReview",
"status": {
"authenticated": true,
"user": {
"username": "janedoe@example.com",
"uid": "42",
"groups": [
"developers",
"qa"
],
"extra": {
"extrafield1": [
"extravalue1",
"extravalue2"
]
}
}
}
}Registration of New OpenIDConnect ​
This new OWA can be configured with multiple OIDC providers and the entire flow can look like this:
Admin adds a new
OpenIDConnectresource (via CRD) to the cluster.yamlapiVersion: authentication.gardener.cloud/v1alpha1 kind: OpenIDConnect metadata: name: foo spec: issuerURL: https://foo.bar clientID: some-client-id usernameClaim: email usernamePrefix: "test-" groupsClaim: groups groupsPrefix: "baz-" supportedSigningAlgs: - RS256 requiredClaims: baz: bar caBundle: LS0tLS1CRUdJTiBDRVJU...base64-encoded CA certs for issuerURL.OWA watches for changes on this resource and does OIDC discovery. The OIDC provider's configuration has to be accessible under the
spec.issuerURLwith a well-known path (.well-known/openid-configuration).OWA uses the
jwks_uriobtained from the OIDC providers configuration, to fetch the OIDC provider's public keys from that endpoint.OWA uses those keys, issuer, client_id and other settings to add an OIDC authenticator to an in-memory list of Token Authenticators.
End-User Authentication via New OpenIDConnect IDP ​
When a user presents an id_token obtained from a OpenID Connect, the flow looks like this:
The user authenticates against a Custom IDP.
id_tokenis obtained from the Custom IDP.The user uses
id_tokento perform an API call tokube-apiserver.As the
id_tokenis not matched by any build-in or configured authenticators in thekube-apiserver, it is send to OWA for validation.json{ "TokenReview": { "kind": "TokenReview", "apiVersion": "authentication.k8s.io/v1beta1", "spec": { "token": "ddeewfwef..." } } }OWA uses
TokenReviewto authenticate the calling API server (thekube-apiserverfor delegation of authentication and authorization may be different from the callingkube-apiserver).json{ "TokenReview": { "kind": "TokenReview", "apiVersion": "authentication.k8s.io/v1beta1", "spec": { "token": "api-server-token..." } } }After the Authentication API server returns the identity of the calling API server:
json{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenReview", "metadata": { "creationTimestamp": null }, "spec": { "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InJocEdLTXZlYjV1OE5heD..." }, "status": { "authenticated": true, "user": { "groups": [ "system:serviceaccounts", "system:serviceaccounts:shoot--abcd", "system:authenticated" ], "uid": "14db103e-88bb-4fb3-8efd-ca9bec91c7bf", "username": "system:serviceaccount:shoot--abcd:kube-apiserver" } } }OWA makes a
SubjectAccessReviewcall to the Authorization API server to ensure that calling API server is allowed to validate tokens:json{ "apiVersion": "authorization.k8s.io/v1", "kind": "SubjectAccessReview", "spec": { "groups": [ "system:serviceaccounts", "system:serviceaccounts:shoot--abcd", "system:authenticated" ], "nonResourceAttributes": { "path": "/validate-token", "verb": "post" }, "user": "system:serviceaccount:shoot--abcd:kube-apiserver" }, "status": { "allowed": true, "reason": "RBAC: allowed by RoleBinding \"kube-apiserver\" of ClusterRole \"kube-apiserver\" to ServiceAccount \"system:serviceaccount:shoot--abcd:kube-apiserver\"" } }OWA then iterates over all registered
OpenIDConnectToken authenticators and tries to validate the token.Upon a successful validation, it returns the
TokeReviewwith user, groups, and extra parameters:json{ "TokenReview": { "kind": "TokenReview", "apiVersion": "authentication.k8s.io/v1beta1", "spec": { "token": "ddeewfwef..." }, "status": { "authenticated": true, "user": { "username": "test-foo@bar.com", "groups": [ "baz-employee" ], "extra": { "gardener.cloud/authenticator/name": [ "foo" ], "gardener.cloud/authenticator/uid": [ "e5062528-e5a4-4b97-ad83-614d015b0979" ] } } } } }It also adds some extra information which can be used by custom authorizers later on:
gardener.cloud/authenticator/namecontains the name of theOpenIDConnectauthenticator which was used.gardener.cloud/authenticator/uidcontains themetadata.uidof theOpenIDConnectauthenticator which was used.
The
kube-apiserverproceeds with authorization checks and returns a response.
An overview of the flow:
Deployment for Shoot Clusters ​
OWA can be deployed per shoot cluster via the Shoot OIDC Service Extension. The shoot's kube-apiserver is mutated so that it has the following flag configured.
--authentication-token-webhook-config-file=/etc/webhook/kubeconfigOWA, on the other hand, uses the shoot's kube-apiserver and delegates auth capabilities to it. This means that the needed RBAC is managed in the shoot cluster. By default only the shoot's kube-apiserver has permissions to validate tokens against OWA.