diff --git a/k8s/certbot.yaml b/k8s/certbot.yaml new file mode 100644 index 00000000..c784cc3a --- /dev/null +++ b/k8s/certbot.yaml @@ -0,0 +1,33 @@ +kind: Service +apiVersion: v1 +metadata: + name: certbot +spec: + ports: + - port: 80 + protocol: TCP +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: certbot +spec: + rules: + - http: + paths: + - path: /.well-known/acme-challenge/ + backend: + serviceName: certbot + servicePort: 80 +--- +apiVersion: v1 +kind: Endpoints +metadata: + name: certbot +subsets: +- addresses: + - ip: A.B.C.D + ports: + - port: 8000 + protocol: TCP + diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index 19a7a427..0e1967ad 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -3,6 +3,10 @@ kind: Ingress metadata: name: whatever spec: + #tls: + #- secretName: whatever.A.B.C.D.nip.io + # hosts: + # - whatever.A.B.C.D.nip.io rules: - host: whatever.A.B.C.D.nip.io http: diff --git a/k8s/traefik-v2.yaml b/k8s/traefik-v2.yaml index 64060532..c8705a7e 100644 --- a/k8s/traefik-v2.yaml +++ b/k8s/traefik-v2.yaml @@ -50,8 +50,10 @@ spec: - --api.insecure - --log.level=INFO - --metrics.prometheus - - --providers.kubernetescrd - --providers.kubernetesingress + - --entrypoints.http.Address=:80 + - --entrypoints.https.Address=:443 + - --entrypoints.https.http.tls.certResolver=default --- kind: Service apiVersion: v1 diff --git a/k8s/traefik.yaml b/k8s/traefik.yaml index 1ab529aa..8ac2c242 120000 --- a/k8s/traefik.yaml +++ b/k8s/traefik.yaml @@ -1 +1 @@ -traefik-v1.yaml \ No newline at end of file +traefik-v2.yaml \ No newline at end of file diff --git a/slides/k8s/ingress-tls.md b/slides/k8s/ingress-tls.md new file mode 100644 index 00000000..6799e344 --- /dev/null +++ b/slides/k8s/ingress-tls.md @@ -0,0 +1,403 @@ +# Ingress and TLS certificates + +- Most ingress controllers support TLS connections + + (in a way that is standard across controllers) + +- The TLS key and certificate are stored in a Secret + +- The Secret is then referenced in the Ingress resource: + ```yaml + spec: + tls: + - secretName: XXX + hosts: + - YYY + rules: + - ZZZ + ``` + +--- + +## Obtaining a certificate + +- In the next section, we will need a TLS key and certificate + +- These usually come in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format: + ``` + -----BEGIN CERTIFICATE----- + MIIDATCCAemg... + ... + -----END CERTIFICATE----- + ``` + +- We will see how to generate a self-signed certificate + + (easy, fast, but won't be recognized by web browsers) + +- We will also see how to obtain a certificate from [Let's Encrypt](https://letsencrypt.org/) + + (requires the cluster to be reachable through a domain name) + +--- + +class: extra-details + +## In production ... + +- A very popular option is to use the [cert-manager](https://cert-manager.io/docs/) operator + +- It's a flexible, modular approach to automated certificate management + +- For simplicity, in this section, we will use [certbot](https://certbot.eff.org/) + +- The method shown here works well for one-time certs, but lacks: + + - automation + + - renewal + +--- + +## Which domain to use + +- If you're doing this in a training: + + *the instructor will tell you what to use* + +- If you're doing this on your own Kubernetes cluster: + + *you should use a domain that points to your cluster* + +- More precisely: + + *you should use a domain that points to your ingress controller* + +- If you don't have a domain name, you can use [nip.io](https://nip.io/) + + (if your ingress controller is on 1.2.3.4, you can use `whatever.1.2.3.4.nip.io`) + +--- + +## Setting `$DOMAIN` + +- We will use `$DOMAIN` in the following section + +- Let's set it now + +.exercise[ + +- Set the `DOMAIN` environment variable: + ```bash + export DOMAIN=... + ``` + +] + +--- + +## Method 1, self-signed certificate + +- Thanks to `openssl`, generating a self-signed cert is just one command away! + +.exercise[ + +- Generate a key and certificate: + ```bash + openssl req \ + -newkey rsa -nodes -keyout privkey.pem \ + -x509 -days 30 -subj /CN=$DOMAIN/ -out cert.pem + ``` + +] + +This will create two files, `privkey.pem` and `cert.pem`. + +--- + +## Method 2, Let's Encrypt with certbot + +- `certbot` is an [ACME](https://tools.ietf.org/html/rfc8555) client + + (Automatic Certificate Management Environment) + +- We can use it to obtain certificates from Let's Encrypt + +- It needs to listen to port 80 + + (to complete the [HTTP-01 challenge](https://letsencrypt.org/docs/challenge-types/)) + +- If port 80 is already taken by our ingress controller, see method 3 + +--- + +class: extra-details + +## HTTP-01 challenge + +- `certbot` contacts Let's Encrypt, asking for a cert for `$DOMAIN` + +- Let's Encrypt gives a token to `certbot` + +- Let's Encrypt then tries to access the following URL: + + `http://$DOMAIN/.well-known/acme-challenge/` + +- That URL needs to be routed to `certbot` + +- Once Let's Encrypt gets the response from `certbot`, it issues the certificate + +--- + +## Running certbot + +- There is a very convenient container image, `certbot/certbot` + +- Let's use a volume to get easy access to the generated key and certificate + +.exercise[ + +- Obtain a certificate from Let's Encrypt: + ```bash + EMAIL=your.address@example.com + docker run --rm -p 80:80 -v $PWD/letsencrypt:/etc/letsencrypt \ + certbot/certbot certonly \ + -m $EMAIL \ + --standalone --agree-tos -n \ + --domain $DOMAIN \ + --test-cert + ``` + +] + +This will get us a "staging" certificate. +Remove `--test-cert` to obtain a *real* certificate. + +--- + +## Copying the key and certificate + +- If everything went fine: + + - the key and certificate files are in `letsencrypt/live/$DOMAIN` + + - they are owned by `root` + +.exercise[ + +- Grant ourselves permissions on these files: + ```bash + sudo chown -R $USER letsencrypt + ``` + +- Copy the certificate and key to the current directory: + ```bash + cp letsencrypt/live/test/{cert,privkey}.pem . + ``` + +] + +--- + +## Method 3, certbot with Ingress + +- Sometimes, we can't simply listen to port 80: + + - we might already have an ingress controller there + - our nodes might be on an internal network + +- But we can define an Ingress to route the HTTP-01 challenge to `certbot`! + +- Our Ingress needs to route all requests to `/.well-known/acme-challenge` to `certbot` + +- There are at least two ways to do that: + + - run `certbot` in a Pod (and extract the cert+key when it's done) + - run `certbot` in a container on a node (and manually route traffic to it) + +- We're going to use the second option + + (mostly because it will give us an excuse to tinker with Endpoints resources!) + +--- + +## The plan + +- We need the following resources: + + - an Endpoints¹ listing a hard-coded IP address and port +
(where our `certbot` container will be listening) + + - a Service corresponding to that Endpoints + + - an Ingress sending requests to `/.well-known/acme-challenge/*` to that Service +
(we don't even need to include a domain name in it) + +- Then we need to start `certbot` so that it's listening on the right address+port + +.footnote[¹Endpoints is always plural, because even a single resource is a list of endpoints.] + +--- + +## Creating resources + +- We prepared a YAML file to create the three resources + +- However, the Endpoints needs to be adapted to put the current node's address + +.exercise[ + +- Edit `~/containers.training/k8s/certbot.yaml` + + (replace `A.B.C.D` with the current node's address) + +- Create the resources: + ```bash + kubectl apply -f ~/containers.training/k8s/certbot.yaml + ``` + +] + +--- + +## Obtaining the certificate + +- Now we can run `certbot`, listening on the port listed in the Endpoints + + (i.e. 8000) + +.exercise[ + +- Run `certbot`: + ```bash + EMAIL=your.address@example.com + docker run --rm -p 8000:80 -v $PWD/letsencrypt:/etc/letsencrypt \ + certbot/certbot certonly \ + -m $EMAIL \ + --standalone --agree-tos -n \ + --domain $DOMAIN \ + --test-cert + ``` + +] + +This is using the staging environment. +Remove `--test-cert` to get a production certificate. + +--- + +## Copying the certificate + +- Just like in the previous method, the certificate is in `letsencrypt/live/$DOMAIN` + + (and owned by root) + +.exercise[ + +- Grand ourselves permissions on these files: + ```bash + sudo chown -R $USER letsencrypt + ``` + +- Copy the certificate and key to the current directory: + ```bash + cp letsencrypt/live/$DOMAIN/{cert,privkey}.pem . + ``` + +] + +--- + +## Creating the Secret + +- We now have two files: + + - `privkey.pem` (the private key) + + - `cert.pem` (the certificate) + +- We can create a Secret to hold them + +.exercise[ + +- Create the Secret: + ```bash + kubectl create secret tls $DOMAIN --cert=cert.pem --key=privkey.pem + ``` + +] + +--- + +## Ingress with TLS + +- To enable TLS for an Ingress, we need to add a `tls` section to the Ingress: + + ```yaml + spec: + tls: + - secretName: DOMAIN + hosts: + - DOMAIN + rules: ... + ``` + +- The list of hosts will be used by the ingress controller + + (to know which certificate to use with [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication)) + +- Of course, the name of the secret can be different + + (here, for clarity and convenience, we set it to match the domain) + +--- + +class: extra-details + +## About the ingress controller + +- Many ingress controllers can use different "stores" for keys and certificates + +- Our ingress controller needs to be configured to use secrets + + (as opposed to, e.g., obtain certificates directly with Let's Encrypt) + +--- + +## Using the certificate + +.exercise[ + +- Edit the Ingress manifest, `~/container.training/k8s/ingress.yaml` + +- Uncomment the `tls` section + +- Update the `secretName` and `hosts` list + +- Create or update the Ingress: + ```bash + kubectl apply -f ~/container.training/k8s/ingress.yaml + ``` + +- Check that the URL now works over `https` + + (it might take a minute to be picked up by the ingress controller) + +] + +--- + +## Discussion + +*To repeat something mentioned earlier ...* + +- The methods presented here are for *educational purpose only* + +- In most production scenarios, the certificates will be obtained automatically + +- A very popular option is to use the [cert-manager](https://cert-manager.io/docs/) operator + +??? + +:EN:- Ingress and TLS +:FR:- Certificats TLS et *ingress* diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml index 3c663dac..e7cd35c9 100644 --- a/slides/kube-fullday.yml +++ b/slides/kube-fullday.yml @@ -74,6 +74,7 @@ content: - - k8s/namespaces.md - k8s/ingress.md + #- k8s/ingress-tls.md #- k8s/kustomize.md #- k8s/helm-intro.md #- k8s/helm-chart-format.md diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml index aa5a2a2b..6315c163 100644 --- a/slides/kube-selfpaced.yml +++ b/slides/kube-selfpaced.yml @@ -77,6 +77,7 @@ content: - k8s/kubectlproxy.md - - k8s/ingress.md + - k8s/ingress-tls.md - k8s/kustomize.md - k8s/helm-intro.md - k8s/helm-chart-format.md diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml index fec866fe..6eb7d5ab 100644 --- a/slides/kube-twodays.yml +++ b/slides/kube-twodays.yml @@ -75,6 +75,7 @@ content: - - k8s/namespaces.md - k8s/ingress.md + #- k8s/ingress-tls.md - k8s/kustomize.md - k8s/helm-intro.md - k8s/helm-chart-format.md