mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-07-24 23:36:46 +00:00
Merge pull request #462 from jpetazzo/user-certificates
Add a chapter showing how to use the CSR API
This commit is contained in:
33
k8s/users:jean.doe.yaml
Normal file
33
k8s/users:jean.doe.yaml
Normal file
@@ -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
|
||||
|
||||
@@ -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
|
||||
<br/>
|
||||
→ 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
|
||||
<br/>
|
||||
→ Inconvenient if we have many users and teams; error-prone
|
||||
|
||||
- grant permissions to individual users, not groups
|
||||
<br/>
|
||||
(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
|
||||
<br/>
|
||||
→ This can be facilitated by e.g. Vault or by the Kubernetes CSR API
|
||||
|
||||
---
|
||||
|
||||
|
||||
426
slides/k8s/csr-api.md
Normal file
426
slides/k8s/csr-api.md
Normal file
@@ -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,
|
||||
<br/>
|
||||
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 - <<EOF
|
||||
apiVersion: certificates.k8s.io/v1beta1
|
||||
kind: CertificateSigningRequest
|
||||
metadata:
|
||||
name: users:jean.doe
|
||||
spec:
|
||||
request: $(base64 -w0 < csr.pem)
|
||||
usages:
|
||||
- digital signature
|
||||
- key encipherment
|
||||
- client auth
|
||||
EOF
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Adjusting certificate expiration
|
||||
|
||||
- By default, the CSR API generates certificates valid 1 year
|
||||
|
||||
- We want to generate short-lived certificates, so we will lower that to 1 hour
|
||||
|
||||
- Fow now, this is configured [through an experimental controller manager flag](https://github.com/kubernetes/kubernetes/issues/67324)
|
||||
|
||||
.exercise[
|
||||
|
||||
- Edit the static pod definition for the controller manager:
|
||||
```bash
|
||||
sudo vim /etc/kubernetes/manifests/kube-controller-manager.yaml
|
||||
```
|
||||
|
||||
- In the list of flags, add the following line:
|
||||
```bash
|
||||
- --experimental-cluster-signing-duration=1h
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Verifying and approving the CSR
|
||||
|
||||
- Let's inspect the CSR, and if it is valid, approve it
|
||||
|
||||
.exercise[
|
||||
|
||||
- Switch back to `cluster-admin`:
|
||||
```bash
|
||||
kctx -
|
||||
```
|
||||
|
||||
- Inspect the CSR:
|
||||
```bash
|
||||
kubectl describe csr users:jean.doe
|
||||
```
|
||||
|
||||
- Approve it:
|
||||
```bash
|
||||
kubectl certificate approve users:jean.doe
|
||||
```
|
||||
|
||||
]
|
||||
|
||||
---
|
||||
|
||||
## Obtaining the certificate
|
||||
|
||||
.exercise[
|
||||
|
||||
- Switch back to the user's identity:
|
||||
```bash
|
||||
kctx -
|
||||
```
|
||||
|
||||
- Retrieve the certificate from the CSR:
|
||||
```bash
|
||||
kubectl get csr users:jean.doe \
|
||||
-o jsonpath={.status.certificate} \
|
||||
| base64 -d > 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user