9.6 KiB
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:
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 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
(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 operator
-
It's a flexible, modular approach to automated certificate management
-
For simplicity, in this section, we will use certbot
-
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
(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
$DOMAINin the following section -
Let's set it now
.lab[
- Set the
DOMAINenvironment variable:export DOMAIN=...
]
Choose your own adventure!
-
We present 3 methods to obtain a certificate
-
We suggest that you use method 1 (self-signed certificate)
-
it's the simplest and fastest method
-
it doesn't rely on other components
-
-
You're welcome to try methods 2 and 3 (leveraging certbot)
-
they're great if you want to understand "how the sausage is made"
-
they require some hacks (make sure port 80 is available)
-
they won't be used in production (cert-manager is better)
-
Method 1, self-signed certificate
- Thanks to
openssl, generating a self-signed cert is just one command away!
.lab[
- Generate a key and certificate:
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
-
certbotis an ACME 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)
-
If port 80 is already taken by our ingress controller, see method 3
class: extra-details
HTTP-01 challenge
-
certbotcontacts 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/<token> -
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
.lab[
- Obtain a certificate from Let's Encrypt:
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
-
.lab[
-
Grant ourselves permissions on these files:
sudo chown -R $USER letsencrypt -
Copy the certificate and key to the current directory:
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-challengetocertbot -
There are at least two ways to do that:
- run
certbotin a Pod (and extract the cert+key when it's done) - run
certbotin a container on a node (and manually route traffic to it)
- run
-
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 ourcertbotcontainer 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
certbotso 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
.lab[
-
Edit
~/containers.training/k8s/certbot.yaml(replace
A.B.C.Dwith the current node's address) -
Create the resources:
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)
.lab[
- Run
certbot: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)
.lab[
-
Grand ourselves permissions on these files:
sudo chown -R $USER letsencrypt -
Copy the certificate and key to the current directory:
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
.lab[
- Create the Secret:
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
tlssection to the Ingress: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)
-
Of course, the name of the secret can be different
(here, for clarity and convenience, we set it to match the domain)
kubectl create ingress
-
We can also create an Ingress using TLS directly
-
To do it, add
,tls=secret-nameto an Ingress rule -
Example:
kubectl create ingress hello \ --rule=hello.example.com/*=hello:80,tls=hello -
The domain will automatically be inferred from the rule
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
.lab[
-
Add the
tlssection to an existing Ingress -
If you need to see what the
tlssection should look like, you can:-
kubectl explain ingress.spec.tls -
kubectl create ingress --dry-run=client -o yaml ... -
check
~/container.training/k8s/ingress.yamlfor inspiration -
read the docs
-
-
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 operator
Security
-
Since TLS certificates are stored in Secrets...
-
...It means that our Ingress controller must be able to read Secrets
-
A vulnerability in the Ingress controller can have dramatic consequences
-
See CVE-2021-25742 for an example
-
This can be mitigated by limiting which Secrets the controller can access
(RBAC rules can specify resource names)
-
Downside: each TLS secret must explicitly be listed in RBAC
(but that's better than a full cluster compromise, isn't it?)
???
:EN:- Ingress and TLS :FR:- Certificats TLS et ingress