From 0dfff264104c7b88b7f99c3cd5d54d45b725c564 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Wed, 22 May 2019 09:38:08 -0500 Subject: [PATCH 1/2] Add a chapter showing how to use the CSR API This is a rather convoluted example, showing step by step how to build a system where each user gets a ServiceAcccount and token with limited access, and can use this token to submit a CSR that will give them a short-lived certificate. Even if this is not a 100% realistic scenario, the general idea (using a "long-term" password or token to obtain a "short-term" token) is used by many other systems, so it makes sense to get acquainted with the various moving parts. --- k8s/users:jean.doe.yaml | 33 +++ slides/k8s/authn-authz.md | 20 +- slides/k8s/csr-api.md | 426 ++++++++++++++++++++++++++++++++++++++ slides/kube-fullday.yml | 1 + slides/kube-selfpaced.yml | 1 + slides/kube-twodays.yml | 1 + 6 files changed, 473 insertions(+), 9 deletions(-) create mode 100644 k8s/users:jean.doe.yaml create mode 100644 slides/k8s/csr-api.md 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..a770e267 --- /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 papers") + +- 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 a 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 110c1b60..4c9789ac 100644 --- a/slides/kube-fullday.yml +++ b/slides/kube-fullday.yml @@ -56,6 +56,7 @@ chapters: # - k8s/namespaces.md # - k8s/netpol.md # - k8s/authn-authz.md +# - k8s/csr-api.md #- - k8s/ingress.md # - k8s/gitworkflows.md - k8s/prometheus.md diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml index 358f5925..5e76c457 100644 --- a/slides/kube-selfpaced.yml +++ b/slides/kube-selfpaced.yml @@ -56,6 +56,7 @@ chapters: - k8s/namespaces.md - k8s/netpol.md - k8s/authn-authz.md + - k8s/csr-api.md - - k8s/ingress.md - k8s/gitworkflows.md - k8s/prometheus.md diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml index 61aaac18..7dadbff1 100644 --- a/slides/kube-twodays.yml +++ b/slides/kube-twodays.yml @@ -56,6 +56,7 @@ chapters: - k8s/namespaces.md - k8s/netpol.md - k8s/authn-authz.md + - k8s/csr-api.md - - k8s/ingress.md #- k8s/gitworkflows.md - k8s/prometheus.md From f69c9853bb39d0a8dc7ce404209c282547351c04 Mon Sep 17 00:00:00 2001 From: Jerome Petazzoni Date: Fri, 24 May 2019 19:36:03 -0500 Subject: [PATCH 2/2] More typos --- slides/k8s/csr-api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slides/k8s/csr-api.md b/slides/k8s/csr-api.md index a770e267..aec66fdc 100644 --- a/slides/k8s/csr-api.md +++ b/slides/k8s/csr-api.md @@ -24,7 +24,7 @@ - When we access an https:// URL, the server authenticates itself - (it proves its identity to us; as if it were "showing its papers") + (it proves its identity to us; as if it were "showing its ID") - But we can also have mutual TLS authentication (mTLS) @@ -94,7 +94,7 @@ The CA (or anyone else) never needs to know my private key. ## Using the CSR API -- We will show a how to use the CSR API to obtain user certificates +- We will show how to use the CSR API to obtain user certificates - This will be a rather complex demo @@ -230,7 +230,7 @@ For a user named `jean.doe`, we will have: - 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!) + (many people prefer cfssl, easyrsa, or other tools; that's fine too!) .exercise[