generated from bisco/codex-bootstrap
fix: route host loopback backends
This commit is contained in:
@@ -31,17 +31,17 @@ tcp:
|
||||
- address: "host.docker.internal:8443"
|
||||
```
|
||||
|
||||
`HostSNI(...)` matches the hostname sent by the client during the TLS handshake. If a backend listens on the Docker host itself, use `host.docker.internal:PORT`.
|
||||
`HostSNI(...)` matches the hostname sent by the client during the TLS handshake. This deployment uses host networking so Traefik can reach services bound to the Docker host loopback address, such as `127.0.0.1:8443`.
|
||||
|
||||
HTTP-01 certificate challenges use normal HTTP routing on port `80`, not SNI. The included example forwards:
|
||||
|
||||
```text
|
||||
lab.younerd.org/.well-known/acme-challenge/* -> host.docker.internal:8080
|
||||
azionelab.org/.well-known/acme-challenge/* -> host.docker.internal:9080
|
||||
www.azionelab.org/.well-known/acme-challenge/* -> host.docker.internal:9080
|
||||
lab.younerd.org -> 127.0.0.1:8080
|
||||
azionelab.org -> 127.0.0.1:9080
|
||||
www.azionelab.org -> 127.0.0.1:9080
|
||||
```
|
||||
|
||||
Adjust `8080` and `9080` to the local ports where the Certbot challenge responder or webroot service is listening.
|
||||
Adjust `8080` and `9080` to the local ports where the backend HTTP service is listening. The full HTTP request is forwarded so the backend can handle redirects and ACME challenge paths.
|
||||
|
||||
## Run
|
||||
|
||||
|
||||
+3
-5
@@ -5,16 +5,14 @@ services:
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- --configFile=/etc/traefik/traefik.yml
|
||||
ports:
|
||||
- "80:8080/tcp"
|
||||
- "443:8443/tcp"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
network_mode: host
|
||||
volumes:
|
||||
- ./config:/etc/traefik:ro
|
||||
read_only: true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
healthcheck:
|
||||
|
||||
@@ -3,25 +3,25 @@ http:
|
||||
lab-younerd-acme:
|
||||
entryPoints:
|
||||
- web
|
||||
rule: "Host(`lab.younerd.org`) && PathPrefix(`/.well-known/acme-challenge/`)"
|
||||
rule: "Host(`lab.younerd.org`)"
|
||||
service: lab-younerd-acme
|
||||
|
||||
azionelab-acme:
|
||||
entryPoints:
|
||||
- web
|
||||
rule: "(Host(`azionelab.org`) || Host(`www.azionelab.org`)) && PathPrefix(`/.well-known/acme-challenge/`)"
|
||||
rule: "Host(`azionelab.org`) || Host(`www.azionelab.org`)"
|
||||
service: azionelab-acme
|
||||
|
||||
services:
|
||||
lab-younerd-acme:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://host.docker.internal:8080"
|
||||
- url: "http://127.0.0.1:8080"
|
||||
|
||||
azionelab-acme:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://host.docker.internal:9080"
|
||||
- url: "http://127.0.0.1:9080"
|
||||
|
||||
tcp:
|
||||
routers:
|
||||
@@ -45,9 +45,9 @@ tcp:
|
||||
lab-younerd:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- address: "host.docker.internal:8443"
|
||||
- address: "127.0.0.1:8443"
|
||||
|
||||
azionelab:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- address: "host.docker.internal:9443"
|
||||
- address: "127.0.0.1:9443"
|
||||
|
||||
+2
-2
@@ -4,9 +4,9 @@ global:
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":8080"
|
||||
address: ":80"
|
||||
tls:
|
||||
address: ":8443"
|
||||
address: ":443"
|
||||
ping:
|
||||
address: ":8082"
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ The load balancer must run as a Docker container and forward TCP TLS traffic to
|
||||
|
||||
Use Traefik Proxy in Docker with the file provider and TCP TLS passthrough routing. Static and dynamic configuration live under the host `./config` directory and are mounted read-only at `/etc/traefik`.
|
||||
|
||||
The public host port `443/tcp` is mapped to container port `8443/tcp` so the container can run with all Linux capabilities dropped. Traefik routes incoming TLS connections with `HostSNI(...)` to the configured TCP load-balancer services.
|
||||
Traefik uses host networking so it can reach backends bound to host loopback addresses such as `127.0.0.1:8443`. The container is granted only `NET_BIND_SERVICE` so Traefik can bind host ports `80` and `443`.
|
||||
|
||||
The public host port `80/tcp` is mapped to container port `8080/tcp` for HTTP-01 certificate challenge traffic. HTTP challenge routes use `Host(...)` and `PathPrefix('/.well-known/acme-challenge/')` because SNI exists only in TLS handshakes.
|
||||
Traefik routes incoming TLS connections with `HostSNI(...)` to the configured TCP load-balancer services. HTTP routes on port `80` use `Host(...)` and forward the full request to the selected backend because SNI exists only in TLS handshakes and backend nginx instances are responsible for redirects and ACME challenge handling.
|
||||
|
||||
## Consequences
|
||||
|
||||
@@ -30,11 +30,11 @@ The Docker provider was not selected because the backends are addressed directly
|
||||
|
||||
## Security impact
|
||||
|
||||
The container does not mount the Docker socket, does not run privileged, drops Linux capabilities, uses `no-new-privileges`, and mounts configuration read-only. No private keys or credentials are required in this repository. Public `80/tcp` exposure is limited by HTTP routers to ACME challenge paths for configured hostnames.
|
||||
The container does not mount the Docker socket, does not run privileged, drops Linux capabilities except `NET_BIND_SERVICE`, uses `no-new-privileges`, and mounts configuration read-only. No private keys or credentials are required in this repository. Host networking exposes Traefik directly on the host network namespace, so host firewall rules should restrict non-public ports such as the ping entrypoint.
|
||||
|
||||
## Operational impact
|
||||
|
||||
Operators update backend IP addresses and ports in `config/dynamic/tcp-services.yml`. The Compose deployment publishes `80/tcp` and `443/tcp`; the ping entrypoint is internal to the container and used by the healthcheck.
|
||||
Operators update backend IP addresses and ports in `config/dynamic/tcp-services.yml`. Traefik listens directly on host `80/tcp` and `443/tcp`; the ping entrypoint is used by the healthcheck.
|
||||
|
||||
## Rollback
|
||||
|
||||
|
||||
@@ -4,24 +4,24 @@ This project runs Traefik Proxy as a Dockerized TCP load balancer.
|
||||
|
||||
## Components
|
||||
|
||||
- `compose.yaml`: defines the Traefik container, published port, host-based configuration volume, container hardening, and healthcheck.
|
||||
- `compose.yaml`: defines the Traefik container, host networking, host-based configuration volume, container hardening, and healthcheck.
|
||||
- `config/traefik.yml`: static Traefik configuration for HTTP, TCP TLS, ping, file provider, and logging.
|
||||
- `config/dynamic/tcp-services.yml`: dynamic HTTP challenge routers, TCP routers, and backend load-balancer services.
|
||||
|
||||
## Data flow
|
||||
|
||||
1. A client opens a TLS connection to the Docker host on `443/tcp`.
|
||||
2. Docker forwards the connection to Traefik on container port `8443/tcp`.
|
||||
3. Traefik matches the TCP router using `HostSNI(*)`.
|
||||
2. Traefik receives the connection directly through host networking on `443/tcp`.
|
||||
3. Traefik matches the TCP router using `HostSNI(...)`.
|
||||
4. Traefik forwards the unchanged TCP stream to one configured backend IP address and port.
|
||||
5. The selected backend terminates TLS and presents its own SSL certificate.
|
||||
|
||||
For HTTP-01 certificate validation:
|
||||
|
||||
1. Let's Encrypt opens an HTTP connection to the Docker host on `80/tcp`.
|
||||
2. Docker forwards the request to Traefik on container port `8080/tcp`.
|
||||
3. Traefik matches the HTTP `Host(...)` and `PathPrefix('/.well-known/acme-challenge/')` router.
|
||||
4. Traefik forwards the request to the configured local challenge responder.
|
||||
2. Traefik receives the request directly through host networking on `80/tcp`.
|
||||
3. Traefik matches the HTTP `Host(...)` router.
|
||||
4. Traefik forwards the full HTTP request to the configured local backend so that backend redirects and ACME challenge handling remain in one place.
|
||||
|
||||
## Persistence
|
||||
|
||||
@@ -31,6 +31,6 @@ No certificates, private keys, application data, or Docker socket are mounted in
|
||||
|
||||
## Deployment topology
|
||||
|
||||
Traefik runs as one Docker container on the load-balancer host. Backends are external to this Compose stack and must be reachable from the container through their configured IP addresses and ports. The Compose service defines `host.docker.internal` through Docker's `host-gateway` feature for backends that listen on the Docker host itself.
|
||||
Traefik runs as one Docker container on the load-balancer host with `network_mode: host`. Backends are external to this Compose stack and can be reached on host loopback addresses such as `127.0.0.1:8443`.
|
||||
|
||||
See `docs/adr/0001-traefik-tcp-tls-passthrough.md` for the design decision.
|
||||
|
||||
+6
-6
@@ -23,12 +23,12 @@ tcp:
|
||||
example:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- address: "host.docker.internal:8443"
|
||||
- address: "127.0.0.1:8443"
|
||||
```
|
||||
|
||||
If a backend listens on the Docker host, use `host.docker.internal:PORT`. Compose maps that name through Docker's `host-gateway` feature.
|
||||
This deployment uses host networking so Traefik can reach services bound to host loopback addresses such as `127.0.0.1:8443`.
|
||||
|
||||
HTTP-01 certificate challenges are routed separately with HTTP `Host(...)` rules on port `80`. Configure the `*-acme` services in `config/dynamic/tcp-services.yml` to point at the local port where the challenge responder listens.
|
||||
HTTP traffic is routed separately with HTTP `Host(...)` rules on port `80`. Configure the HTTP services in `config/dynamic/tcp-services.yml` to point at the local backend port that should handle redirects and ACME challenge paths.
|
||||
|
||||
The `./config` directory is the host-based configuration volume. It is mounted read-only into the container:
|
||||
|
||||
@@ -39,9 +39,9 @@ volumes:
|
||||
|
||||
## Ports
|
||||
|
||||
- Host `80/tcp` -> container `8080/tcp`: public HTTP entrypoint for ACME HTTP-01 challenge paths.
|
||||
- Host `443/tcp` -> container `8443/tcp`: public TCP TLS passthrough load-balancer entrypoint.
|
||||
- Container `8082/tcp`: internal Traefik ping entrypoint used only by the container healthcheck.
|
||||
- Host `80/tcp`: public HTTP entrypoint handled directly by Traefik through host networking.
|
||||
- Host `443/tcp`: public TCP TLS passthrough entrypoint handled directly by Traefik through host networking.
|
||||
- Host `8082/tcp`: Traefik ping entrypoint for the container healthcheck; restrict this port with host firewall rules if needed.
|
||||
|
||||
## Deploy
|
||||
|
||||
|
||||
+3
-3
@@ -48,13 +48,13 @@ docker compose logs -f traefik
|
||||
|
||||
To remove a backend from rotation, edit `config/dynamic/tcp-services.yml` and remove or comment out its `address` entry. Keep at least one reachable backend configured before applying the change.
|
||||
|
||||
For backends running on the Docker host, use `host.docker.internal:PORT` rather than `127.0.0.1:PORT`, because `127.0.0.1` inside the container refers to the container itself.
|
||||
This deployment uses host networking. For backends running on the Docker host, use `127.0.0.1:PORT` when the service is bound to host loopback.
|
||||
|
||||
For HTTP-01 challenges, update the `*-acme` HTTP services in `config/dynamic/tcp-services.yml` to match the local challenge responder ports.
|
||||
For HTTP on port `80`, update the HTTP services in `config/dynamic/tcp-services.yml` to match the local backend ports that handle redirects and ACME challenge paths.
|
||||
|
||||
## Known operational risks
|
||||
|
||||
- TLS passthrough prevents Traefik from inspecting HTTP paths, headers, or certificate contents.
|
||||
- Backend certificates and renewal jobs must be monitored on each backend.
|
||||
- SNI routing depends on clients sending the expected hostname during the TLS handshake. Clients that connect by IP address or omit SNI will not match hostname-specific routers.
|
||||
- Public port `80/tcp` is only intended for `/.well-known/acme-challenge/` traffic in this configuration.
|
||||
- Public port `80/tcp` forwards all HTTP paths for configured hostnames to the matching backend.
|
||||
|
||||
+2
-2
@@ -41,11 +41,11 @@ Confirm that `config/traefik.yml` is mounted and that the ping entrypoint is ena
|
||||
|
||||
### Clients cannot connect
|
||||
|
||||
Check that the host is listening on `80/tcp` and `443/tcp`, firewall rules allow inbound traffic, and backend IP addresses are reachable from the Traefik container. For services on the Docker host, configure backends as `host.docker.internal:PORT`.
|
||||
Check that the host is listening on `80/tcp` and `443/tcp`, firewall rules allow inbound traffic, and backend IP addresses are reachable from Traefik. For services bound to host loopback, configure backends as `127.0.0.1:PORT`.
|
||||
|
||||
### Certificate HTTP-01 challenge fails
|
||||
|
||||
Check that port `80/tcp` is reachable from the internet and that the matching `*-acme` service points to the local challenge responder port.
|
||||
Check that port `80/tcp` is reachable from the internet and that the matching HTTP service points to the local backend that handles ACME challenge paths.
|
||||
|
||||
### Wrong certificate is presented
|
||||
|
||||
|
||||
+3
-3
@@ -13,9 +13,9 @@ Backend services are responsible for:
|
||||
|
||||
## Network exposure
|
||||
|
||||
Compose publishes `80/tcp` for HTTP-01 certificate challenges and `443/tcp` for TLS passthrough traffic. The Traefik ping entrypoint listens inside the container for healthchecks and is not published on the host.
|
||||
Traefik uses host networking and listens directly on host ports `80/tcp` for HTTP traffic and `443/tcp` for TLS passthrough traffic. The Traefik ping entrypoint listens on host port `8082/tcp` for healthchecks; restrict this port with host firewall rules if needed.
|
||||
|
||||
The HTTP routers only match `/.well-known/acme-challenge/` paths for configured hostnames.
|
||||
The HTTP routers match configured hostnames and forward the full HTTP request to the selected backend. Backend services are responsible for redirects and ACME challenge handling.
|
||||
|
||||
## Container hardening
|
||||
|
||||
@@ -23,7 +23,7 @@ The Traefik service:
|
||||
|
||||
- does not mount the Docker socket;
|
||||
- is not privileged;
|
||||
- drops Linux capabilities;
|
||||
- drops Linux capabilities except `NET_BIND_SERVICE`, which is required to bind host ports `80` and `443`;
|
||||
- enables `no-new-privileges`;
|
||||
- mounts configuration read-only;
|
||||
- uses an explicit Traefik image tag instead of `latest`.
|
||||
|
||||
+5
-5
@@ -12,12 +12,12 @@ docker compose config
|
||||
|
||||
## Smoke test
|
||||
|
||||
After replacing the example backend addresses and starting the stack, verify HTTP-01 routing:
|
||||
After replacing the example backend addresses and starting the stack, verify HTTP routing:
|
||||
|
||||
```bash
|
||||
curl -H 'Host: lab.younerd.org' http://LOAD_BALANCER_HOST/.well-known/acme-challenge/test-token
|
||||
curl -H 'Host: azionelab.org' http://LOAD_BALANCER_HOST/.well-known/acme-challenge/test-token
|
||||
curl -H 'Host: www.azionelab.org' http://LOAD_BALANCER_HOST/.well-known/acme-challenge/test-token
|
||||
curl -H 'Host: lab.younerd.org' http://LOAD_BALANCER_HOST/
|
||||
curl -H 'Host: azionelab.org' http://LOAD_BALANCER_HOST/
|
||||
curl -H 'Host: www.azionelab.org' http://LOAD_BALANCER_HOST/
|
||||
```
|
||||
|
||||
Then verify TCP TLS connectivity:
|
||||
@@ -40,5 +40,5 @@ openssl s_client -connect LOAD_BALANCER_HOST:443 -servername www.azionelab.org
|
||||
|
||||
- Docker/Compose validation: `docker compose config`.
|
||||
- Runtime health: `docker compose ps`.
|
||||
- HTTP-01 route smoke test: `curl`.
|
||||
- HTTP route smoke test: `curl`.
|
||||
- TLS passthrough smoke test: `openssl s_client`.
|
||||
|
||||
Reference in New Issue
Block a user