OpenID Connect Webhook Authenticator for Kubernetes
Table of content
Overview
The OpenID Connect Webhook Authenticator allows Kubernetes cluster administrators to dynamically register new OpenID Connect providers in their clusters to use for kube-apiserver authentication.
Note: This repository still in
alpha
stage and in active development. It should not be used in production. The API can change without any backwards compatibility.
Background
In Kubernetes, only a single OpenID Connect authenticator can be used for end-users to authenticate.
To workaround this limitations, a Webhook Token Authentication can be configured. The Kube APIServer then sends the Bearer Tokens (id_token) to an 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"
]
}
}
}
}
This repository is the out-of tree implementation of Dynamic Authentication KEP.
Use cases
- Establish trust relationship between different Kubernetes clusters using Service Account Token Volume Projection and Service Account Issuer Discovery.
- Offer cluster admins the option to dynamically allow users from other OIDC IDPs to authenticate against their
kube-apiserver
.
How it works
This webhook is a Kubernetes controller that acts on OpenIDConnect
resources e.g:
apiVersion: 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.
Note: The fields in the specification corresponds to the kube-apiserver OIDC flags.
Registration of a new OpenID Connect provider
The flow is following:
- Admin adds a new
OpenIDConnect
to the cluster. - The webhook controller watches for changes on this resource and does OIDC discovery. The OIDC provider's configuration has to be accessible under the
spec.issuerURL
with a well-known path (.well-known/openid-configuration). - The webhook controller uses the
jwks_uri
obtained from the OIDC providers configuration, to fetch the OIDC provider's public keys from that endpoint. - The webhook controller uses those keys, issuer, client_id and other settings to add OIDC authenticator to a in-memory list of Token Authenticators.
An overview of the controller:
End-user authentication via new OpenIDConnect IDP
When a user wants to authenticate to the kube-apiserver
via this new Custom OpenIDConnect IDP:
-
The user authenticates in Custom IDP.
-
id_token
is obtained from Custom IDP (e.g.ddeewfwef...
). -
The user uses
id_token
to perform an API call to Kube APIServer. -
As the
id_token
is not matched by any build-in or configured authenticators in the Kube APIServer, it is send to OpenID Connect Webhook Authenticator for validation.{ "TokenReview": { "kind": "TokenReview", "apiVersion": "authentication.k8s.io/v1", "spec": { "token": "ddeewfwef..." } } }
-
The webhook uses
TokenReview
to authenticate the calling API server (the Kube APIServer for delegation of authentication and authorization might be different from the calling API server).{ "TokenReview": { "kind": "TokenReview", "apiVersion": "authentication.k8s.io/v1beta1", "spec": { "token": "api-server-token..." } } }
-
After the Authentication API server returns the identity of callee API server. In this case the API server is running as a Pod in a Kubernetes cluster:
{ "apiVersion": "authentication.k8s.io/v1", "kind": "TokenReview", "metadata": { "creationTimestamp": null }, "spec": { "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InJocEdLTXZlYjV1OE5heD..." }, "status": { "authenticated": true, "user": { "groups": [ "system:serviceaccounts", "system:serviceaccounts:some-namespace", "system:authenticated" ], "uid": "14db103e-88bb-4fb3-8efd-ca9bec91c7bf", "username": "system:serviceaccount:some-namespace:kube-apiserver" } } }
-
The webhook makes a
SubjectAccessReview
call to the Authorization API server to ensure that callee API server is allowed to validate tokens:{ "apiVersion": "authorization.k8s.io/v1", "kind": "SubjectAccessReview", "spec": { "groups": [ "system:serviceaccounts", "system:serviceaccounts:some-namespace", "system:authenticated" ], "nonResourceAttributes": { "path": "/validate-token", "verb": "post" }, "user": "system:serviceaccount:some-namespace:kube-apiserver" }, "status": { "allowed": true, "reason": "RBAC: allowed by RoleBinding \"kube-apiserver\" of ClusterRole \"kube-apiserver\" to ServiceAccount \"system:serviceaccount:some-namespace:kube-apiserver\"" } }
-
The webhook then iterates over all registered
OpenIDConnect
Token authenticators and tries to validate the token. -
Upon a successful validation it returns the
TokenReview
with user, groups and extra parameters:{ "TokenReview": { "kind": "TokenReview", "apiVersion": "authentication.k8s.io/v1", "spec": { "token": "ddeewfwef..." }, "status": { "authenticated": true, "user": { "username": "test-admin@example.com", "groups": [ "test-some-group" ], "extra": { "gardener.cloud/apiserver/groups": [ "system:serviceaccounts", "system:serviceaccounts:garden", "system:authenticated" ], "gardener.cloud/apiserver/uid": [ "system:serviceaccount:garden:default" ], "gardener.cloud/apiserver/username": [ "system:serviceaccount:garden:default" ], "gardener.cloud/authenticator/name": [ "gardener" ], "gardener.cloud/authenticator/uid": [ "e5062528-e5a4-4b97-ad83-614d015b0979" ] } } } } }
It adds the following extra information, that can be used by custom authorizers later on:
gardener.cloud/apiserver/groups
contains all the groups of the API server which is making theTokenReview
request (it's the ServiceAccount of the API Server Pod in this case).gardener.cloud/apiserver/uid
contains the UID of the API server which is making theTokenReview
request (it's the ServiceAccount of the API Server Pod in this case).gardener.cloud/apiserver/username
contains the username of the API server which is making theTokenReview
request (it's the ServiceAccount of the API Server Pod in this case).gardener.cloud/authenticator/name
contains the name of theOpenIDConnect
authenticator which was used.gardener.cloud/authenticator/uid
contains the UID of theOpenIDConnect
authenticator which was used.
An overview of the flow:
Docker images
Docker images are available here or you can choose to pull the latest pre-release version with the following command:
docker pull eu.gcr.io/gardener-project/gardener/oidc-webhook-authenticator:latest
Local development
For this setup the following components are needed:
The API server is started with --authentication-token-webhook-config-file
with kubeconfig
file pointing to the Webhook.
mkdir -p ~/.minikube/files/var/lib/minikube/certs
cp config/samples/minikube-webhook-kubeconfig.yaml ~/.minikube/files/var/lib/minikube/certs/minikube-webhook-kubeconfig.yaml
minikube start \
--extra-config=apiserver.authentication-token-webhook-config-file=/var/lib/minikube/certs/minikube-webhook-kubeconfig.yaml \
--extra-config=apiserver.authentication-token-webhook-cache-ttl=10s
To allow easy communication between the kube-apiserver
and the oidc-webhook-authenticator
minikube IP is added as control-plane.minikube.internal
in /etc/hosts
sudo sed -ie '/control-plane.minikube.internal/d' /etc/hosts
echo "$(minikube ip) control-plane.minikube.internal" | sudo tee -a /etc/hosts
Add the CRD:
kubectl apply -f config/crd/bases/authentication.gardener.cloud_openidconnects.yaml
Build the image, so it's accessible by minikube
:
minikube image build -t oidc-webhook-authenticator .
Deploy the oidc webhook authenticator.
kubectl apply -f config/samples/deployment.yaml
Create an OpenIDConnect
resource configured with your identity provider's settings (see an example here). Get a token from your identity provider. You can now authenticate against the minikube cluster.
curl -k -H "Authorization: Bearer $MY_TOKEN" $(k config view -o=jsonpath="{.clusters[?(@.name=='minikube')].cluster.server}")
Alternatively you can also use a token kubeconfig or the kubelogin plugin and configure an OIDC kubeconfig.