diff --git a/k8s/users:jean.doe.yaml b/k8s/users:jean.doe.yaml
new file mode 100644
index 00000000..ef96d39e
--- /dev/null
+++ b/k8s/users:jean.doe.yaml
@@ -0,0 +1,33 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: jean.doe
+ namespace: users
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: users:jean.doe
+rules:
+- apiGroups: [ certificates.k8s.io ]
+ resources: [ certificatesigningrequests ]
+ verbs: [ create ]
+- apiGroups: [ certificates.k8s.io ]
+ resourceNames: [ users:jean.doe ]
+ resources: [ certificatesigningrequests ]
+ verbs: [ get, create, delete, watch ]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: users:jean.doe
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: users:jean.doe
+subjects:
+- kind: ServiceAccount
+ name: jean.doe
+ namespace: users
+
diff --git a/slides/k8s/authn-authz.md b/slides/k8s/authn-authz.md
index d4dd70bf..30c77d3c 100644
--- a/slides/k8s/authn-authz.md
+++ b/slides/k8s/authn-authz.md
@@ -143,19 +143,21 @@ class: extra-details
(see issue [#18982](https://github.com/kubernetes/kubernetes/issues/18982))
-- As a result, we cannot easily suspend a user's access
+- As a result, we don't have an easy way to terminate someone's access
-- There are workarounds, but they are very inconvenient:
+ (if their key is compromised, or they leave the organization)
- - issue short-lived certificates (e.g. 24 hours) and regenerate them often
+- Option 1: re-create a new CA and re-issue everyone's certificates
+
+ → Maybe OK if we only have a few users; no way otherwise
- - re-create the CA and re-issue all certificates in case of compromise
+- Option 2: don't use groups; grant permissions to individual users
+
+ → Inconvenient if we have many users and teams; error-prone
- - grant permissions to individual users, not groups
-
- (and remove all permissions to a compromised user)
-
-- Until this is fixed, we probably want to use other methods
+- Option 3: issue short-lived certificates (e.g. 24 hours) and renew them often
+
+ → This can be facilitated by e.g. Vault or by the Kubernetes CSR API
---
diff --git a/slides/k8s/csr-api.md b/slides/k8s/csr-api.md
new file mode 100644
index 00000000..aec66fdc
--- /dev/null
+++ b/slides/k8s/csr-api.md
@@ -0,0 +1,426 @@
+# The CSR API
+
+- The Kubernetes API exposes CSR resources
+
+- We can use these resources to issue TLS certificates
+
+- First, we will go through a quick reminder about TLS certificates
+
+- Then, we will see how to obtain a certificate for a user
+
+- We will use that certificate to authenticate with the cluster
+
+- Finally, we will grant some privileges to that user
+
+---
+
+## Reminder about TLS
+
+- TLS (Transport Layer Security) is a protocol providing:
+
+ - encryption (to prevent eavesdropping)
+
+ - authentication (using public key cryptography)
+
+- When we access an https:// URL, the server authenticates itself
+
+ (it proves its identity to us; as if it were "showing its ID")
+
+- But we can also have mutual TLS authentication (mTLS)
+
+ (client proves its identity to server; server proves its identity to client)
+
+---
+
+## Authentication with certificates
+
+- To authenticate, someone (client or server) needs:
+
+ - a *private key* (that remains known only to them)
+
+ - a *public key* (that they can distribute)
+
+ - a *certificate* (associating the public key with an identity)
+
+- A message encrypted with the private key can only be decrypted with the public key
+
+ (and vice versa)
+
+- If I use someone's public key to encrypt / decrypt their messages,
+
+ I can be certain that I am talking to them / they are talking to me
+
+- The certificate proves that I have the correct public key for them
+
+---
+
+## Certificate generation workflow
+
+This is what I do if I want to obtain a certificate.
+
+1. Create public and private key.
+
+2. Create a Certificate Signing Request (CSR).
+
+ (The CSR contains the identity that I claim and an expiration date.)
+
+3. Send that CSR to the Certificate Authority (CA).
+
+4. The CA verifies that I can claim the identity in the CSR.
+
+5. The CA generates my certificate and gives it to me.
+
+The CA (or anyone else) never needs to know my private key.
+
+---
+
+## The CSR API
+
+- The Kubernetes API has a CertificateSigningRequest resource type
+
+ (we can list them with e.g. `kubectl get csr`)
+
+- We can create a CSR object
+
+ (= upload a CSR to the Kubernetes API)
+
+- Then, using the Kubernetes API, we can approve / deny the request
+
+- If we approve the request, the Kubernetes API generates a certificate
+
+- The certificate gets attached to the CSR object and can be retrieved
+
+---
+
+## Using the CSR API
+
+- We will show how to use the CSR API to obtain user certificates
+
+- This will be a rather complex demo
+
+- ... And yet, we will take a few shortcuts to simplify it
+
+ (but it will illustrate the general idea)
+
+- The demo also won't be automated
+
+ (we would have to write extra code to make it fully functional)
+
+---
+
+## General idea
+
+- We will create a Namespace named "users"
+
+- Each user will get a ServiceAccount in that Namespace
+
+- That ServiceAccount will give read/write access to *one* CSR object
+
+- Users will use that ServiceAccount's token to submit a CSR
+
+- We will approve the CSR (or not)
+
+- Users can then retrieve their certificate from their CSR object
+
+- ... And use that certificate for subsequent interactions
+
+---
+
+## Resource naming
+
+For a user named `jean.doe`, we will have:
+
+- ServiceAccount `jean.doe` in Namespace `users`
+
+- CertificateSigningRequest `users:jean.doe`
+
+- ClusterRole `users:jean.doe` giving read/write access to that CSR
+
+- ClusterRoleBinding `users:jean.doe` binding ClusterRole and ServiceAccount
+
+---
+
+## Creating the user's resources
+
+.warning[If you want to use another name than `jean.doe`, update the YAML file!]
+
+.exercise[
+
+- Create the global namespace for all users:
+ ```bash
+ kubectl create namespace users
+ ```
+
+- Create the ServiceAccount, ClusterRole, ClusterRoleBinding for `jean.doe`:
+ ```bash
+ kubectl apply -f ~/container.training/k8s/users:jean.doe.yaml
+ ```
+
+]
+
+---
+
+## Extracting the user's token
+
+- Let's obtain the user's token and give it to them
+
+ (the token will be their password)
+
+.exercise[
+
+- List the user's secrets:
+ ```bash
+ kubectl --namespace=users describe serviceaccount jean.doe
+ ```
+
+- Show the user's token:
+ ```bash
+ kubectl --namespace=users describe secret `jean.doe-token-xxxxx`
+ ```
+
+]
+
+---
+
+## Configure `kubectl` to use the token
+
+- Let's create a new context that will use that token to access the API
+
+.exercise[
+
+- Add a new identity to our kubeconfig file:
+ ```bash
+ kubectl config set-credentials token:jean.doe --token=...
+ ```
+
+- Add a new context using that identity:
+ ```bash
+ kubectl config set-context jean.doe --user=token:jean.doe --cluster=kubernetes
+ ```
+
+]
+
+---
+
+## Access the API with the token
+
+- Let's check that our access rights are set properly
+
+.exercise[
+
+- Try to access any resource:
+ ```bash
+ kubectl get pods
+ ```
+ (This should tell us "Forbidden")
+
+- Try to access "our" CertificateSigningRequest:
+ ```bash
+ kubectl get csr users:jean.doe
+ ```
+ (This should tell us "NotFound")
+
+]
+
+---
+
+## Create a key and a CSR
+
+- There are many tools to generate TLS keys and CSRs
+
+- Let's use OpenSSL; it's not the best one, but it's installed everywhere
+
+ (many people prefer cfssl, easyrsa, or other tools; that's fine too!)
+
+.exercise[
+
+- Generate the key and certificate signing request:
+ ```bash
+ openssl req -newkey rsa:2048 -nodes -keyout key.pem \
+ -new -subj /CN=jean.doe/O=devs/ -out csr.pem
+ ```
+
+]
+
+The command above generates:
+
+- a 2048-bit RSA key, without DES encryption, stored in key.pem
+- a CSR for the name `jean.doe` in group `devs`
+
+---
+
+## Inside the Kubernetes CSR object
+
+- The Kubernetes CSR object is a thin wrapper around the CSR PEM file
+
+- The PEM file needs to be encoded to base64 on a single line
+
+ (we will use `base64 -w0` for that purpose)
+
+- The Kubernetes CSR object also needs to list the right "usages"
+
+ (these are flags indicating how the certificate can be used)
+
+---
+
+## Sending the CSR to Kubernetes
+
+.exercise[
+
+- Generate and create the CSR resource:
+ ```bash
+ kubectl apply -f - < cert.pem
+ ```
+
+- Inspect the certificate:
+ ```bash
+ openssl x509 -in cert.pem -text -noout
+ ```
+
+]
+
+---
+
+## Using the certificate
+
+.exercise[
+
+- Add the key and certificate to kubeconfig:
+ ```bash
+ kubectl config set-credentials cert:jean.doe --embed-certs \
+ --client-certificate=cert.pem --client-key=key.pem
+ ```
+
+- Update the user's context to use the key and cert to authenticate:
+ ```bash
+ kubectl config set-context jean.doe --user cert:jean.doe
+ ```
+
+- Confirm that we are seen as `jean.doe` (but don't have permissions):
+ ```bash
+ kubectl get pods
+ ```
+
+]
+
+---
+
+## What's missing?
+
+We shown, step by step, a method to issue short-lived certificates for users.
+
+To be usable in real environments, we would need to add:
+
+- a kubectl helper to automatically generate the CSR and obtain the cert
+
+ (and transparently renew the cert when needed)
+
+- a Kubernetes controller to automatically validate and approve CSRs
+
+ (checking that the subject and groups are valid)
+
+- a way for the users to know the groups to add to their CSR
+
+ (e.g.: annotations on their ServiceAccount + read access to the ServiceAccount)
+
+---
+
+## Is this realistic?
+
+- Larger organizations typically integrate with their own directory
+
+- The general principle, however, is the same:
+
+ - users have long-term credentials (password, token, ...)
+
+ - they use these credentials to obtain other, short-lived credentials
+
+- This provides enhanced security:
+
+ - the long-term credentials can use long passphrases, 2FA, HSM ...
+
+ - the short-term credentials are more convenient to use
+
+ - we get strong security *and* convenience
+
+- Systems like Vault also have certificate issuance mechanisms
diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml
index 33b85dfa..f5462d85 100644
--- a/slides/kube-fullday.yml
+++ b/slides/kube-fullday.yml
@@ -57,6 +57,7 @@ chapters:
- k8s/logs-centralized.md
#- k8s/netpol.md
#- k8s/authn-authz.md
+ #- k8s/csr-api.md
#- k8s/podsecuritypolicy.md
#- k8s/ingress.md
#- k8s/gitworkflows.md
diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml
index bb58f76c..932754f1 100644
--- a/slides/kube-selfpaced.yml
+++ b/slides/kube-selfpaced.yml
@@ -57,6 +57,7 @@ chapters:
- k8s/logs-centralized.md
- k8s/netpol.md
- k8s/authn-authz.md
+ - k8s/csr-api.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
- k8s/gitworkflows.md
diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml
index 95a0e001..e338b627 100644
--- a/slides/kube-twodays.yml
+++ b/slides/kube-twodays.yml
@@ -57,6 +57,7 @@ chapters:
- k8s/logs-centralized.md
#- k8s/netpol.md
- k8s/authn-authz.md
+ - k8s/csr-api.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
#- k8s/gitworkflows.md