# OpenID Connect

- The Kubernetes API server can perform authentication with OpenID connect

- This requires an *OpenID provider*

  (external authorization server using the OAuth 2.0 protocol)

- We can use a third-party provider (e.g. Google) or run our own (e.g. Dex)

- We are going to give an overview of the protocol

- We will show it in action (in a simplified scenario)

---

## Workflow overview

- We want to access our resources (a Kubernetes cluster)

- We authenticate with the OpenID provider

  - we can do this directly (e.g. by going to https://accounts.google.com)

  - or maybe a kubectl plugin can open a browser page on our behalf

- After authenticating us, the OpenID provider gives us:

  - an *id token* (a short-lived signed JSON Web Token, see next slide)

  - a *refresh token* (to renew the *id token* when needed)

- We can now issue requests to the Kubernetes API with the *id token*

- The API server will verify that token's content to authenticate us

---

## JSON Web Tokens

- A JSON Web Token (JWT) has three parts:

  - a header specifying algorithms and token type

  - a payload (indicating who issued the token, for whom, which purposes...)

  - a signature generated by the issuer (the issuer = the OpenID provider)

- Anyone can verify a JWT without contacting the issuer

  (except to obtain the issuer's public key)

- Pro tip: we can inspect a JWT with https://jwt.io/

---

## How the Kubernetes API uses JWT

- Server side

  - enable OIDC authentication

  - indicate which issuer (provider) should be allowed

  - indicate which audience (or "client id") should be allowed

  - optionally, map or prefix user and group names

- Client side

  - obtain JWT as described earlier

  - pass JWT as authentication token

  - renew JWT when needed (using the refresh token)

---

## Demo time!

- We will use [Google Accounts](https://accounts.google.com) as our OpenID provider

- We will use the [Google OAuth Playground](https://developers.google.com/oauthplayground) as the "audience" or "client id"

- We will obtain a JWT through Google Accounts and the OAuth Playground

- We will enable OIDC in the Kubernetes API server

- We will use the JWT to authenticate

.footnote[If you can't or won't use a Google account, you can try to adapt this to another provider.]

---

## Checking the API server logs

- The API server logs will be particularly useful in this section

  (they will indicate e.g. why a specific token is rejected)

- Let's keep an eye on the API server output!

.exercise[

- Tail the logs of the API server:
  ```bash
  kubectl logs kube-apiserver-node1 --follow --namespace=kube-system
  ```

]

---

## Authenticate with the OpenID provider

- We will use the Google OAuth Playground for convenience

- In a real scenario, we would need our own OAuth client instead of the playground

  (even if we were still using Google as the OpenID provider)

.exercise[

- Open the Google OAuth Playground:
  ```
  https://developers.google.com/oauthplayground/
  ```

- Enter our own custom scope in the text field:
  ```
  https://www.googleapis.com/auth/userinfo.email
  ```

- Click on "Authorize APIs" and allow the playground to access our email address

]

---

## Obtain our JSON Web Token

- The previous step gave us an "authorization code"

- We will use it to obtain tokens

.exercise[

- Click on "Exchange authorization code for tokens"

]

- The JWT is the very long `id_token` that shows up on the right hand side

  (it is a base64-encoded JSON object, and should therefore start with `eyJ`)

---

## Using our JSON Web Token

- We need to create a context (in kubeconfig) for our token

  (if we just add the token or use `kubectl --token`, our certificate will still be used)

.exercise[

- Create a new authentication section in kubeconfig:
  ```bash
  kubectl config set-credentials myjwt --token=eyJ...
  ```

- Try to use it:
  ```bash
  kubectl --user=myjwt get nodes
  ```

]

We should get an `Unauthorized` response, since we haven't enabled OpenID Connect in the API server yet. We should also see `invalid bearer token` in the API server log output.

---

## Enabling OpenID Connect

- We need to add a few flags to the API server configuration

- These two are mandatory:

  `--oidc-issuer-url` → URL of the OpenID provider

  `--oidc-client-id` → app requesting the authentication
  <br/>(in our case, that's the ID for the Google OAuth Playground)

- This one is optional:

  `--oidc-username-claim` → which field should be used as user name
  <br/>(we will use the user's email address instead of an opaque ID)

- See the [API server documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server
) for more details about all available flags

---

## Updating the API server configuration

- The instructions below will work for clusters deployed with kubeadm

  (or where the control plane is deployed in static pods)

- If your cluster is deployed differently, you will need to adapt them

.exercise[

- Edit `/etc/kubernetes/manifests/kube-apiserver.yaml`

- Add the following lines to the list of command-line flags:
  ```yaml
  - --oidc-issuer-url=https://accounts.google.com
  - --oidc-client-id=407408718192.apps.googleusercontent.com
  - --oidc-username-claim=email
  ```
]

---

## Restarting the API server

- The kubelet monitors the files in `/etc/kubernetes/manifests`

- When we save the pod manifest, kubelet will restart the corresponding pod

  (using the updated command line flags)

.exercise[

- After making the changes described on the previous slide, save the file

- Issue a simple command (like `kubectl version`) until the API server is back up

  (it might take between a few seconds and one minute for the API server to restart)

- Restart the `kubectl logs` command to view the logs of the API server

]

---

## Using our JSON Web Token

- Now that the API server is set up to recognize our token, try again!

.exercise[

- Try an API command with our token:
  ```bash
  kubectl --user=myjwt get nodes
  kubectl --user=myjwt get pods
  ```

]

We should see a message like:
```
Error from server (Forbidden): nodes is forbidden: User "jean.doe@gmail.com"
cannot list resource "nodes" in API group "" at the cluster scope
```

→ We were successfully *authenticated*, but not *authorized*.

---

## Authorizing our user

- As an extra step, let's grant read access to our user

- We will use the pre-defined ClusterRole `view`

.exercise[

- Create a ClusterRoleBinding allowing us to view resources:
  ```bash
    kubectl create clusterrolebinding i-can-view \
            --user=`jean.doe@gmail.com` --clusterrole=view
  ```

  (make sure to put *your* Google email address there)

- Confirm that we can now list pods with our token:
   ```bash
  kubectl --user=myjwt get pods
  ```

]

---

## From demo to production

.warning[This was a very simplified demo! In a real deployment...]

- We wouldn't use the Google OAuth Playground

- We *probably* wouldn't even use Google at all

  (it doesn't seem to provide a way to include groups!)

- Some popular alternatives:

  - [Dex](https://github.com/dexidp/dex),
    [Keycloak](https://www.keycloak.org/)
    (self-hosted)

  - [Okta](https://developer.okta.com/docs/how-to/creating-token-with-groups-claim/#step-five-decode-the-jwt-to-verify)
    (SaaS)

- We would use a helper (like the [kubelogin](https://github.com/int128/kubelogin) plugin) to automatically obtain tokens

---

class: extra-details

## Service Account tokens

- The tokens used by Service Accounts are JWT tokens as well

- They are signed and verified using a special service account key pair

.exercise[

- Extract the token of a service account in the current namespace:
  ```bash
  kubectl get secrets -o jsonpath={..token} | base64 -d
  ```

- Copy-paste the token to a verification service like https://jwt.io 

- Notice that it says "Invalid Signature"

]

---

class: extra-details

## Verifying Service Account tokens

- JSON Web Tokens embed the URL of the "issuer" (=OpenID provider)

- The issuer provides its public key through a well-known discovery endpoint

  (similar to https://accounts.google.com/.well-known/openid-configuration)

- There is no such endpoint for the Service Account key pair

- But we can provide the public key ourselves for verification

---

class: extra-details

## Verifying a Service Account token

- On clusters provisioned with kubeadm, the Service Account key pair is:

  `/etc/kubernetes/pki/sa.key` (used by the controller manager to generate tokens)

  `/etc/kubernetes/pki/sa.pub` (used by the API server to validate the same tokens)

.exercise[

- Display the public key used to sign Service Account tokens:
  ```bash
  sudo cat /etc/kubernetes/pki/sa.pub
  ```

- Copy-paste the key in the "verify signature" area on https://jwt.io

- It should now say "Signature Verified"

]

???

:EN:- Authenticating with OIDC
:FR:- S'identifier avec OIDC
