Securing LDAP with LDAPS via Traefik
TLDR; Scroll to the last section for setup instructions. Other sections detail why other approaches don’t work and why.
Summary: We want to traefik to get SSL certificates and present a public-facing LDAPS service backed by an LDAP service running inside a docker network. Most people use traefik to reverse proxy and handle certs for HTTPS/HTTP traffic, we want to do the same with LDAPS/LDAP.
The Desired Setup
Warning: Make sure there isn’t a Cloudflare Proxy on your desired LDAPS domain. Cloudflare will not proxy TCP traffic!
+-----------------------------------------------------+
| +------------------------------------------------+ |
| | +-----------+ +-------------+ | |
+----------+ +----------+-+ | +-----+-----+ | | |
| Internet | --> | LDAPS Port | | --> | LDAP Port | | | |
+----------+ +------------+ traefik | +-----------+ lldap | | |
| | | Container | | Container | Docker | |
| | +-----------+ +-------------+ Env | |
| +------------------------------------------------+ |
| Host |
+-----------------------------------------------------+
The diagram above shows the desired setup. The image depicts two docker containers, traefik and lldap, running on a host. There is an open port on our host, the LDAPS port, which passes traffic to the traefik docker container via published port, which is further passed along to a traefik TCP entrypoint. From there, traefik will terminate the TLS TCP traffic and forward unencrypted TCP traffic to the lldap container’s LDAP port. Note that lldap’s LDAP port remains unexposed to the wider internet.
What Does Not Work
Sample LDAPSearch command, for convenience:
ldapsearch -H ldaps://lldap.example.com:636 -LLL -D "uid=some_user_name,ou=people,DC=example,DC=com" -w 'password' -s "One" -b "dc=example,dc=com"
Say we have the following for entryPoints and certificatesResolvers in our traefik.yaml:
entryPoints:
ldapsecure:
address: :636
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: :443
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /var/traefik/certs/letsencrypt-acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
One might naively add the following labels to lldap container:
traefik.tcp.routers.lldap.rule=HostSNI(`lldap.example.com`)
traefik.tcp.routers.lldap.entrypoints=ldapsecure
traefik.tcp.routers.lldap.tls=true
traefik.tcp.routers.lldap.tls.certresolver=letsencrypt
traefik.tcp.services.lldap.loadbalancer.server.port=3890 # lldap, by default, uses 3890 as LDAP port
This won’t always work, since some legacy LDAP clients will not pass Server Name Indications (such as ldapsearch). You can tell if this is the case if you see the following in traefik debug logs Serving default certificate for request: "". We can set the router rule to HostSNI(`*`) so that all traffic on ldapsecure entrypoint goes to our lldap container. We can also set up a default SSL cert in traefik.yaml
Solution
Put together, we want a traefik.yaml similar to:
entryPoints:
ldapsecure:
address: :636
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: :443
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com
storage: /var/traefik/certs/letsencrypt-acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "1.0.0.1:53"
tls:
stores:
default:
defaultGeneratedCert:
resolver: letsencrypt
domain:
main: example.com
sans:
- lldap.example.com
Set lldap container’s labels to:
traefik.tcp.routers.lldap.rule=HostSNI(`*`)
traefik.tcp.routers.lldap.entrypoints=ldapsecure
traefik.tcp.routers.lldap.tls=true
traefik.tcp.routers.lldap.tls.certresolver=letsencrypt
traefik.tcp.services.lldap.loadbalancer.server.port=389 # lldap, by default, uses 3890 as LDAP port
What About Cloudflare?
Neither Cloudflare proxy nor Cloudflare tunnels can be used to pass LDAPS TCP traffic over the public internet. Cloudflare Proxy refuses to forward non-http/https traffic. You cannot use the other ports Cloudflare Proxy accepts, as it’s not a passthrough. Cloudflare Tunnels will only forward TCP data if you set up cloudflared on the both the host and the client.