When setting up my local K3s on a Intel NUC I thought it would be nice to use Ingress resources to access the services instead of using a node-port. As those services will only be used inside my LAN (and I didn’t want to have an invalid certificate warning) I decided to use a self-signed certificate for my usecase. Because I have multiple services (and add more on a regular basis) it seemed like a bad idea having to generate a new certificate for each services. So I decided to use a wildcard-certificate. However I was stopped short quite quickly: Chrome didn’t accept my certificate as valid. The reason for this is that Chrome deprecated in version 58 the usage of commonName
(which I’ve used in the wildcard-certificate) and enforced the setting of subjectAltNames
. This post shows my approach in creating a valid self-signed wildcard-certificates for modern browsers and even for Android.
Configuring OpenSSL
A default OpenSSL installation lacks the possibility to set the needed property. For this reason you will have to edit the configuration file. I advise copying the configuration and modifying this replica. On Ubuntu I’ve found the config in the following path.
cp /usr/lib/ssl/openssl.cnf .
Modify the copy and uncomment the part which we need for further setup: req_extensions = v3_req
In the v3_req
section add the subjectAltName
property like this:
[ v3_req ] # Extensions to add to a certificate request basicConstraints = CA:TRUE # TRUE if you want to use it on Android as well, else FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names
If you want to use an Android browser (I suppose it also applies to Apples IOS) you will have to set basicConstraints
to CA:TRUE
. Otherwise the certificate cannot be imported on current Android devices. This is unfortunate, because creating it with CA capabilities creates a theoretical attack vector, although this is probably negligible on a regular, moderately secured LAN. If you do not plan to use mobiles devices, feel free to set it to CA:FALSE
.
Now create a new section and add the alias as well as your wildcard alias. My device is reachable by the hostname nuc.local
.
[alt_names] DNS.1 = nuc.local DNS.2 = *.nuc.local
Creating the certificate
Save the config file and create the certificate with the following commands. I recommend filling in the CN (commonName), but you may fill in the other information as well. The following commands expect the modified config file in the current working directory. Feel free to adjust it for a different path in the last parameter on the fourth line. As it’s only local I’ve decided to create a very long validity period.
openssl genrsa -out hostname.key 2048 openssl rsa -in hostname.key -out hostname-key.pem openssl req -new -key hostname-key.pem -out hostname-request.csr openssl x509 -req -extensions v3_req -days 14600 -in hostname-request.csr -signkey hostname-key.pem -out hostname-cert.pem -extfile openssl.cnf
Default certificate for Traefik
The both created files hostname-key.pem
and hostname-cert.pem
represent the private and public key of your newly generated wildcard-certificate. In K3s when using Traefik as an Ingress controller, it’s fairly easy to use this certificate as the default certificate which is published on each Ingress. This way you will never have to manage the certificates again in the cluster (except when it’s compromised or invalid). This only applies to Ingresses which feature a subdomain route matching your wildcard certificate of course. In my example: resilio.nuc.local
.
The first step is to create a TLS secret from the pem-files:
kubectl create secret tls self-signed-default-cert -n kube-system \ --cert=/path/to/hostname-cert.pem \ --key=/path/to/hostname-key.pem
To have Traefik serve this certificate by default we need to tell it make use of it as a default certificate. If you skip this step, Traefik will still serve the initial Traefik default certificate. I recommend restarting the Traefik pods after this.
apiVersion: traefik.containo.us/v1alpha1 kind: TLSStore metadata: name: default namespace: kube-system spec: defaultCertificate: secretName: self-signed-default-cert
When accessing an Ingress Traefik should now serve your self-signed certificate. You can step it up one level by creating a Traefik middleware to redirect HTTP automatically to HTTPS to make use of your new, fancy certificate. See this post to achieve this.
Trusting the certificate
If you have accessed an Ingress route at this point you will probably see your certificate served, however your browser will still insist that this certificate cannot be trusted. This is because we have yet to add the certificate as trusted. Download your certificate to the host-machine where your browser resides. Either copy the certificate or just click the lock on your Ingress route and click certificate
. Click the tab details
and copy to file
. In the wizard choose DER-coded
.
In Windows right-click the certificate and choose Install certificate
. Follow the wizard until you’re asked to choose the certificate store. It’s important to choose Trusted Root certificates
. You need to open a new tab (refreshing an invalid-cert tab in Chrome does not work) to verify the validity (or just use an incognito tab).
Since version 106 Chrome does not make use of the Windows certificate root store but uses it’s own instead. To have Chrome use the Windows store again: Create a REG_DWORD value ChromeRootStoreEnabled
= 0 at HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Google\Chrome (as described here).
In Android: settings -> security -> encryption -> install from storage
and choose the certificate file.
Mick
You have a mistake in -signkey in “Creating the certificate”:
openssl x509 -req -extensions v3_req -days 14600 -in hostname-request.csr -signkey hostname-key.key -out hostname-cert.pem -extfile openssl.cnf
Tobey
Hi, could you elaborate what the mistake here is? Your openssl request differs in the file extension. “.key” is not necessary, as I’ve used “.pem” in my example.