Switching to Traefik and step-ca (from nginx-proxy)
I’ve been using nginx-proxy as a reverse proxy for my docker containers for a few years, where I manually generate and inject the necessary SSL certificates to make stuff work. The certificates were generated on my Opnsense box. A bit tedious, but manageable.
I’ve been planning to upgrade this setup for a while now, using step-ca as an ACME-compatible backend for handing out certificates. No more me having to go and remember to put in the necessary files in the right location in my Ansible playbooks ;)
Since Traefik has also been on my to-look-at-and-play-with list, I decided to combine the two.
Smallstep step-ca
step-ca
is running as a docker container (using the official image in my environment), listening on port 9000, but not exposed to the outside world. I’m using a docker .internal
network, so that the container is only visible inside the docker network, not outside.
Most of the info you need can be found in the tutorial on running a TLS certificate authority on the smallstep website.
Root / intermediate CA certificates
One important thing to remember (and this bit me) if you’re not starting from scratch is to make sure you have a trusted/known root CA certificate, intermediate CA and key installed in the container volume. I dropped an intermediate certificate under /home/step/certs
.
Make sure to correctly configure the dnsNames
section, as this will determine on which hostnames step-ca
will answer.
Config
I’m using the following configuration with step-ca
, which is installed under /home/step/config/ca.json
when I instantiate the container:
{
"root": "/home/step/certs/root_ca.crt",
"federatedRoots": null,
"crt": "/home/step/certs/intermediate_ca.crt",
"key": "/home/step/secrets/intermediate_ca_key",
"address": ":9000",
"insecureAddress": "",
"dnsNames": [
"localhost",
"smallstep-ca.internal"
],
"logger": {
"format": "text"
},
"db": {
"type": "badgerv2",
"dataSource": "/home/step/db",
"badgerFileLoadingMode": ""
},
"authority": {
"provisioners": [
{
"type": "ACME",
"name": "acme",
"forceCN": true,
"claims": {
"enableSSHCA": true,
"disableRenewal": false,
"allowRenewalAfterExpiry": false,
"disableSmallstepExtensions": false,
"maxTLSCertDuration": "2160h",
"defaultTLSCertDuration": "2160h"
},
"options": {
"x509": {},
"ssh": {}
}
}
],
"template": {},
"backdate": "1m0s",
"enableAdmin": true
},
"tls": {
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
],
"minVersion": 1.2,
"maxVersion": 1.3,
"renegotiation": false
},
"commonName": "MyFancyCA"
}
This can probably be improved, but it Works For MeTM.
Traefik
Traefik is (surprise!) also running as a Docker container using the official image.
Docker socket file
Please don’t give Traefik read/write access to your docker socket file! It just needs to be able to read it, nothing more, so do add :ro
in your volume spec.
CA Certificate
Remember to put your root CA certificate somewhere where Traefik can find it. Without this it will not trust the certificates given out by step-ca
and you’ll tear out your hair in frustration…! (ask me how I know)
Config
As with step-ca
, you can specify a lot of things on the environment variables. I’m not a huge fan of doing that so I also put the necessary config files.
I map the /etc/traefik
directory to a volume where I add my files.
The main one is traefik.yml
:
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
network: internal
log:
level: INFO
api:
dashboard: true
certificatesResolvers:
stepca:
acme:
caServer: "https://smallstep-ca.internal:9000/acme/acme/directory"
email: "MyFancyCA@MyFancyTLD.lan"
storage: "/etc/traefik/acme.json"
certificatesDuration: 2160
tlsChallenge: {}
entryPoints:
http:
address: ":80"
http:
redirections:
entryPoint:
to: https
scheme: https
https:
address: ":443"
http:
tls:
certresolver: stepca
Environment
Certain things you still need to configure using the environment variables.
Environment variable | Value | Notes |
---|---|---|
LEGO_CA_CERTIFICATES | /etc/traefik/ca_certificate.pem | Certificate needed to validate the connection to step-ca |
TZ | Europe/Brussels | Your timezone for logs in the right timezone |
Dashboard
I wanted a functioning Traefik Dashboard, but also hosted securely. This means configuring the necessary routes in Traefik to route this correctly.
Traefik works it magic using container labels, so we need to add some labels on the Traefik container itself:
Label | Value | Notes |
---|---|---|
traefik.http.routers.dashboard.rule | “Host(‘traefik.myhome.lan’) && (PathPrefix(‘/api’) || PathPrefix(‘/dashboard’))” | Router Rule for Dashboard and API |
traefik.http.routers.dashboard.service | “api@internal” | Route the dashboard to the internal service |
traefik.http.routers.dashboard.middlewares | “auth” | Enable BasicAuth authentication |
traefik.http.middlewares.auth.basicauth.users | “MyUser:passwordHash” | User:password string, generated with htpasswd |
Container configuration
Since I don’t want Traefik to pick up all containers by default, I added the exposedByDefault: false
setting. To enable Traefik-ing a container, you’ll need to add a label to them:
Label | Value | Notes |
---|---|---|
traefik.enable | true | Tell Traefik to handle this container |
traefik.http.routers.mycontainer.rule | “Host(‘mycontainer.myhome.lan’)” | The container hostname |
traefik.http.services.mycontainer.loadbalancer.server.port | 80 | The port the container itself listens on |
That should be all that is necessary to get Traefik to route mycontainer.myhome.lan
to your container :)
Leave a comment