Let's Encrypt Certificates for Self-Hosted Services
TLS certificates are an important security tool for both, services accessible over the internet and services hosted locally in a home lab. If you are just starting out, self-signed certificates can be a very good option. If you own the domain, you could get valid TLS certificates. Using the dnsChallenge option in Traefik, you can do it without having to expose any services to the internet.
Here I have documented the steps to achieve this on a K3S cluster.
ACME Challenges
ACME standard has different challenge types that can be used to prove that you control the domain. There is a very good write-up about ACME challenges on the Let's Encrypt site.
Most of these challenge types require the service / reverse proxy (in our case traefik) to be accessible by Let's Encrypt. This would mean opening up ports on the router if you are self-hosting on your home lab.
DNS challenge is great for such cases. You give the ACME client API token to modify your DNS entries and it uses that to add a TXT record. This record is then queried by Let's Encrypt to authenticate and issue the certificate.
HelmChartConfig
K3S uses a CRD called HelmChartConfig to configure packaged components like traefik. This CRD can be used to pass any arguments that can be passed using the helm CLI tool.
I created this config to use cloudflare as the DNS provider.
1 apiVersion: helm.cattle.io/v1
2 kind: HelmChartConfig
3 metadata:
4 name: traefik
5 namespace: kube-system
6 spec:
7 valuesContent: |-
8 env:
9 - name: CF_API_EMAIL
10 valueFrom:
11 secretKeyRef:
12 key: email
13 name: traefik-cloudflare-secrets
14 - name: CF_DNS_API_TOKEN
15 valueFrom:
16 secretKeyRef:
17 key: apikey
18 name: traefik-cloudflare-secrets
19 additionalArguments:
20 - "--log.level=DEBUG"
21 - "--certificatesresolvers.le.acme.email=email@example.com"
22 - "--certificatesresolvers.le.acme.storage=/data/acme.json"
23 - "--certificatesresolvers.le.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory"
24 - "--certificatesresolvers.le.acme.dnsChallenge.provider=cloudflare"
25 - "--certificatesResolvers.le.acme.dnsChallenge.resolvers=1.1.1.1:53,1.0.0.1:53"
Cloudflare email and API key are passed as secrets. It is also important to pass the email in --certificatesresolvers.le.acme.email additional argument. This example uses the Let's Encrypt staging server (--certificatesresolvers.le.acme.caServer) to safely test because the production server has rate limits.
The Cloudflare API token needs to have the following permissions:
- Zone / Zone / Read
- Zone / DNS / Edit
You can also scope the token to use a single zone.
Apply this config using kubectl.
Ingress
Once the HelmChartConfig is deployed, you can create an Ingress.
1 apiVersion: networking.k8s.io/v1
2 kind: Ingress
3 metadata:
4 name: nginx-ingress
5 namespace: default
6 annotations:
7 ingressClassName: traefik
8 traefik.ingress.kubernetes.io/router.entrypoints: websecure
9 traefik.ingress.kubernetes.io/router.tls: "true"
10 traefik.ingress.kubernetes.io/router.tls.certresolver: le
11 spec:
12 rules:
13 - host: "nginx.example.com"
14 http:
15 paths:
16 - path: /
17 pathType: Prefix
18 backend:
19 service:
20 name: nginx-svc
21 port:
22 number: 80
You will need to point this domain name to the Ingress IP. I use Pi-Hole, so I added a local DNS entry. It might take a few seconds to retrieve the certificates if they are being issued for the first time. Now you should have valid TLS certificates from Let's Encrypt even for fully locally hosted services.