caulagi / oidc-webhook-authenticator

Kubernetes Webhook Authenticator that allows for dynamic registration of OpenID Connect providers

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

OpenID Connect Webhook Authenticator for Kubernetes

reuse compliant

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

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:

  1. Admin adds a new OpenIDConnect to the cluster.
  2. 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).
  3. The webhook controller uses the jwks_uri obtained from the OIDC providers configuration, to fetch the OIDC provider's public keys from that endpoint.
  4. 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:

alt text

End-user authentication via new OpenIDConnect IDP

When a user wants to authenticate to the kube-apiserver via this new Custom OpenIDConnect IDP:

  1. The user authenticates in Custom IDP.

  2. id_token is obtained from Custom IDP (e.g. ddeewfwef...).

  3. The user uses id_token to perform an API call to Kube APIServer.

  4. 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..."
        }
      }
    }
  5. 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..."
        }
      }
    }
  6. 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"
            }
        }
    }
  7. 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\""
      }
    }
  8. The webhook then iterates over all registered OpenIDConnect Token authenticators and tries to validate the token.

  9. 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 the TokenReview 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 the TokenReview 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 the TokenReview request (it's the ServiceAccount of the API Server Pod in this case).
  • gardener.cloud/authenticator/name contains the name of the OpenIDConnect authenticator which was used.
  • gardener.cloud/authenticator/uid contains the UID of the OpenIDConnect authenticator which was used.

An overview of the flow:

alt text

Docker images

Pre-release docker image is available at

eu.gcr.io/gardener-project/gardener/oidc-webhook-authenticator:latest
eu.gcr.io/gardener-project/gardener/oidc-webhook-authenticator:v0.1.0-dev-18eb190a4704e97cf995f33c3fa6b65e1a973358

Local development

For this setup several 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.service-account-api-audiences=api \
  --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 authentication via kubelogin the minikube IP is added as control-plane.minikube.internal in /etc/hosts

sudo sed -ie '/control-plane.minikube.internal/d' /etc/hosts
sudo echo "$(minikube ip) control-plane.minikube.internal" >> /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:

eval $(minikube docker-env)
make docker-build

Deploy Dex and the oidc webhook authenticator:

kubectl apply -f config/samples/deployment.yaml

Add an additional user with Dex authentication to the kubeconfig and use it as current context

kubectl config set-credentials minikube-dex \
--exec-command=kubectl \
--exec-arg=oidc-login \
--exec-arg=get-token \
--exec-arg=--certificate-authority=$(readlink -f cfssl/ca.crt) \
--exec-arg=--oidc-issuer-url=https://control-plane.minikube.internal:31133 \
--exec-arg=--oidc-client-id=oidc-webhook \
--exec-arg=--oidc-client-secret=ZXhhbXBsZS1hcHAtc2VjcmV0 \
--exec-arg=--oidc-extra-scope=email \
--exec-arg=--oidc-extra-scope=profile \
--exec-arg=--oidc-extra-scope=groups \
--exec-arg=--grant-type=auto \
--exec-api-version=client.authentication.k8s.io/v1beta1

kubectl config set-context minikube-dex --user=minikube-dex --cluster=minikube
kubectl config use-context minikube-dex

Try to authenticate - when a browser pops up, use admin@example.com / password as email and password:

kubectl get pods
error: You must be logged in to the server (Unauthorized)

The authentication to the apiserver fails. Now apply the OpenIDConneect resource:

kubectl --context=minikube apply -f config/samples/authentication_v1alpha1_openidconnect.yaml

Wait a bit (the kube-apiserver caches the authentication response for 10s) and try to execute kubectl commands again:

kubectl get pods
Error from server (Forbidden): pods is forbidden: User "dex/admin@example.com" cannot list resource "pods" in API group "" in the namespace "default"

You have successfully authenticated (you can add some RBAC rules to allow that user to perform some operations).

About

Kubernetes Webhook Authenticator that allows for dynamic registration of OpenID Connect providers

License:Other


Languages

Language:Go 89.7%Language:Makefile 4.1%Language:Dockerfile 2.2%Language:Smarty 2.0%Language:Shell 2.0%