1. The problem I’m having:
TL/DR;
- What’s the matcher syntax to specify a
layer4
matcher for TLS version in my otherwise-working json config? I have sni matching working OK.
I need to proxy (without TLS termination) traffic from clients that only support TLS v1.0 (ie, ancient, insecure client apps that I cannot change). On the same port I also wish to serve regular web traffic for modern clients, reverse_proxy
’d to other servers.
I have been able to configure the layer4 module to do the proxying etc that I need, but I have not been able to work out how to get it to apply a match on the TLS version, which is provided in the clienthello.
If I use a matcher for “sni” the rest of my config works, I just don’t know the syntax for matching by tls version (or if it’s definitely possible - but mholt indicated it should be
My setup generally is:
- I build a docker image that includes the layer4 module (and installs curl)
- I start the container with a Caddyfile that holds my “normal” config (as it’s in a file format that supports comments)
- caddy then is able to serve my normal sites on port 443
- I then inject my layer4 config via the API
- this adds the layer4 app which listens on port 4433.
- it identifies the legacy clients (currently by SNI but I need tls version) and proxies their connections to the legacy server, without performing TLS termination.
- un-matched connections get proxied to port 443, so all my normal services are served via the http and tls apps that are defined in the main
Caddyfile
.
I’ve done it this way so that I can keep the simple Caddyfile config, and at my firewall I redirect incoming port 443 to caddy’s port 4433.
2. Error messages and/or full log output:
When I try to inject my layer4 config block with the command
docker-compose exec caddy curl \
-X POST \
-H "Content-Type: application/json" \
-d @caddy-inject.json \
"http://127.0.0.1:2019/config/apps/layer4"
I get:
{"error":"loading new config: loading layer4 app module: provision layer4: server 'ajgtlswrap': route 0: loading matcher modules: module name 'tls': provision layer4.matchers.tls: loading TLS matchers: module name 'version': unknown module: tls.handshake_match.version"}
This is when the caddy-inject.json
snippet contains (as opposed to the “working” config using SNI specified further below):
{
"servers": {
"ajgtlswrap": {
"listen": [
":4433"
],
"routes": [
{
"match": [
{
"tls": {
"version": [
"TLS10"
]
}
}
],
"handle": [
{
"handler": "proxy",
"upstreams": [
{
"dial": [
"legacyserver.internal.example.com:443"
]
}
]
}
]
},
{
"handle": [
{
"handler": "proxy",
"upstreams": [
{
"dial": [
"127.0.0.1:443"
]
}
]
}
]
}
]
}
}
}
3. Caddy version:
I build from caddy 2.7.5 locally using this Dockerfile:
ARG VERSION
FROM caddy:$VERSION-builder-alpine AS builder
RUN xcaddy build \
--with github.com/abiosoft/caddy-json-schema \
--with github.com/mholt/caddy-l4
FROM caddy:$VERSION-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
RUN apk add curl
4. How I installed and ran Caddy:
a. System environment:
Debian 11.8 with its docker.io 20.10.5+dfsg1-1+deb11u2
docker-compose 1.25.0-1
b. Command:
docker-compose up -d
c. Service/unit/compose file:
My docker-compose.yaml
includes:
services:
caddy:
build:
context: ./caddy-build
args:
- VERSION=2.7.5
restart: unless-stopped
cap_add:
- NET_ADMIN # Required for tweaking of UDP buffers for http/3
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3 support(!)
- "4433:4433" # Gateway directs traffic here, which handles proxying.
volumes:
- $PWD/Caddyfile:/etc/caddy/Caddyfile
- $PWD/caddy-srv:/srv
- $PWD/caddy-data:/data
- $PWD/caddy-config:/config
d. My complete Caddy config:
Note: I am redacting info here because I don’t believe its inclusion is relevant to my problem (ie all certs, redirections etc are working OK) and because my situation requires obfuscating details due to the presence of legacy systems. I can provide details by DM if they are relevant.
Initial Caddyfile
loaded at startup:
{
email hostmaster@example.com
}
home.example.com {
reverse_proxy 192.168.35.42:8088
}
media.example.com {
reverse_proxy jellyfin:8096
}
“Injected” l4 config which DOES work, but I need to match on TLS version, not on SNI:
{
"servers": {
"ajgtlswrap": {
"listen": [
":4433"
],
"routes": [
{
"match": [
{
"tls": {
"sni": [
"legacy.example.com"
]
}
}
],
"handle": [
{
"handler": "proxy",
"upstreams": [
{
"dial": [
"legacyserver.internal.example.com:443"
]
}
]
}
]
},
{
"handle": [
{
"handler": "proxy",
"upstreams": [
{
"dial": [
"127.0.0.1:443"
]
}
]
}
]
}
]
}
}
}