83 lines
3.1 KiB
Markdown
83 lines
3.1 KiB
Markdown
|
|
# reverse_proxy
|
||
|
|
|
||
|
|
Boma's standard Caddy reverse proxy (ADR-024). Runs on `askari` (the off-site
|
||
|
|
Hetzner host) and terminates TLS for all public-facing services via ACME DNS-01
|
||
|
|
against Gandi LiveDNS. The custom Caddy image (with the `caddy-dns/gandi` plugin)
|
||
|
|
is built directly on the host because `askari` cannot reach the internal registry
|
||
|
|
before the mesh VPN is established.
|
||
|
|
|
||
|
|
## How TLS works
|
||
|
|
|
||
|
|
Caddy obtains wildcard certificates for `*.{{ reverse_proxy__acme_domain }}` using
|
||
|
|
the ACME DNS-01 challenge. The Gandi PAT (`vault.gandi.pat`) is injected into
|
||
|
|
the container at runtime via an `.env` file as `GANDI_BEARER_TOKEN`. Caddy reads
|
||
|
|
it with `{env.GANDI_BEARER_TOKEN}` in the Caddyfile so the secret never lands in a
|
||
|
|
config file on disk.
|
||
|
|
|
||
|
|
## Route catalog — `reverse_proxy__routes`
|
||
|
|
|
||
|
|
Services register themselves as routes by appending an entry to
|
||
|
|
`reverse_proxy__routes` in `group_vars/all/reverse_proxy.yml`:
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
reverse_proxy__routes:
|
||
|
|
- host: netbird.askari.wingu.me
|
||
|
|
upstream: "netbird-management:443"
|
||
|
|
- host: dashboard.askari.wingu.me
|
||
|
|
upstream: "dashboard:8080"
|
||
|
|
```
|
||
|
|
|
||
|
|
Each entry renders a named matcher and a `handle` block in the Caddyfile:
|
||
|
|
|
||
|
|
```
|
||
|
|
@netbird_askari_wingu_me host netbird.askari.wingu.me
|
||
|
|
handle @netbird_askari_wingu_me {
|
||
|
|
reverse_proxy netbird-management:443
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Requests that match no route receive a plain `200 "boma reverse proxy"` response.
|
||
|
|
|
||
|
|
## On-host image build
|
||
|
|
|
||
|
|
The Dockerfile at `roles/reverse_proxy/files/Dockerfile` builds a two-stage image:
|
||
|
|
|
||
|
|
1. `caddy:2-builder` — uses `xcaddy` to compile Caddy with `caddy-dns/gandi`.
|
||
|
|
2. `caddy:2` — copies the compiled binary into the slim runtime image.
|
||
|
|
|
||
|
|
The Ansible role copies the Dockerfile to `{{ reverse_proxy__base_dir }}` and
|
||
|
|
calls `community.docker.docker_image` to build `boma/caddy-gandi:latest` on the
|
||
|
|
host. The build is only re-triggered when the Dockerfile changes
|
||
|
|
(`force_source: "{{ _caddy_dockerfile.changed }}"`).
|
||
|
|
|
||
|
|
For local dev or CI: `make caddy-image`.
|
||
|
|
|
||
|
|
## Variables
|
||
|
|
|
||
|
|
| Variable | Default | Description |
|
||
|
|
|---|---|---|
|
||
|
|
| `reverse_proxy__image` | `boma/caddy-gandi:latest` | Docker image name (built on-host) |
|
||
|
|
| `reverse_proxy__base_dir` | `/opt/services/reverse_proxy` | Working directory for Compose project |
|
||
|
|
| `reverse_proxy__acme_domain` | `example.test` | Wildcard domain for ACME cert |
|
||
|
|
| `reverse_proxy__acme_email` | `admin@example.test` | ACME registration email |
|
||
|
|
| `reverse_proxy__routes` | `[]` | List of `{host, upstream}` route entries |
|
||
|
|
| `reverse_proxy__manage` | `true` | Set `false` in Molecule to skip Docker tasks |
|
||
|
|
|
||
|
|
Production overrides live in
|
||
|
|
`inventories/production/group_vars/all/reverse_proxy.yml`.
|
||
|
|
|
||
|
|
## `reverse_proxy__manage` toggle
|
||
|
|
|
||
|
|
Docker operations (image build, `docker compose up`) are gated on
|
||
|
|
`reverse_proxy__manage | bool`. Set it to `false` in Molecule so the role can be
|
||
|
|
tested (template rendering, directory creation) without a Docker daemon.
|
||
|
|
|
||
|
|
## Secrets
|
||
|
|
|
||
|
|
| Secret path | Usage |
|
||
|
|
|---|---|
|
||
|
|
| `vault.gandi.pat` | Gandi PAT injected as `GANDI_BEARER_TOKEN` |
|
||
|
|
|
||
|
|
Stored encrypted in `inventories/production/group_vars/all/vault.yml`. Decrypt
|
||
|
|
with `make decrypt FILE=...` before editing; re-encrypt after.
|