How to implement End-to-End TLS flow using Traefik
An interesting usecase I have encountered recently is establishing a secure path (bridge) between Traefik as ingress controller and its destination backends.
The post addresses traefik
usage within Kubernetes, however it is applicable to other setups.
Case
A flow diagram is probably the way to explain the setup.
The main routing resource is a CRD that resembles the following snippet.
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: web-ingressroute
namespace: web
spec:
entryPoints:
- websecure
routes:
- kind: Rule
match: Host(`web.example.com`)
services:
- name: backend-svc
namespace: web
port: 443
scheme: https
passHostHeader: true
secretName: web-ingressroute-tls
The secret here web-ingressroute-tls
is a standard tls
secret for the external route containing the private key, certificate, and ca certificate. Let’s mark it as secret (1).
The target backend is a classic sidecar container “nginx” fronting the main container.
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-conf
data:
nginx.conf: |
user nginx;
worker_processes 3;
...
http {
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name _;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
proxy_pass http://localhost:80;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
The pod is exposed using a cluster service backend-svc
as denoted in the ingress-route.
backend-tls
contains SSL certificate files that nginx
uses and mounts. At the same time, it is intended for traefik
to trust as the target certificate. As expected for internal services, this is an automatically rotated self-signed certificate by an internal issuer or “let’s encrypt”. Here is the second tls
secret (2).
Initial Approach
First, most resources out there might guide you to explore setting the TLS options of the route:
tls:
options:
name: backend-tls-opts
namespace: web
---
apiVersion: traefik.containo.us/v1alpha1
kind: TLSOption
metadata:
name: backend-tls-opts
namespace: web
spec:
cipherSuites: [...]
clientAuth:
clientAuthType: RequireAndVerifyClientCert
secretNames:
- backend-tls
minVersion: VersionTLS12
But that didn’t work.
The router logs shows internal errors (500) when connecting to the upstream (pod IP).
traefikee-ingress-proxy-57.. 10.244.0.128 - - [15/Mar/2023:10:45:50 +0000] "GET / HTTP/2.0" 500 21 "-" "-" 2987228 "web-web-ingressroute-6cd908afc82ca51c00cf@kubernetescrd" "https://10.244.2.105:443" 2ms
This is despite that nginx
was functioning fine when forwarded locally.
kubectl port-forward svc/backend-svc 8443:443
Serving requests and SSL/TLS settings were correct. The logs emit success (200) when reached over https. Basically, the backend itself was browsable.
After further review, it turns out that TLSOptions
as implemented above was merely for client side certificate when reaching the ingress router, an implementation of mTLS and not our case of TLS passing.
Solution
ServersTransport
was what is needed to let traefik
trust the backend certificate instead of faulting.
kind: ServersTransport
metadata:
name: web-transport
namespace: web
spec:
certificatesSecrets:
- backend-tls
rootCAsSecrets:
- backend-tls
serverName: web.example.com
And appending to the route:
serversTransport: web-transport
Of course you may turn off the destination certificate check by setting insecureSkipVerify: true
but that would defeat the purpose we aim for, an end to end TLS flow (bridging) !