docs(traffic): document smart tcp entrypoints
- commit
- f02707aa808a4dc26cb94aae78fd2de00fb22eb6
- author
- brice · · 2026-06-30 13:28 UTC
- parent
- 6dc6603
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
16 files · +328 -157
M README.md +2 -2
| @@ -127,7 +127,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf | |||
| 127 | 127 | ## Features | |
| 128 | 128 | ||
| 129 | 129 | - Private Docker registry on your VPS | |
| 130 | - | - Domain-to-container routing via HTTP reverse proxy | |
| 130 | + | - Domain-to-container routing through a smart TCP edge reverse proxy | |
| 131 | 131 | - Automatic deployment on image push | |
| 132 | 132 | - Auto-routing from image labels | |
| 133 | 133 | - Remote CLI management | |
| @@ -138,7 +138,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf | |||
| 138 | 138 | - Single binary, ~15MB RAM | |
| 139 | 139 | ||
| 140 | 140 | > [!NOTE] | |
| 141 | - | > Gordon can terminate TLS directly via its internal CA (default `server.tls_port = 8443`), static certificates, or public ACME certificates. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements. | |
| 141 | + | > Gordon exposes public traffic through entrypoints such as `[entrypoints.edge]` with `protocol = "smart_tcp"`. It can terminate TLS via static certificates, public ACME certificates, or its internal CA. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements. | |
| 142 | 142 | ||
| 143 | 143 | ## Documentation | |
| 144 | 144 | ||
| @@ -127,7 +127,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf | |||||
| 127 | ## Features | 127 | ## Features | ||
| 128 | 128 | ||||
| 129 | - Private Docker registry on your VPS | 129 | - Private Docker registry on your VPS | ||
| 130 | - | - Domain-to-container routing via HTTP reverse proxy | 130 | + | - Domain-to-container routing through a smart TCP edge reverse proxy |
| 131 | - Automatic deployment on image push | 131 | - Automatic deployment on image push | ||
| 132 | - Auto-routing from image labels | 132 | - Auto-routing from image labels | ||
| 133 | - Remote CLI management | 133 | - Remote CLI management | ||
| @@ -138,7 +138,7 @@ See the [Deploy Action README](.github/actions/deploy/README.md) for multi-platf | |||||
| 138 | - Single binary, ~15MB RAM | 138 | - Single binary, ~15MB RAM | ||
| 139 | 139 | ||||
| 140 | > [!NOTE] | 140 | > [!NOTE] | ||
| 141 | - | > Gordon can terminate TLS directly via its internal CA (default `server.tls_port = 8443`), static certificates, or public ACME certificates. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements. | 141 | + | > Gordon exposes public traffic through entrypoints such as `[entrypoints.edge]` with `protocol = "smart_tcp"`. It can terminate TLS via static certificates, public ACME certificates, or its internal CA. Cloudflare and upstream reverse proxies are optional deployment choices, not requirements. |
| 142 | 142 | ||||
| 143 | ## Documentation | 143 | ## Documentation | ||
| 144 | 144 | ||||
M docs/cli/ca.md +2 -2
| @@ -12,7 +12,7 @@ Manage Gordon's internal Certificate Authority. | |||
| 12 | 12 | | `info` | Show CA status (root CN, fingerprint, intermediate expiry) | | |
| 13 | 13 | | `install` | Install/uninstall the root CA in system trust stores | | |
| 14 | 14 | ||
| 15 | - | All subcommands require `server.tls_port` to be non-zero in the Gordon configuration. | |
| 15 | + | All subcommands require Gordon to have TLS-capable HTTPS fallback configured on an entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`. | |
| 16 | 16 | ||
| 17 | 17 | --- | |
| 18 | 18 | ||
| @@ -120,5 +120,5 @@ Visit `http://<gordon-host>/.well-known/gordon/ca` from the device browser. The | |||
| 120 | 120 | ||
| 121 | 121 | ## Related | |
| 122 | 122 | ||
| 123 | - | - [Server Configuration](../config/server.md) — `tls_port`, `force_https_redirect`, and internal CA settings | |
| 123 | + | - [Server Configuration](../config/server.md) — `force_https_redirect`, smart TCP edge, and internal CA settings | |
| 124 | 124 | - [CLI Commands](./index.md) | |
| @@ -12,7 +12,7 @@ Manage Gordon's internal Certificate Authority. | |||||
| 12 | | `info` | Show CA status (root CN, fingerprint, intermediate expiry) | | 12 | | `info` | Show CA status (root CN, fingerprint, intermediate expiry) | | ||
| 13 | | `install` | Install/uninstall the root CA in system trust stores | | 13 | | `install` | Install/uninstall the root CA in system trust stores | | ||
| 14 | 14 | ||||
| 15 | - | All subcommands require `server.tls_port` to be non-zero in the Gordon configuration. | 15 | + | All subcommands require Gordon to have TLS-capable HTTPS fallback configured on an entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`. |
| 16 | 16 | ||||
| 17 | --- | 17 | --- | ||
| 18 | 18 | ||||
| @@ -120,5 +120,5 @@ Visit `http://<gordon-host>/.well-known/gordon/ca` from the device browser. The | |||||
| 120 | 120 | ||||
| 121 | ## Related | 121 | ## Related | ||
| 122 | 122 | ||||
| 123 | - | - [Server Configuration](../config/server.md) — `tls_port`, `force_https_redirect`, and internal CA settings | 123 | + | - [Server Configuration](../config/server.md) — `force_https_redirect`, smart TCP edge, and internal CA settings |
| 124 | - [CLI Commands](./index.md) | 124 | - [CLI Commands](./index.md) | ||
M docs/cli/remotes.md +1 -1
| @@ -350,7 +350,7 @@ A common Tailscale setup is to point your domain's DNS at the machine's Tailscal | |||
| 350 | 350 | - `insecure_tls = true` on the remote — skips certificate verification entirely | |
| 351 | 351 | - `sudo gordon ca install` — installs the root CA into system, Firefox, and Java trust stores | |
| 352 | 352 | - `gordon ca export --out gordon-ca.crt` — exports the root CA PEM for manual installation | |
| 353 | - | - Visit `https://<gordon-host>:<tls_port>/ca` in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android | |
| 353 | + | - Visit `https://<gordon-host>/ca` (or your mapped edge address) in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android | |
| 354 | 354 | ||
| 355 | 355 | If you have your own certificate for the domain (e.g. from a corporate CA), you can provide it via `tls_cert_file`/`tls_key_file` — see [Server Configuration](../config/server.md#custom-certificates). The static cert is served for SNI-matching domains; everything else falls through to the internal CA. | |
| 356 | 356 | ||
| @@ -350,7 +350,7 @@ A common Tailscale setup is to point your domain's DNS at the machine's Tailscal | |||||
| 350 | - `insecure_tls = true` on the remote — skips certificate verification entirely | 350 | - `insecure_tls = true` on the remote — skips certificate verification entirely | ||
| 351 | - `sudo gordon ca install` — installs the root CA into system, Firefox, and Java trust stores | 351 | - `sudo gordon ca install` — installs the root CA into system, Firefox, and Java trust stores | ||
| 352 | - `gordon ca export --out gordon-ca.crt` — exports the root CA PEM for manual installation | 352 | - `gordon ca export --out gordon-ca.crt` — exports the root CA PEM for manual installation | ||
| 353 | - | - Visit `https://<gordon-host>:<tls_port>/ca` in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android | 353 | + | - Visit `https://<gordon-host>/ca` (or your mapped edge address) in a browser — onboarding page with downloads for macOS, Linux, Windows, iOS, and Android |
| 354 | 354 | ||||
| 355 | If you have your own certificate for the domain (e.g. from a corporate CA), you can provide it via `tls_cert_file`/`tls_key_file` — see [Server Configuration](../config/server.md#custom-certificates). The static cert is served for SNI-matching domains; everything else falls through to the internal CA. | 355 | If you have your own certificate for the domain (e.g. from a corporate CA), you can provide it via `tls_cert_file`/`tls_key_file` — see [Server Configuration](../config/server.md#custom-certificates). The static cert is served for SNI-matching domains; everything else falls through to the internal CA. | ||
| 356 | 356 | ||||
M docs/cli/tls.md +8 -0
| @@ -2,6 +2,14 @@ | |||
| 2 | 2 | ||
| 3 | 3 | Inspect public TLS/ACME certificate status. | |
| 4 | 4 | ||
| 5 | + | Gordon serves normal HTTPS fallback on TLS-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Certificate priority is static certificates first, then public ACME certificates, then Gordon's internal CA. | |
| 6 | + | ||
| 7 | + | ACME challenge notes: | |
| 8 | + | ||
| 9 | + | - DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge. | |
| 10 | + | - HTTP-01 requires an HTTP-capable smart TCP entrypoint reachable on external port 80 for every hostname being validated. | |
| 11 | + | - TLS-ALPN-01 is not supported. | |
| 12 | + | ||
| 5 | 13 | Remote targeting uses client config or an active remote by default. | |
| 6 | 14 | Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | |
| 7 | 15 | ||
| @@ -2,6 +2,14 @@ | |||||
| 2 | 2 | ||||
| 3 | Inspect public TLS/ACME certificate status. | 3 | Inspect public TLS/ACME certificate status. | ||
| 4 | 4 | ||||
| 5 | + | Gordon serves normal HTTPS fallback on TLS-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Certificate priority is static certificates first, then public ACME certificates, then Gordon's internal CA. | |||
| 6 | + | ||||
| 7 | + | ACME challenge notes: | |||
| 8 | + | ||||
| 9 | + | - DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge. | |||
| 10 | + | - HTTP-01 requires an HTTP-capable smart TCP entrypoint reachable on external port 80 for every hostname being validated. | |||
| 11 | + | - TLS-ALPN-01 is not supported. | |||
| 12 | + | ||||
| 5 | Remote targeting uses client config or an active remote by default. | 13 | Remote targeting uses client config or an active remote by default. | ||
| 6 | Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | 14 | Use `--remote` and `--token` to override. See [CLI Overview](./index.md). | ||
| 7 | 15 | ||||
M docs/cli/traffic.md +30 -2
| @@ -39,7 +39,21 @@ gordon traffic status --remote https://gordon.example.com --json | |||
| 39 | 39 | "total_refused": 0, | |
| 40 | 40 | "total_errors": 0, | |
| 41 | 41 | "bytes_in": 4096, | |
| 42 | - | "bytes_out": 8192 | |
| 42 | + | "bytes_out": 8192, | |
| 43 | + | "smart_tcp": { | |
| 44 | + | "http_accepted": 0, | |
| 45 | + | "h2c_accepted": 0, | |
| 46 | + | "https_fallback_accepted": 0, | |
| 47 | + | "tls_passthrough_accepted": 0, | |
| 48 | + | "raw_fallback_accepted": 0, | |
| 49 | + | "entrypoint_cidr_refused": 0, | |
| 50 | + | "raw_fallback_cidr_refused": 0, | |
| 51 | + | "proxy_refused": 0, | |
| 52 | + | "unknown_no_fallback_refused": 0, | |
| 53 | + | "malformed_rejected": 0, | |
| 54 | + | "sniff_timeout": 0, | |
| 55 | + | "client_hello_too_large": 0 | |
| 56 | + | } | |
| 43 | 57 | } | |
| 44 | 58 | ], | |
| 45 | 59 | "routers": [], | |
| @@ -51,7 +65,21 @@ gordon traffic status --remote https://gordon.example.com --json | |||
| 51 | 65 | "total_refused": 0, | |
| 52 | 66 | "total_errors": 0, | |
| 53 | 67 | "bytes_in": 4096, | |
| 54 | - | "bytes_out": 8192 | |
| 68 | + | "bytes_out": 8192, | |
| 69 | + | "smart_tcp": { | |
| 70 | + | "http_accepted": 0, | |
| 71 | + | "h2c_accepted": 0, | |
| 72 | + | "https_fallback_accepted": 0, | |
| 73 | + | "tls_passthrough_accepted": 0, | |
| 74 | + | "raw_fallback_accepted": 0, | |
| 75 | + | "entrypoint_cidr_refused": 0, | |
| 76 | + | "raw_fallback_cidr_refused": 0, | |
| 77 | + | "proxy_refused": 0, | |
| 78 | + | "unknown_no_fallback_refused": 0, | |
| 79 | + | "malformed_rejected": 0, | |
| 80 | + | "sniff_timeout": 0, | |
| 81 | + | "client_hello_too_large": 0 | |
| 82 | + | } | |
| 55 | 83 | } | |
| 56 | 84 | } | |
| 57 | 85 | ``` | |
| @@ -39,7 +39,21 @@ gordon traffic status --remote https://gordon.example.com --json | |||||
| 39 | "total_refused": 0, | 39 | "total_refused": 0, | ||
| 40 | "total_errors": 0, | 40 | "total_errors": 0, | ||
| 41 | "bytes_in": 4096, | 41 | "bytes_in": 4096, | ||
| 42 | - | "bytes_out": 8192 | 42 | + | "bytes_out": 8192, |
| 43 | + | "smart_tcp": { | |||
| 44 | + | "http_accepted": 0, | |||
| 45 | + | "h2c_accepted": 0, | |||
| 46 | + | "https_fallback_accepted": 0, | |||
| 47 | + | "tls_passthrough_accepted": 0, | |||
| 48 | + | "raw_fallback_accepted": 0, | |||
| 49 | + | "entrypoint_cidr_refused": 0, | |||
| 50 | + | "raw_fallback_cidr_refused": 0, | |||
| 51 | + | "proxy_refused": 0, | |||
| 52 | + | "unknown_no_fallback_refused": 0, | |||
| 53 | + | "malformed_rejected": 0, | |||
| 54 | + | "sniff_timeout": 0, | |||
| 55 | + | "client_hello_too_large": 0 | |||
| 56 | + | } | |||
| 43 | } | 57 | } | ||
| 44 | ], | 58 | ], | ||
| 45 | "routers": [], | 59 | "routers": [], | ||
| @@ -51,7 +65,21 @@ gordon traffic status --remote https://gordon.example.com --json | |||||
| 51 | "total_refused": 0, | 65 | "total_refused": 0, | ||
| 52 | "total_errors": 0, | 66 | "total_errors": 0, | ||
| 53 | "bytes_in": 4096, | 67 | "bytes_in": 4096, | ||
| 54 | - | "bytes_out": 8192 | 68 | + | "bytes_out": 8192, |
| 69 | + | "smart_tcp": { | |||
| 70 | + | "http_accepted": 0, | |||
| 71 | + | "h2c_accepted": 0, | |||
| 72 | + | "https_fallback_accepted": 0, | |||
| 73 | + | "tls_passthrough_accepted": 0, | |||
| 74 | + | "raw_fallback_accepted": 0, | |||
| 75 | + | "entrypoint_cidr_refused": 0, | |||
| 76 | + | "raw_fallback_cidr_refused": 0, | |||
| 77 | + | "proxy_refused": 0, | |||
| 78 | + | "unknown_no_fallback_refused": 0, | |||
| 79 | + | "malformed_rejected": 0, | |||
| 80 | + | "sniff_timeout": 0, | |||
| 81 | + | "client_hello_too_large": 0 | |||
| 82 | + | } | |||
| 55 | } | 83 | } | ||
| 56 | } | 84 | } | ||
| 57 | ``` | 85 | ``` | ||
M docs/config/index.md +9 -5
| @@ -13,11 +13,13 @@ Gordon uses a single TOML configuration file located at `~/.config/gordon/gordon | |||
| 13 | 13 | ||
| 14 | 14 | ```toml | |
| 15 | 15 | [server] | |
| 16 | - | port = 8088 | |
| 17 | - | tls_port = 8443 | |
| 18 | 16 | registry_port = 5000 | |
| 19 | 17 | gordon_domain = "gordon.mydomain.com" | |
| 20 | 18 | ||
| 19 | + | [entrypoints.edge] | |
| 20 | + | address = ":443" | |
| 21 | + | protocol = "smart_tcp" | |
| 22 | + | ||
| 21 | 23 | [routes] | |
| 22 | 24 | "app.mydomain.com" = "myapp:latest" | |
| 23 | 25 | ``` | |
| @@ -35,11 +37,14 @@ For a complete list of all configuration options with their default values, see | |||
| 35 | 37 | ```toml | |
| 36 | 38 | # Server settings | |
| 37 | 39 | [server] | |
| 38 | - | port = 8088 # HTTP proxy port (default: 8088) | |
| 39 | 40 | registry_port = 5000 # Registry port (default: 5000) | |
| 40 | 41 | gordon_domain = "gordon.mydomain.com" # Required: Gordon domain (registry + API) | |
| 41 | 42 | # data_dir = "~/.gordon" # Default for user installations | |
| 42 | 43 | ||
| 44 | + | [entrypoints.edge] | |
| 45 | + | address = ":443" # Deployment-selected public TCP socket | |
| 46 | + | protocol = "smart_tcp" | |
| 47 | + | ||
| 43 | 48 | # Authentication (includes secrets backend) | |
| 44 | 49 | [auth] | |
| 45 | 50 | enabled = true # Enable registry authentication (default: true) | |
| @@ -183,7 +188,6 @@ keep_last = 3 | |||
| 183 | 188 | ||
| 184 | 189 | | Setting | Default | | |
| 185 | 190 | |---------|---------| | |
| 186 | - | | `server.port` | `8088` | | |
| 187 | 191 | | `server.registry_port` | `5000` | | |
| 188 | 192 | | `server.data_dir` | `~/.gordon` | | |
| 189 | 193 | | `server.max_blob_chunk_size` | `"95MB"` | | |
| @@ -258,7 +262,7 @@ gordon reload | |||
| 258 | 262 | ||
| 259 | 263 | | Setting | | |
| 260 | 264 | |---------| | |
| 261 | - | | `server.port` | | |
| 265 | + | | `entrypoints.edge.address` | | |
| 262 | 266 | | `server.data_dir` | | |
| 263 | 267 | | `server.max_blob_chunk_size` | | |
| 264 | 268 | | `server.max_blob_size` | | |
| @@ -13,11 +13,13 @@ Gordon uses a single TOML configuration file located at `~/.config/gordon/gordon | |||||
| 13 | 13 | ||||
| 14 | ```toml | 14 | ```toml | ||
| 15 | [server] | 15 | [server] | ||
| 16 | - | port = 8088 | |||
| 17 | - | tls_port = 8443 | |||
| 18 | registry_port = 5000 | 16 | registry_port = 5000 | ||
| 19 | gordon_domain = "gordon.mydomain.com" | 17 | gordon_domain = "gordon.mydomain.com" | ||
| 20 | 18 | ||||
| 19 | + | [entrypoints.edge] | |||
| 20 | + | address = ":443" | |||
| 21 | + | protocol = "smart_tcp" | |||
| 22 | + | ||||
| 21 | [routes] | 23 | [routes] | ||
| 22 | "app.mydomain.com" = "myapp:latest" | 24 | "app.mydomain.com" = "myapp:latest" | ||
| 23 | ``` | 25 | ``` | ||
| @@ -35,11 +37,14 @@ For a complete list of all configuration options with their default values, see | |||||
| 35 | ```toml | 37 | ```toml | ||
| 36 | # Server settings | 38 | # Server settings | ||
| 37 | [server] | 39 | [server] | ||
| 38 | - | port = 8088 # HTTP proxy port (default: 8088) | |||
| 39 | registry_port = 5000 # Registry port (default: 5000) | 40 | registry_port = 5000 # Registry port (default: 5000) | ||
| 40 | gordon_domain = "gordon.mydomain.com" # Required: Gordon domain (registry + API) | 41 | gordon_domain = "gordon.mydomain.com" # Required: Gordon domain (registry + API) | ||
| 41 | # data_dir = "~/.gordon" # Default for user installations | 42 | # data_dir = "~/.gordon" # Default for user installations | ||
| 42 | 43 | ||||
| 44 | + | [entrypoints.edge] | |||
| 45 | + | address = ":443" # Deployment-selected public TCP socket | |||
| 46 | + | protocol = "smart_tcp" | |||
| 47 | + | ||||
| 43 | # Authentication (includes secrets backend) | 48 | # Authentication (includes secrets backend) | ||
| 44 | [auth] | 49 | [auth] | ||
| 45 | enabled = true # Enable registry authentication (default: true) | 50 | enabled = true # Enable registry authentication (default: true) | ||
| @@ -183,7 +188,6 @@ keep_last = 3 | |||||
| 183 | 188 | ||||
| 184 | | Setting | Default | | 189 | | Setting | Default | | ||
| 185 | |---------|---------| | 190 | |---------|---------| | ||
| 186 | - | | `server.port` | `8088` | | |||
| 187 | | `server.registry_port` | `5000` | | 191 | | `server.registry_port` | `5000` | | ||
| 188 | | `server.data_dir` | `~/.gordon` | | 192 | | `server.data_dir` | `~/.gordon` | | ||
| 189 | | `server.max_blob_chunk_size` | `"95MB"` | | 193 | | `server.max_blob_chunk_size` | `"95MB"` | | ||
| @@ -258,7 +262,7 @@ gordon reload | |||||
| 258 | 262 | ||||
| 259 | | Setting | | 263 | | Setting | | ||
| 260 | |---------| | 264 | |---------| | ||
| 261 | - | | `server.port` | | 265 | + | | `entrypoints.edge.address` | |
| 262 | | `server.data_dir` | | 266 | | `server.data_dir` | | ||
| 263 | | `server.max_blob_chunk_size` | | 267 | | `server.max_blob_chunk_size` | | ||
| 264 | | `server.max_blob_size` | | 268 | | `server.max_blob_size` | | ||
M docs/config/rate-limiting.md +1 -1
| @@ -150,7 +150,7 @@ A burst of `100` with `per_ip_rps = 50` means a client can send 100 requests ins | |||
| 150 | 150 | ||
| 151 | 151 | **Critical for correct IP detection in production.** | |
| 152 | 152 | ||
| 153 | - | Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (via its internal CA on `server.tls_port`) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer. | |
| 153 | + | Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (on a smart TCP edge entrypoint via its internal CA) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer. | |
| 154 | 154 | ||
| 155 | 155 | ``` | |
| 156 | 156 | Internet → [Cloudflare/nginx] → Gordon → Containers | |
| @@ -150,7 +150,7 @@ A burst of `100` with `per_ip_rps = 50` means a client can send 100 requests ins | |||||
| 150 | 150 | ||||
| 151 | **Critical for correct IP detection in production.** | 151 | **Critical for correct IP detection in production.** | ||
| 152 | 152 | ||||
| 153 | - | Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (via its internal CA on `server.tls_port`) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer. | 153 | + | Gordon is a reverse proxy that routes requests to your containers. In production you can either terminate TLS directly in Gordon (on a smart TCP edge entrypoint via its internal CA) or place it behind a TLS-terminating proxy like Cloudflare, nginx, or a load balancer. |
| 154 | 154 | ||||
| 155 | ``` | 155 | ``` | ||
| 156 | Internet → [Cloudflare/nginx] → Gordon → Containers | 156 | Internet → [Cloudflare/nginx] → Gordon → Containers | ||
M docs/config/reference.md +21 -8
| @@ -9,10 +9,8 @@ Complete configuration reference with all options and their default values. | |||
| 9 | 9 | # SERVER | |
| 10 | 10 | # ============================================================================= | |
| 11 | 11 | [server] | |
| 12 | - | port = 8088 # HTTP proxy port | |
| 13 | 12 | registry_port = 5000 # Container registry port | |
| 14 | - | tls_port = 8443 # HTTPS proxy port (0 = disabled, no internal CA) | |
| 15 | - | tls_cert_file = "" # PEM cert path (optional, for static TLS alongside internal CA) | |
| 13 | + | tls_cert_file = "" # PEM cert path (optional, for static TLS fallback) | |
| 16 | 14 | tls_key_file = "" # PEM key path (optional, must be set with tls_cert_file) | |
| 17 | 15 | force_https_redirect = false # Redirect all HTTP traffic to HTTPS (for direct-access setups) | |
| 18 | 16 | gordon_domain = "" # Required: Gordon domain (registry + API) | |
| @@ -20,9 +18,20 @@ data_dir = "~/.gordon" # Data directory (varies by install | |||
| 20 | 18 | max_blob_chunk_size = "95MB" # Max size per registry blob upload chunk | |
| 21 | 19 | max_blob_size = "1GB" # Max cumulative size per registry blob/layer upload | |
| 22 | 20 | registry_allowed_ips = [] # IPs or CIDR ranges allowed to access the registry (empty = allow all) | |
| 23 | - | proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach the proxy (empty = allow all, e.g. Cloudflare IPs) | |
| 21 | + | proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all, e.g. Cloudflare IPs) | |
| 24 | 22 | registry_listen_address = "" # Bind address for registry (empty = all interfaces, "127.0.0.1" = loopback only) | |
| 25 | 23 | ||
| 24 | + | # ============================================================================= | |
| 25 | + | # ENTRYPOINTS | |
| 26 | + | # ============================================================================= | |
| 27 | + | [entrypoints.edge] | |
| 28 | + | address = ":443" # Deployment-selected public TCP socket | |
| 29 | + | protocol = "smart_tcp" # Sniff HTTP, h2c, TLS, passthrough, or explicit raw fallback | |
| 30 | + | trusted_cidrs = [] # Peer socket IP allowlist for all traffic on this entrypoint | |
| 31 | + | # raw_fallback = "ssh-fallback" # Optional TCP router for unknown non-HTTP/non-TLS bytes | |
| 32 | + | # raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |
| 33 | + | # allow_public_raw_fallback = false | |
| 34 | + | ||
| 26 | 35 | # ============================================================================= | |
| 27 | 36 | # DNS | |
| 28 | 37 | # ============================================================================= | |
| @@ -45,7 +54,7 @@ access_token_ttl = "15m" # Ephemeral access token lifetime ( | |||
| 45 | 54 | # PUBLIC TLS / ACME | |
| 46 | 55 | # ============================================================================= | |
| 47 | 56 | [tls.acme] | |
| 48 | - | enabled = false # Enable public ACME certificates (requires server.tls_port > 0) | |
| 57 | + | enabled = false # Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint) | |
| 49 | 58 | email = "" # ACME account email when enabled | |
| 50 | 59 | challenge = "auto" # "auto", "http-01", or "cloudflare-dns-01" | |
| 51 | 60 | obtain_batch_size = 1 # New certificate orders per reconcile run | |
| @@ -210,9 +219,7 @@ keep_last = 3 # Keep N newest tags per repository | |||
| 210 | 219 | ||
| 211 | 220 | | Setting | Default | Description | | |
| 212 | 221 | |---------|---------|-------------| | |
| 213 | - | | `server.port` | `8088` | HTTP proxy port | | |
| 214 | 222 | | `server.registry_port` | `5000` | Container registry port | | |
| 215 | - | | `server.tls_port` | `8443` | HTTPS listener port (0 = disabled) | | |
| 216 | 223 | | `server.tls_cert_file` | `""` | PEM cert path for static TLS (optional) | | |
| 217 | 224 | | `server.tls_key_file` | `""` | PEM key path for static TLS (optional) | | |
| 218 | 225 | | `server.force_https_redirect` | `false` | Redirect all HTTP to HTTPS (for direct-access setups) | | |
| @@ -223,10 +230,16 @@ keep_last = 3 # Keep N newest tags per repository | |||
| 223 | 230 | | `server.registry_allowed_ips` | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) | | |
| 224 | 231 | | `server.proxy_allowed_ips` | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) | | |
| 225 | 232 | | `server.registry_listen_address` | `""` | Bind address for registry (empty = all interfaces) | | |
| 233 | + | | `entrypoints.<name>.address` | none | Deployment-selected listen address; `edge` is conventional but has no built-in port | | |
| 234 | + | | `entrypoints.<name>.protocol` | none | Entrypoint protocol: `smart_tcp`, `http`, `tls_mux`, `tcp`, or `udp` | | |
| 235 | + | | `entrypoints.<name>.trusted_cidrs` | `[]` | Peer socket IP allowlist for all traffic on the entrypoint | | |
| 236 | + | | `entrypoints.<name>.raw_fallback` | `""` | TCP router used by smart TCP for unknown non-HTTP/non-TLS bytes | | |
| 237 | + | | `entrypoints.<name>.raw_fallback_trusted_cidrs` | `[]` | Peer socket IP allowlist for smart TCP raw fallback | | |
| 238 | + | | `entrypoints.<name>.allow_public_raw_fallback` | `false` | Explicit acknowledgement for public raw fallback exposure | | |
| 226 | 239 | | `dns.resolvers` | `["1.1.1.1:53", "8.8.8.8:53"]` | Recursive resolvers used for public DNS visibility checks, including ACME DNS-01 propagation | | |
| 227 | 240 | | `dns.propagation_timeout` | `"5m"` | Maximum time to wait for DNS-01 TXT records to become visible through configured recursive resolvers | | |
| 228 | 241 | | `dns.polling_interval` | `"5s"` | Interval between DNS-01 propagation checks | | |
| 229 | - | | `tls.acme.enabled` | `false` | Enable public ACME certificates (requires `server.tls_port > 0`) | | |
| 242 | + | | `tls.acme.enabled` | `false` | Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint) | | |
| 230 | 243 | | `tls.acme.email` | `""` | ACME account email when enabled | | |
| 231 | 244 | | `tls.acme.challenge` | `"auto"` | ACME challenge mode: `auto`, `http-01`, or `cloudflare-dns-01` | | |
| 232 | 245 | | `tls.acme.obtain_batch_size` | `1` | Maximum new ACME certificate orders per reconcile run | | |
| @@ -9,10 +9,8 @@ Complete configuration reference with all options and their default values. | |||||
| 9 | # SERVER | 9 | # SERVER | ||
| 10 | # ============================================================================= | 10 | # ============================================================================= | ||
| 11 | [server] | 11 | [server] | ||
| 12 | - | port = 8088 # HTTP proxy port | |||
| 13 | registry_port = 5000 # Container registry port | 12 | registry_port = 5000 # Container registry port | ||
| 14 | - | tls_port = 8443 # HTTPS proxy port (0 = disabled, no internal CA) | 13 | + | tls_cert_file = "" # PEM cert path (optional, for static TLS fallback) |
| 15 | - | tls_cert_file = "" # PEM cert path (optional, for static TLS alongside internal CA) | |||
| 16 | tls_key_file = "" # PEM key path (optional, must be set with tls_cert_file) | 14 | tls_key_file = "" # PEM key path (optional, must be set with tls_cert_file) | ||
| 17 | force_https_redirect = false # Redirect all HTTP traffic to HTTPS (for direct-access setups) | 15 | force_https_redirect = false # Redirect all HTTP traffic to HTTPS (for direct-access setups) | ||
| 18 | gordon_domain = "" # Required: Gordon domain (registry + API) | 16 | gordon_domain = "" # Required: Gordon domain (registry + API) | ||
| @@ -20,9 +18,20 @@ data_dir = "~/.gordon" # Data directory (varies by install | |||||
| 20 | max_blob_chunk_size = "95MB" # Max size per registry blob upload chunk | 18 | max_blob_chunk_size = "95MB" # Max size per registry blob upload chunk | ||
| 21 | max_blob_size = "1GB" # Max cumulative size per registry blob/layer upload | 19 | max_blob_size = "1GB" # Max cumulative size per registry blob/layer upload | ||
| 22 | registry_allowed_ips = [] # IPs or CIDR ranges allowed to access the registry (empty = allow all) | 20 | registry_allowed_ips = [] # IPs or CIDR ranges allowed to access the registry (empty = allow all) | ||
| 23 | - | proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach the proxy (empty = allow all, e.g. Cloudflare IPs) | 21 | + | proxy_allowed_ips = [] # IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all, e.g. Cloudflare IPs) |
| 24 | registry_listen_address = "" # Bind address for registry (empty = all interfaces, "127.0.0.1" = loopback only) | 22 | registry_listen_address = "" # Bind address for registry (empty = all interfaces, "127.0.0.1" = loopback only) | ||
| 25 | 23 | ||||
| 24 | + | # ============================================================================= | |||
| 25 | + | # ENTRYPOINTS | |||
| 26 | + | # ============================================================================= | |||
| 27 | + | [entrypoints.edge] | |||
| 28 | + | address = ":443" # Deployment-selected public TCP socket | |||
| 29 | + | protocol = "smart_tcp" # Sniff HTTP, h2c, TLS, passthrough, or explicit raw fallback | |||
| 30 | + | trusted_cidrs = [] # Peer socket IP allowlist for all traffic on this entrypoint | |||
| 31 | + | # raw_fallback = "ssh-fallback" # Optional TCP router for unknown non-HTTP/non-TLS bytes | |||
| 32 | + | # raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |||
| 33 | + | # allow_public_raw_fallback = false | |||
| 34 | + | ||||
| 26 | # ============================================================================= | 35 | # ============================================================================= | ||
| 27 | # DNS | 36 | # DNS | ||
| 28 | # ============================================================================= | 37 | # ============================================================================= | ||
| @@ -45,7 +54,7 @@ access_token_ttl = "15m" # Ephemeral access token lifetime ( | |||||
| 45 | # PUBLIC TLS / ACME | 54 | # PUBLIC TLS / ACME | ||
| 46 | # ============================================================================= | 55 | # ============================================================================= | ||
| 47 | [tls.acme] | 56 | [tls.acme] | ||
| 48 | - | enabled = false # Enable public ACME certificates (requires server.tls_port > 0) | 57 | + | enabled = false # Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint) |
| 49 | email = "" # ACME account email when enabled | 58 | email = "" # ACME account email when enabled | ||
| 50 | challenge = "auto" # "auto", "http-01", or "cloudflare-dns-01" | 59 | challenge = "auto" # "auto", "http-01", or "cloudflare-dns-01" | ||
| 51 | obtain_batch_size = 1 # New certificate orders per reconcile run | 60 | obtain_batch_size = 1 # New certificate orders per reconcile run | ||
| @@ -210,9 +219,7 @@ keep_last = 3 # Keep N newest tags per repository | |||||
| 210 | 219 | ||||
| 211 | | Setting | Default | Description | | 220 | | Setting | Default | Description | | ||
| 212 | |---------|---------|-------------| | 221 | |---------|---------|-------------| | ||
| 213 | - | | `server.port` | `8088` | HTTP proxy port | | |||
| 214 | | `server.registry_port` | `5000` | Container registry port | | 222 | | `server.registry_port` | `5000` | Container registry port | | ||
| 215 | - | | `server.tls_port` | `8443` | HTTPS listener port (0 = disabled) | | |||
| 216 | | `server.tls_cert_file` | `""` | PEM cert path for static TLS (optional) | | 223 | | `server.tls_cert_file` | `""` | PEM cert path for static TLS (optional) | | ||
| 217 | | `server.tls_key_file` | `""` | PEM key path for static TLS (optional) | | 224 | | `server.tls_key_file` | `""` | PEM key path for static TLS (optional) | | ||
| 218 | | `server.force_https_redirect` | `false` | Redirect all HTTP to HTTPS (for direct-access setups) | | 225 | | `server.force_https_redirect` | `false` | Redirect all HTTP to HTTPS (for direct-access setups) | | ||
| @@ -223,10 +230,16 @@ keep_last = 3 # Keep N newest tags per repository | |||||
| 223 | | `server.registry_allowed_ips` | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) | | 230 | | `server.registry_allowed_ips` | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) | | ||
| 224 | | `server.proxy_allowed_ips` | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) | | 231 | | `server.proxy_allowed_ips` | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) | | ||
| 225 | | `server.registry_listen_address` | `""` | Bind address for registry (empty = all interfaces) | | 232 | | `server.registry_listen_address` | `""` | Bind address for registry (empty = all interfaces) | | ||
| 233 | + | | `entrypoints.<name>.address` | none | Deployment-selected listen address; `edge` is conventional but has no built-in port | | |||
| 234 | + | | `entrypoints.<name>.protocol` | none | Entrypoint protocol: `smart_tcp`, `http`, `tls_mux`, `tcp`, or `udp` | | |||
| 235 | + | | `entrypoints.<name>.trusted_cidrs` | `[]` | Peer socket IP allowlist for all traffic on the entrypoint | | |||
| 236 | + | | `entrypoints.<name>.raw_fallback` | `""` | TCP router used by smart TCP for unknown non-HTTP/non-TLS bytes | | |||
| 237 | + | | `entrypoints.<name>.raw_fallback_trusted_cidrs` | `[]` | Peer socket IP allowlist for smart TCP raw fallback | | |||
| 238 | + | | `entrypoints.<name>.allow_public_raw_fallback` | `false` | Explicit acknowledgement for public raw fallback exposure | | |||
| 226 | | `dns.resolvers` | `["1.1.1.1:53", "8.8.8.8:53"]` | Recursive resolvers used for public DNS visibility checks, including ACME DNS-01 propagation | | 239 | | `dns.resolvers` | `["1.1.1.1:53", "8.8.8.8:53"]` | Recursive resolvers used for public DNS visibility checks, including ACME DNS-01 propagation | | ||
| 227 | | `dns.propagation_timeout` | `"5m"` | Maximum time to wait for DNS-01 TXT records to become visible through configured recursive resolvers | | 240 | | `dns.propagation_timeout` | `"5m"` | Maximum time to wait for DNS-01 TXT records to become visible through configured recursive resolvers | | ||
| 228 | | `dns.polling_interval` | `"5s"` | Interval between DNS-01 propagation checks | | 241 | | `dns.polling_interval` | `"5s"` | Interval between DNS-01 propagation checks | | ||
| 229 | - | | `tls.acme.enabled` | `false` | Enable public ACME certificates (requires `server.tls_port > 0`) | | 242 | + | | `tls.acme.enabled` | `false` | Enable public ACME certificates (requires HTTPS fallback on a TLS-capable entrypoint) | |
| 230 | | `tls.acme.email` | `""` | ACME account email when enabled | | 243 | | `tls.acme.email` | `""` | ACME account email when enabled | | ||
| 231 | | `tls.acme.challenge` | `"auto"` | ACME challenge mode: `auto`, `http-01`, or `cloudflare-dns-01` | | 244 | | `tls.acme.challenge` | `"auto"` | ACME challenge mode: `auto`, `http-01`, or `cloudflare-dns-01` | | ||
| 232 | | `tls.acme.obtain_batch_size` | `1` | Maximum new ACME certificate orders per reconcile run | | 245 | | `tls.acme.obtain_batch_size` | `1` | Maximum new ACME certificate orders per reconcile run | | ||
M docs/config/security-hardening.md +29 -0
| @@ -56,6 +56,35 @@ require_digest = true | |||
| 56 | 56 | - `require_digest = true` requires allowlisted external images to use `@sha256:<64 hex chars>`. | |
| 57 | 57 | - Include ports in allowlist entries when the registry uses a non-default port. | |
| 58 | 58 | ||
| 59 | + | ## Smart TCP Raw Fallback | |
| 60 | + | ||
| 61 | + | Raw TCP fallback on a `smart_tcp` edge entrypoint is disabled by default. Unknown non-HTTP/non-TLS bytes reach a backend only when the entrypoint explicitly sets `raw_fallback` to a TCP router and the fallback source policy allows the peer. | |
| 62 | + | ||
| 63 | + | ```toml | |
| 64 | + | [entrypoints.edge] | |
| 65 | + | address = ":443" | |
| 66 | + | protocol = "smart_tcp" | |
| 67 | + | raw_fallback = "ssh-fallback" | |
| 68 | + | raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |
| 69 | + | ``` | |
| 70 | + | ||
| 71 | + | Use `raw_fallback_trusted_cidrs` for private raw fallback. Public raw fallback requires an explicit acknowledgement: | |
| 72 | + | ||
| 73 | + | ```toml | |
| 74 | + | [entrypoints.edge] | |
| 75 | + | address = ":443" | |
| 76 | + | protocol = "smart_tcp" | |
| 77 | + | raw_fallback = "public-raw" | |
| 78 | + | allow_public_raw_fallback = true | |
| 79 | + | ``` | |
| 80 | + | ||
| 81 | + | Security behavior: | |
| 82 | + | ||
| 83 | + | - PROXY protocol v1 and v2 prefixes are rejected; Gordon does not trust or parse PROXY headers on smart TCP entrypoints. | |
| 84 | + | - `trusted_cidrs` and `raw_fallback_trusted_cidrs` use the peer socket IP, not `X-Forwarded-For`. | |
| 85 | + | - HTTP-looking or TLS-looking malformed traffic is rejected and never bypasses to raw fallback. | |
| 86 | + | - Entry-point-wide `trusted_cidrs` applies before sniffing to all protocols; raw fallback has its own narrower policy for private fallback exposure. | |
| 87 | + | ||
| 59 | 88 | ## Docker network isolation | |
| 60 | 89 | ||
| 61 | 90 | Per-app Docker networks are enabled by default. To block direct external egress from isolated networks, opt into Docker internal networks: | |
| @@ -56,6 +56,35 @@ require_digest = true | |||||
| 56 | - `require_digest = true` requires allowlisted external images to use `@sha256:<64 hex chars>`. | 56 | - `require_digest = true` requires allowlisted external images to use `@sha256:<64 hex chars>`. | ||
| 57 | - Include ports in allowlist entries when the registry uses a non-default port. | 57 | - Include ports in allowlist entries when the registry uses a non-default port. | ||
| 58 | 58 | ||||
| 59 | + | ## Smart TCP Raw Fallback | |||
| 60 | + | ||||
| 61 | + | Raw TCP fallback on a `smart_tcp` edge entrypoint is disabled by default. Unknown non-HTTP/non-TLS bytes reach a backend only when the entrypoint explicitly sets `raw_fallback` to a TCP router and the fallback source policy allows the peer. | |||
| 62 | + | ||||
| 63 | + | ```toml | |||
| 64 | + | [entrypoints.edge] | |||
| 65 | + | address = ":443" | |||
| 66 | + | protocol = "smart_tcp" | |||
| 67 | + | raw_fallback = "ssh-fallback" | |||
| 68 | + | raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |||
| 69 | + | ``` | |||
| 70 | + | ||||
| 71 | + | Use `raw_fallback_trusted_cidrs` for private raw fallback. Public raw fallback requires an explicit acknowledgement: | |||
| 72 | + | ||||
| 73 | + | ```toml | |||
| 74 | + | [entrypoints.edge] | |||
| 75 | + | address = ":443" | |||
| 76 | + | protocol = "smart_tcp" | |||
| 77 | + | raw_fallback = "public-raw" | |||
| 78 | + | allow_public_raw_fallback = true | |||
| 79 | + | ``` | |||
| 80 | + | ||||
| 81 | + | Security behavior: | |||
| 82 | + | ||||
| 83 | + | - PROXY protocol v1 and v2 prefixes are rejected; Gordon does not trust or parse PROXY headers on smart TCP entrypoints. | |||
| 84 | + | - `trusted_cidrs` and `raw_fallback_trusted_cidrs` use the peer socket IP, not `X-Forwarded-For`. | |||
| 85 | + | - HTTP-looking or TLS-looking malformed traffic is rejected and never bypasses to raw fallback. | |||
| 86 | + | - Entry-point-wide `trusted_cidrs` applies before sniffing to all protocols; raw fallback has its own narrower policy for private fallback exposure. | |||
| 87 | + | ||||
| 59 | ## Docker network isolation | 88 | ## Docker network isolation | ||
| 60 | 89 | ||||
| 61 | Per-app Docker networks are enabled by default. To block direct external egress from isolated networks, opt into Docker internal networks: | 90 | Per-app Docker networks are enabled by default. To block direct external egress from isolated networks, opt into Docker internal networks: | ||
M docs/config/server.md +53 -73
| @@ -6,29 +6,29 @@ Core server settings for Gordon. | |||
| 6 | 6 | ||
| 7 | 7 | ```toml | |
| 8 | 8 | [server] | |
| 9 | - | port = 8088 # HTTP proxy port | |
| 10 | 9 | registry_port = 5000 # Container registry port | |
| 11 | - | tls_port = 8443 # HTTPS listener port (0 = disabled) | |
| 10 | + | gordon_domain = "gordon.mydomain.com" # Gordon domain (required) | |
| 12 | 11 | # tls_cert_file = "" # Optional: PEM certificate for static TLS | |
| 13 | 12 | # tls_key_file = "" # Optional: PEM key for static TLS | |
| 14 | - | # force_https_redirect = false # Redirect all HTTP traffic to HTTPS | |
| 15 | - | gordon_domain = "gordon.mydomain.com" # Gordon domain (required) | |
| 13 | + | # force_https_redirect = false # Redirect cleartext HTTP requests to HTTPS | |
| 16 | 14 | # legacy_registry_domains = ["registry.example.com:5000"] | |
| 17 | 15 | # data_dir = "~/.gordon" # Data storage directory (default) | |
| 18 | 16 | max_blob_chunk_size = "95MB" # Max registry blob upload chunk | |
| 19 | 17 | max_blob_size = "1GB" # Max cumulative registry blob/layer upload | |
| 18 | + | ||
| 19 | + | [entrypoints.edge] | |
| 20 | + | address = ":443" # Deployment-selected public TCP socket | |
| 21 | + | protocol = "smart_tcp" | |
| 20 | 22 | ``` | |
| 21 | 23 | ||
| 22 | 24 | ## Options | |
| 23 | 25 | ||
| 24 | 26 | | Option | Type | Default | Description | | |
| 25 | 27 | |--------|------|---------|-------------| | |
| 26 | - | | `port` | int | `8088` | HTTP proxy port for routing traffic to containers | | |
| 27 | 28 | | `registry_port` | int | `5000` | Docker registry port for image push/pull | | |
| 28 | - | | `tls_port` | int | `8443` | HTTPS listener port with internal CA. Set to `0` to disable TLS entirely | | |
| 29 | - | | `tls_cert_file` | string | `""` | Path to PEM certificate file. When set (with `tls_key_file`), this cert is served for matching SNI domains; unmatched domains fall through to the internal CA | | |
| 29 | + | | `tls_cert_file` | string | `""` | Path to PEM certificate file. When set with `tls_key_file`, matching SNI domains use this static cert before ACME or internal CA certs | | |
| 30 | 30 | | `tls_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` | | |
| 31 | - | | `force_https_redirect` | bool | `false` | Redirect all HTTP requests to the HTTPS port. For direct-access setups without a TLS-terminating proxy | | |
| 31 | + | | `force_https_redirect` | bool | `false` | Redirect cleartext HTTP requests to HTTPS on HTTP-capable edge entrypoints | | |
| 32 | 32 | | `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) | | |
| 33 | 33 | | `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. | | |
| 34 | 34 | | `legacy_registry_domains` | []string | `[]` | Additional Gordon registry hosts treated as aliases during staged migration. See [Upgrading: Staged Registry Host Rename](../upgrading.md#staged-registry-host-rename). | | |
| @@ -37,46 +37,33 @@ max_blob_size = "1GB" # Max cumulative registry blob/layer up | |||
| 37 | 37 | | `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk | | |
| 38 | 38 | | `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload | | |
| 39 | 39 | | `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) | | |
| 40 | - | | `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) | | |
| 40 | + | | `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all) | | |
| 41 | 41 | | `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) | | |
| 42 | 42 | ||
| 43 | - | ## Port Configuration | |
| 44 | - | ||
| 45 | - | ### Proxy Port | |
| 46 | - | ||
| 47 | - | The `port` setting controls where Gordon listens for HTTP traffic: | |
| 43 | + | Public application traffic is configured under `[entrypoints]`. The standard edge listener is a `smart_tcp` socket: | |
| 48 | 44 | ||
| 49 | 45 | ```toml | |
| 50 | - | [server] | |
| 51 | - | port = 8088 # Default high port for rootless or forwarded setups | |
| 52 | - | ``` | |
| 53 | - | ||
| 54 | - | For rootless containers, you'll typically use a high port and configure firewall port forwarding: | |
| 55 | - | ||
| 56 | - | ```bash | |
| 57 | - | # Forward port 80 to 8088 | |
| 58 | - | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088 | |
| 46 | + | [entrypoints.edge] | |
| 47 | + | address = ":443" | |
| 48 | + | protocol = "smart_tcp" | |
| 59 | 49 | ``` | |
| 60 | 50 | ||
| 61 | - | ### Internal CA and TLS | |
| 51 | + | `entrypoints.edge.address` is not an HTTP or HTTPS port. It is the TCP address Gordon sniffs for cleartext HTTP, h2c, TLS passthrough, normal HTTPS fallback, or explicit raw TCP fallback. Gordon does not provide a built-in default public port; choose `:443`, a high port such as `:9000`, or container/firewall mappings to match your deployment. | |
| 62 | 52 | ||
| 63 | - | Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains. The HTTPS listener is enabled by default on `tls_port` (8443). | |
| 53 | + | ## Internal CA and TLS | |
| 64 | 54 | ||
| 65 | - | ```toml | |
| 66 | - | [server] | |
| 67 | - | tls_port = 8443 # Default. Set to 0 to disable TLS entirely. | |
| 68 | - | ``` | |
| 55 | + | Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains served through HTTPS fallback on a TLS-capable entrypoint such as `smart_tcp`. | |
| 69 | 56 | ||
| 70 | - | With TLS enabled: | |
| 57 | + | With TLS-capable edge traffic: | |
| 71 | 58 | - Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/` | |
| 72 | 59 | - Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory | |
| 73 | 60 | - The intermediate CA auto-renews before expiry | |
| 74 | - | - An onboarding page at `https://<gordon-host>:<tls_port>/.well-known/gordon/` lets clients download the root CA certificate | |
| 61 | + | - An onboarding page at `https://<gordon-host>/.well-known/gordon/` lets clients download the root CA certificate | |
| 75 | 62 | - The root CA is stable across restarts (generated once, persisted to disk) | |
| 76 | 63 | ||
| 77 | 64 | #### Custom certificates | |
| 78 | 65 | ||
| 79 | - | To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside the internal CA: | |
| 66 | + | To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside ACME/internal CA fallback: | |
| 80 | 67 | ||
| 81 | 68 | ```toml | |
| 82 | 69 | [server] | |
| @@ -84,15 +71,16 @@ tls_cert_file = "/etc/gordon/tls/cert.pem" | |||
| 84 | 71 | tls_key_file = "/etc/gordon/tls/key.pem" | |
| 85 | 72 | ``` | |
| 86 | 73 | ||
| 87 | - | The static certificate is served for SNI-matching domains (including wildcards). All other domains get on-demand certs from the internal CA. | |
| 74 | + | Certificate priority for normal HTTPS fallback is: static certificates first, then public ACME certificates, then Gordon's internal CA. | |
| 88 | 75 | ||
| 89 | 76 | #### Public ACME certificates | |
| 90 | 77 | ||
| 91 | - | Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. The HTTPS listener must also be enabled (`server.tls_port > 0`). | |
| 78 | + | Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. Normal HTTPS fallback must be available on a TLS-capable entrypoint. | |
| 92 | 79 | ||
| 93 | 80 | ```toml | |
| 94 | - | [server] | |
| 95 | - | tls_port = 8443 | |
| 81 | + | [entrypoints.edge] | |
| 82 | + | address = ":443" | |
| 83 | + | protocol = "smart_tcp" | |
| 96 | 84 | ||
| 97 | 85 | [dns] | |
| 98 | 86 | resolvers = ["1.1.1.1:53", "8.8.8.8:53"] | |
| @@ -107,11 +95,12 @@ obtain_batch_size = 1 # maximum new certificate orders per reconcile run | |||
| 107 | 95 | ``` | |
| 108 | 96 | ||
| 109 | 97 | Challenge behavior: | |
| 110 | - | - `http-01` serves `/.well-known/acme-challenge/<token>` on the HTTP proxy port. | |
| 111 | - | - `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`. | |
| 98 | + | - `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`; it does not need a special public port 80 edge. | |
| 99 | + | - `http-01` serves `/.well-known/acme-challenge/<token>` through Gordon's HTTP handler and requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated. | |
| 112 | 100 | - `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01. | |
| 101 | + | - `tls-alpn-01` is not supported. | |
| 113 | 102 | ||
| 114 | - | HTTP-01 requires public access to port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to Gordon's HTTP proxy port. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation. | |
| 103 | + | HTTP-01 requires public access to external port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to the HTTP-capable smart TCP entrypoint. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation. | |
| 115 | 104 | ||
| 116 | 105 | Gordon limits new ACME certificate orders to `obtain_batch_size` per reconcile run (default `1`) so enabling ACME on an existing multi-route server does not burst through every route and hit Let's Encrypt rate limits. Later reloads, restarts, or other explicit reconcile runs continue issuing remaining certificates. | |
| 117 | 106 | ||
| @@ -129,7 +118,7 @@ Because Gordon's ACME challenge mode is global, `cloudflare-dns-01` requires a C | |||
| 129 | 118 | ||
| 130 | 119 | #### Direct HTTP Onboarding | |
| 131 | 120 | ||
| 132 | - | When `tls_port` is non-zero, direct HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only: | |
| 121 | + | When Gordon is serving TLS-capable edge traffic, direct cleartext HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only: | |
| 133 | 122 | ||
| 134 | 123 | - `/.well-known/gordon/`, `/.well-known/gordon/ca`, `/.well-known/gordon/ca.crt`, `/.well-known/gordon/ca.mobileconfig` — serve the onboarding page and CA certificate downloads | |
| 135 | 124 | - `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found` | |
| @@ -141,7 +130,7 @@ On HTTPS, onboarding paths are served only on `server.gordon_domain`; app hosts | |||
| 141 | 130 | ||
| 142 | 131 | #### HTTP to HTTPS redirect | |
| 143 | 132 | ||
| 144 | - | When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect all HTTP traffic to the HTTPS port. Direct HTTP onboarding paths are still served even with this flag enabled. | |
| 133 | + | When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect cleartext HTTP traffic to HTTPS on the same smart TCP edge. | |
| 145 | 134 | ||
| 146 | 135 | ```toml | |
| 147 | 136 | [server] | |
| @@ -150,40 +139,22 @@ force_https_redirect = true | |||
| 150 | 139 | ||
| 151 | 140 | When `proxy_allowed_ips` is configured, non-proxy clients are redirected automatically even without this flag — trusted proxy IPs pass through to serve Cloudflare-proxied traffic. | |
| 152 | 141 | ||
| 153 | - | #### Disabling TLS | |
| 142 | + | #### Firewall and Container Port Mapping | |
| 154 | 143 | ||
| 155 | - | For setups where TLS is handled entirely by an external proxy (Cloudflare, nginx, etc.): | |
| 144 | + | Choose the bind address in `[entrypoints.edge]` and map external ports to it as needed. For example, a rootless service can bind a high port while the firewall exposes 443: | |
| 156 | 145 | ||
| 157 | 146 | ```toml | |
| 158 | - | [server] | |
| 159 | - | tls_port = 0 # No HTTPS listener, no internal CA generated | |
| 147 | + | [entrypoints.edge] | |
| 148 | + | address = ":9000" | |
| 149 | + | protocol = "smart_tcp" | |
| 160 | 150 | ``` | |
| 161 | 151 | ||
| 162 | - | #### Firewall port forwarding | |
| 163 | - | ||
| 164 | - | For rootless user services, forward 443 to the high port at the firewall: | |
| 165 | - | ||
| 166 | 152 | ```bash | |
| 167 | - | # Forward 443 -> 8443 | |
| 168 | - | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443 | |
| 153 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 | |
| 169 | 154 | sudo firewall-cmd --reload | |
| 170 | 155 | ``` | |
| 171 | 156 | ||
| 172 | - | With firewalld and Tailscale: | |
| 173 | - | ||
| 174 | - | ```bash | |
| 175 | - | # Verify tailscale0 is in trusted zone | |
| 176 | - | sudo firewall-cmd --get-zone-of-interface=tailscale0 | |
| 177 | - | ||
| 178 | - | # If needed, place tailscale0 in trusted | |
| 179 | - | sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0 | |
| 180 | - | sudo firewall-cmd --reload | |
| 181 | - | ||
| 182 | - | # Forward 443 -> 8443 permanently (IPv4 + IPv6) | |
| 183 | - | sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443' | |
| 184 | - | sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 forward-port port=443 protocol=tcp to-port=8443' | |
| 185 | - | sudo firewall-cmd --reload | |
| 186 | - | ``` | |
| 157 | + | `:443`, `:9000`, and container `-p 443:9000` mappings are deployment choices, not protocol modes. | |
| 187 | 158 | ||
| 188 | 159 | ### Registry Port | |
| 189 | 160 | ||
| @@ -223,11 +194,11 @@ For a staged rename, set `gordon_domain` to the new public host and add any old | |||
| 223 | 194 | ||
| 224 | 195 | Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange. | |
| 225 | 196 | ||
| 226 | - | When requests arrive on the proxy port with this domain as the Host header, Gordon routes them to the backend services (registry and admin API). | |
| 197 | + | When requests arrive on an HTTP-capable edge entrypoint with this domain as the Host header, Gordon routes them to the backend services (registry and admin API). | |
| 227 | 198 | ||
| 228 | 199 | Security note: | |
| 229 | 200 | - Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients. | |
| 230 | - | - Admin API traffic should go through the main proxy listener (`server.port`/`server.tls_port`). | |
| 201 | + | - Admin API traffic should go through an HTTP-capable edge entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`. | |
| 231 | 202 | ||
| 232 | 203 | > **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs. | |
| 233 | 204 | ||
| @@ -404,7 +375,7 @@ registry_listen_address = "127.0.0.1" # Loopback only | |||
| 404 | 375 | ||
| 405 | 376 | ## HSTS | |
| 406 | 377 | ||
| 407 | - | HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it. | |
| 378 | + | HSTS headers are sent automatically on TLS requests (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it. | |
| 408 | 379 | ||
| 409 | 380 | ## Examples | |
| 410 | 381 | ||
| @@ -412,35 +383,44 @@ HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`). | |||
| 412 | 383 | ||
| 413 | 384 | ```toml | |
| 414 | 385 | [server] | |
| 415 | - | port = 8088 | |
| 416 | 386 | registry_port = 5000 | |
| 417 | 387 | gordon_domain = "gordon.local" | |
| 418 | 388 | data_dir = "./dev-data" | |
| 389 | + | ||
| 390 | + | [entrypoints.edge] | |
| 391 | + | address = ":9000" | |
| 392 | + | protocol = "smart_tcp" | |
| 419 | 393 | ``` | |
| 420 | 394 | ||
| 421 | 395 | ### Production Configuration | |
| 422 | 396 | ||
| 423 | 397 | ```toml | |
| 424 | 398 | [server] | |
| 425 | - | port = 8088 | |
| 426 | 399 | registry_port = 5000 | |
| 427 | 400 | gordon_domain = "gordon.company.com" | |
| 428 | 401 | data_dir = "/var/lib/gordon" | |
| 402 | + | ||
| 403 | + | [entrypoints.edge] | |
| 404 | + | address = ":443" | |
| 405 | + | protocol = "smart_tcp" | |
| 429 | 406 | ``` | |
| 430 | 407 | ||
| 431 | 408 | ### Rootless Container Setup | |
| 432 | 409 | ||
| 433 | 410 | ```toml | |
| 434 | 411 | [server] | |
| 435 | - | port = 8088 # High port for rootless | |
| 436 | 412 | registry_port = 5000 | |
| 437 | 413 | gordon_domain = "gordon.mydomain.com" | |
| 414 | + | ||
| 415 | + | [entrypoints.edge] | |
| 416 | + | address = ":9000" # High port for rootless | |
| 417 | + | protocol = "smart_tcp" | |
| 438 | 418 | ``` | |
| 439 | 419 | ||
| 440 | 420 | With firewall port forwarding: | |
| 441 | 421 | ||
| 442 | 422 | ```bash | |
| 443 | - | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088 | |
| 423 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 | |
| 444 | 424 | sudo firewall-cmd --reload | |
| 445 | 425 | ``` | |
| 446 | 426 | ||
| @@ -6,29 +6,29 @@ Core server settings for Gordon. | |||||
| 6 | 6 | ||||
| 7 | ```toml | 7 | ```toml | ||
| 8 | [server] | 8 | [server] | ||
| 9 | - | port = 8088 # HTTP proxy port | |||
| 10 | registry_port = 5000 # Container registry port | 9 | registry_port = 5000 # Container registry port | ||
| 11 | - | tls_port = 8443 # HTTPS listener port (0 = disabled) | 10 | + | gordon_domain = "gordon.mydomain.com" # Gordon domain (required) |
| 12 | # tls_cert_file = "" # Optional: PEM certificate for static TLS | 11 | # tls_cert_file = "" # Optional: PEM certificate for static TLS | ||
| 13 | # tls_key_file = "" # Optional: PEM key for static TLS | 12 | # tls_key_file = "" # Optional: PEM key for static TLS | ||
| 14 | - | # force_https_redirect = false # Redirect all HTTP traffic to HTTPS | 13 | + | # force_https_redirect = false # Redirect cleartext HTTP requests to HTTPS |
| 15 | - | gordon_domain = "gordon.mydomain.com" # Gordon domain (required) | |||
| 16 | # legacy_registry_domains = ["registry.example.com:5000"] | 14 | # legacy_registry_domains = ["registry.example.com:5000"] | ||
| 17 | # data_dir = "~/.gordon" # Data storage directory (default) | 15 | # data_dir = "~/.gordon" # Data storage directory (default) | ||
| 18 | max_blob_chunk_size = "95MB" # Max registry blob upload chunk | 16 | max_blob_chunk_size = "95MB" # Max registry blob upload chunk | ||
| 19 | max_blob_size = "1GB" # Max cumulative registry blob/layer upload | 17 | max_blob_size = "1GB" # Max cumulative registry blob/layer upload | ||
| 18 | + | ||||
| 19 | + | [entrypoints.edge] | |||
| 20 | + | address = ":443" # Deployment-selected public TCP socket | |||
| 21 | + | protocol = "smart_tcp" | |||
| 20 | ``` | 22 | ``` | ||
| 21 | 23 | ||||
| 22 | ## Options | 24 | ## Options | ||
| 23 | 25 | ||||
| 24 | | Option | Type | Default | Description | | 26 | | Option | Type | Default | Description | | ||
| 25 | |--------|------|---------|-------------| | 27 | |--------|------|---------|-------------| | ||
| 26 | - | | `port` | int | `8088` | HTTP proxy port for routing traffic to containers | | |||
| 27 | | `registry_port` | int | `5000` | Docker registry port for image push/pull | | 28 | | `registry_port` | int | `5000` | Docker registry port for image push/pull | | ||
| 28 | - | | `tls_port` | int | `8443` | HTTPS listener port with internal CA. Set to `0` to disable TLS entirely | | 29 | + | | `tls_cert_file` | string | `""` | Path to PEM certificate file. When set with `tls_key_file`, matching SNI domains use this static cert before ACME or internal CA certs | |
| 29 | - | | `tls_cert_file` | string | `""` | Path to PEM certificate file. When set (with `tls_key_file`), this cert is served for matching SNI domains; unmatched domains fall through to the internal CA | | |||
| 30 | | `tls_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` | | 30 | | `tls_key_file` | string | `""` | Path to PEM private key file. Must be set together with `tls_cert_file` | | ||
| 31 | - | | `force_https_redirect` | bool | `false` | Redirect all HTTP requests to the HTTPS port. For direct-access setups without a TLS-terminating proxy | | 31 | + | | `force_https_redirect` | bool | `false` | Redirect cleartext HTTP requests to HTTPS on HTTP-capable edge entrypoints | |
| 32 | | `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) | | 32 | | `gordon_domain` | string | **required** | Domain for Gordon (registry + admin API) | | ||
| 33 | | `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. | | 33 | | `registry_domain` | string | - | Deprecated migration key. Set `gordon_domain` instead. | | ||
| 34 | | `legacy_registry_domains` | []string | `[]` | Additional Gordon registry hosts treated as aliases during staged migration. See [Upgrading: Staged Registry Host Rename](../upgrading.md#staged-registry-host-rename). | | 34 | | `legacy_registry_domains` | []string | `[]` | Additional Gordon registry hosts treated as aliases during staged migration. See [Upgrading: Staged Registry Host Rename](../upgrading.md#staged-registry-host-rename). | | ||
| @@ -37,46 +37,33 @@ max_blob_size = "1GB" # Max cumulative registry blob/layer up | |||||
| 37 | | `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk | | 37 | | `max_blob_chunk_size` | string | `"95MB"` | Maximum request body size for a single registry blob upload chunk | | ||
| 38 | | `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload | | 38 | | `max_blob_size` | string | `"1GB"` | Maximum cumulative size for one registry blob/layer upload | | ||
| 39 | | `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) | | 39 | | `registry_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to access the registry (empty = allow all) | | ||
| 40 | - | | `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach the proxy (empty = allow all) | | 40 | + | | `proxy_allowed_ips` | []string | `[]` | IPs or CIDR ranges allowed to reach HTTP proxy paths (empty = allow all) | |
| 41 | | `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) | | 41 | | `registry_listen_address` | string | `""` | Bind address for registry (empty = all interfaces) | | ||
| 42 | 42 | ||||
| 43 | - | ## Port Configuration | 43 | + | Public application traffic is configured under `[entrypoints]`. The standard edge listener is a `smart_tcp` socket: |
| 44 | - | ||||
| 45 | - | ### Proxy Port | |||
| 46 | - | ||||
| 47 | - | The `port` setting controls where Gordon listens for HTTP traffic: | |||
| 48 | 44 | ||||
| 49 | ```toml | 45 | ```toml | ||
| 50 | - | [server] | 46 | + | [entrypoints.edge] |
| 51 | - | port = 8088 # Default high port for rootless or forwarded setups | 47 | + | address = ":443" |
| 52 | - | ``` | 48 | + | protocol = "smart_tcp" |
| 53 | - | ||||
| 54 | - | For rootless containers, you'll typically use a high port and configure firewall port forwarding: | |||
| 55 | - | ||||
| 56 | - | ```bash | |||
| 57 | - | # Forward port 80 to 8088 | |||
| 58 | - | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088 | |||
| 59 | ``` | 49 | ``` | ||
| 60 | 50 | ||||
| 61 | - | ### Internal CA and TLS | 51 | + | `entrypoints.edge.address` is not an HTTP or HTTPS port. It is the TCP address Gordon sniffs for cleartext HTTP, h2c, TLS passthrough, normal HTTPS fallback, or explicit raw TCP fallback. Gordon does not provide a built-in default public port; choose `:443`, a high port such as `:9000`, or container/firewall mappings to match your deployment. |
| 62 | 52 | ||||
| 63 | - | Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains. The HTTPS listener is enabled by default on `tls_port` (8443). | 53 | + | ## Internal CA and TLS |
| 64 | 54 | ||||
| 65 | - | ```toml | 55 | + | Gordon includes an internal certificate authority that issues on-demand TLS certificates for proxied domains served through HTTPS fallback on a TLS-capable entrypoint such as `smart_tcp`. |
| 66 | - | [server] | |||
| 67 | - | tls_port = 8443 # Default. Set to 0 to disable TLS entirely. | |||
| 68 | - | ``` | |||
| 69 | 56 | ||||
| 70 | - | With TLS enabled: | 57 | + | With TLS-capable edge traffic: |
| 71 | - Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/` | 58 | - Gordon runs an internal CA (root + intermediate) stored in `{data_dir}/pki/` | ||
| 72 | - Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory | 59 | - Leaf certificates are issued on-demand per domain (SNI-based) and cached in memory | ||
| 73 | - The intermediate CA auto-renews before expiry | 60 | - The intermediate CA auto-renews before expiry | ||
| 74 | - | - An onboarding page at `https://<gordon-host>:<tls_port>/.well-known/gordon/` lets clients download the root CA certificate | 61 | + | - An onboarding page at `https://<gordon-host>/.well-known/gordon/` lets clients download the root CA certificate |
| 75 | - The root CA is stable across restarts (generated once, persisted to disk) | 62 | - The root CA is stable across restarts (generated once, persisted to disk) | ||
| 76 | 63 | ||||
| 77 | #### Custom certificates | 64 | #### Custom certificates | ||
| 78 | 65 | ||||
| 79 | - | To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside the internal CA: | 66 | + | To use your own certificate (e.g. from Tailscale, Let's Encrypt, or a corporate CA) alongside ACME/internal CA fallback: |
| 80 | 67 | ||||
| 81 | ```toml | 68 | ```toml | ||
| 82 | [server] | 69 | [server] | ||
| @@ -84,15 +71,16 @@ tls_cert_file = "/etc/gordon/tls/cert.pem" | |||||
| 84 | tls_key_file = "/etc/gordon/tls/key.pem" | 71 | tls_key_file = "/etc/gordon/tls/key.pem" | ||
| 85 | ``` | 72 | ``` | ||
| 86 | 73 | ||||
| 87 | - | The static certificate is served for SNI-matching domains (including wildcards). All other domains get on-demand certs from the internal CA. | 74 | + | Certificate priority for normal HTTPS fallback is: static certificates first, then public ACME certificates, then Gordon's internal CA. |
| 88 | 75 | ||||
| 89 | #### Public ACME certificates | 76 | #### Public ACME certificates | ||
| 90 | 77 | ||||
| 91 | - | Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. The HTTPS listener must also be enabled (`server.tls_port > 0`). | 78 | + | Gordon can obtain public certificates through Let's Encrypt-compatible ACME when `[tls.acme]` is enabled. Normal HTTPS fallback must be available on a TLS-capable entrypoint. |
| 92 | 79 | ||||
| 93 | ```toml | 80 | ```toml | ||
| 94 | - | [server] | 81 | + | [entrypoints.edge] |
| 95 | - | tls_port = 8443 | 82 | + | address = ":443" |
| 83 | + | protocol = "smart_tcp" | |||
| 96 | 84 | ||||
| 97 | [dns] | 85 | [dns] | ||
| 98 | resolvers = ["1.1.1.1:53", "8.8.8.8:53"] | 86 | resolvers = ["1.1.1.1:53", "8.8.8.8:53"] | ||
| @@ -107,11 +95,12 @@ obtain_batch_size = 1 # maximum new certificate orders per reconcile run | |||||
| 107 | ``` | 95 | ``` | ||
| 108 | 96 | ||||
| 109 | Challenge behavior: | 97 | Challenge behavior: | ||
| 110 | - | - `http-01` serves `/.well-known/acme-challenge/<token>` on the HTTP proxy port. | 98 | + | - `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`; it does not need a special public port 80 edge. |
| 111 | - | - `cloudflare-dns-01` uses a Cloudflare API token from `pass`, `GORDON_CLOUDFLARE_API_TOKEN_FILE`, or `GORDON_CLOUDFLARE_API_TOKEN`. | 99 | + | - `http-01` serves `/.well-known/acme-challenge/<token>` through Gordon's HTTP handler and requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated. |
| 112 | - `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01. | 100 | - `auto` selects Cloudflare DNS-01 when a token is available, otherwise falls back to HTTP-01. | ||
| 101 | + | - `tls-alpn-01` is not supported. | |||
| 113 | 102 | ||||
| 114 | - | HTTP-01 requires public access to port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to Gordon's HTTP proxy port. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation. | 103 | + | HTTP-01 requires public access to external port 80 for each hostname being validated. If you use Cloudflare in DNS-only/gray-cloud mode, your firewall/NAT must allow direct public traffic to the HTTP-capable smart TCP entrypoint. A firewall rule that only allows Cloudflare source IPs on port 80 is compatible with orange-cloud proxying, but it blocks gray-cloud HTTP-01 validation; use DNS-01 or temporarily open port 80 for direct validation. |
| 115 | 104 | ||||
| 116 | Gordon limits new ACME certificate orders to `obtain_batch_size` per reconcile run (default `1`) so enabling ACME on an existing multi-route server does not burst through every route and hit Let's Encrypt rate limits. Later reloads, restarts, or other explicit reconcile runs continue issuing remaining certificates. | 105 | Gordon limits new ACME certificate orders to `obtain_batch_size` per reconcile run (default `1`) so enabling ACME on an existing multi-route server does not burst through every route and hit Let's Encrypt rate limits. Later reloads, restarts, or other explicit reconcile runs continue issuing remaining certificates. | ||
| 117 | 106 | ||||
| @@ -129,7 +118,7 @@ Because Gordon's ACME challenge mode is global, `cloudflare-dns-01` requires a C | |||||
| 129 | 118 | ||||
| 130 | #### Direct HTTP Onboarding | 119 | #### Direct HTTP Onboarding | ||
| 131 | 120 | ||||
| 132 | - | When `tls_port` is non-zero, direct HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only: | 121 | + | When Gordon is serving TLS-capable edge traffic, direct cleartext HTTP clients (those not arriving through a trusted proxy) are restricted to CA onboarding paths only: |
| 133 | 122 | ||||
| 134 | - `/.well-known/gordon/`, `/.well-known/gordon/ca`, `/.well-known/gordon/ca.crt`, `/.well-known/gordon/ca.mobileconfig` — serve the onboarding page and CA certificate downloads | 123 | - `/.well-known/gordon/`, `/.well-known/gordon/ca`, `/.well-known/gordon/ca.crt`, `/.well-known/gordon/ca.mobileconfig` — serve the onboarding page and CA certificate downloads | ||
| 135 | - `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found` | 124 | - `/.well-known/acme-challenge/*` — serves ACME HTTP-01 challenges when public ACME HTTP-01 is enabled, otherwise returns `404 Not Found` | ||
| @@ -141,7 +130,7 @@ On HTTPS, onboarding paths are served only on `server.gordon_domain`; app hosts | |||||
| 141 | 130 | ||||
| 142 | #### HTTP to HTTPS redirect | 131 | #### HTTP to HTTPS redirect | ||
| 143 | 132 | ||||
| 144 | - | When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect all HTTP traffic to the HTTPS port. Direct HTTP onboarding paths are still served even with this flag enabled. | 133 | + | When clients connect directly (no Cloudflare or reverse proxy in front), enable `force_https_redirect` to redirect cleartext HTTP traffic to HTTPS on the same smart TCP edge. |
| 145 | 134 | ||||
| 146 | ```toml | 135 | ```toml | ||
| 147 | [server] | 136 | [server] | ||
| @@ -150,40 +139,22 @@ force_https_redirect = true | |||||
| 150 | 139 | ||||
| 151 | When `proxy_allowed_ips` is configured, non-proxy clients are redirected automatically even without this flag — trusted proxy IPs pass through to serve Cloudflare-proxied traffic. | 140 | When `proxy_allowed_ips` is configured, non-proxy clients are redirected automatically even without this flag — trusted proxy IPs pass through to serve Cloudflare-proxied traffic. | ||
| 152 | 141 | ||||
| 153 | - | #### Disabling TLS | 142 | + | #### Firewall and Container Port Mapping |
| 154 | 143 | ||||
| 155 | - | For setups where TLS is handled entirely by an external proxy (Cloudflare, nginx, etc.): | 144 | + | Choose the bind address in `[entrypoints.edge]` and map external ports to it as needed. For example, a rootless service can bind a high port while the firewall exposes 443: |
| 156 | 145 | ||||
| 157 | ```toml | 146 | ```toml | ||
| 158 | - | [server] | 147 | + | [entrypoints.edge] |
| 159 | - | tls_port = 0 # No HTTPS listener, no internal CA generated | 148 | + | address = ":9000" |
| 149 | + | protocol = "smart_tcp" | |||
| 160 | ``` | 150 | ``` | ||
| 161 | 151 | ||||
| 162 | - | #### Firewall port forwarding | |||
| 163 | - | ||||
| 164 | - | For rootless user services, forward 443 to the high port at the firewall: | |||
| 165 | - | ||||
| 166 | ```bash | 152 | ```bash | ||
| 167 | - | # Forward 443 -> 8443 | 153 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 |
| 168 | - | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443 | |||
| 169 | sudo firewall-cmd --reload | 154 | sudo firewall-cmd --reload | ||
| 170 | ``` | 155 | ``` | ||
| 171 | 156 | ||||
| 172 | - | With firewalld and Tailscale: | 157 | + | `:443`, `:9000`, and container `-p 443:9000` mappings are deployment choices, not protocol modes. |
| 173 | - | ||||
| 174 | - | ```bash | |||
| 175 | - | # Verify tailscale0 is in trusted zone | |||
| 176 | - | sudo firewall-cmd --get-zone-of-interface=tailscale0 | |||
| 177 | - | ||||
| 178 | - | # If needed, place tailscale0 in trusted | |||
| 179 | - | sudo firewall-cmd --permanent --zone=trusted --add-interface=tailscale0 | |||
| 180 | - | sudo firewall-cmd --reload | |||
| 181 | - | ||||
| 182 | - | # Forward 443 -> 8443 permanently (IPv4 + IPv6) | |||
| 183 | - | sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443' | |||
| 184 | - | sudo firewall-cmd --permanent --zone=trusted --add-rich-rule='rule family=ipv6 forward-port port=443 protocol=tcp to-port=8443' | |||
| 185 | - | sudo firewall-cmd --reload | |||
| 186 | - | ``` | |||
| 187 | 158 | ||||
| 188 | ### Registry Port | 159 | ### Registry Port | ||
| 189 | 160 | ||||
| @@ -223,11 +194,11 @@ For a staged rename, set `gordon_domain` to the new public host and add any old | |||||
| 223 | 194 | ||||
| 224 | Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange. | 195 | Without this migration, a Host/remote-target mismatch can break routing or remote CLI token exchange. | ||
| 225 | 196 | ||||
| 226 | - | When requests arrive on the proxy port with this domain as the Host header, Gordon routes them to the backend services (registry and admin API). | 197 | + | When requests arrive on an HTTP-capable edge entrypoint with this domain as the Host header, Gordon routes them to the backend services (registry and admin API). |
| 227 | 198 | ||||
| 228 | Security note: | 199 | Security note: | ||
| 229 | - Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients. | 200 | - Direct access to `/admin/*` on `registry_port` is blocked for non-loopback clients. | ||
| 230 | - | - Admin API traffic should go through the main proxy listener (`server.port`/`server.tls_port`). | 201 | + | - Admin API traffic should go through an HTTP-capable edge entrypoint such as `entrypoints.edge` with `protocol = "smart_tcp"`. |
| 231 | 202 | ||||
| 232 | > **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs. | 203 | > **Note:** `registry_domain` is a deprecated migration key; use `gordon_domain` for new and upgraded configs. | ||
| 233 | 204 | ||||
| @@ -404,7 +375,7 @@ registry_listen_address = "127.0.0.1" # Loopback only | |||||
| 404 | 375 | ||||
| 405 | ## HSTS | 376 | ## HSTS | ||
| 406 | 377 | ||||
| 407 | - | HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it. | 378 | + | HSTS headers are sent automatically on TLS requests (when `r.TLS != nil`). When Gordon runs behind a TLS-terminating proxy (Cloudflare, Tailscale) and only receives plain HTTP, HSTS is not sent — the edge proxy is expected to handle it. |
| 408 | 379 | ||||
| 409 | ## Examples | 380 | ## Examples | ||
| 410 | 381 | ||||
| @@ -412,35 +383,44 @@ HSTS headers are sent automatically on the HTTPS listener (when `r.TLS != nil`). | |||||
| 412 | 383 | ||||
| 413 | ```toml | 384 | ```toml | ||
| 414 | [server] | 385 | [server] | ||
| 415 | - | port = 8088 | |||
| 416 | registry_port = 5000 | 386 | registry_port = 5000 | ||
| 417 | gordon_domain = "gordon.local" | 387 | gordon_domain = "gordon.local" | ||
| 418 | data_dir = "./dev-data" | 388 | data_dir = "./dev-data" | ||
| 389 | + | ||||
| 390 | + | [entrypoints.edge] | |||
| 391 | + | address = ":9000" | |||
| 392 | + | protocol = "smart_tcp" | |||
| 419 | ``` | 393 | ``` | ||
| 420 | 394 | ||||
| 421 | ### Production Configuration | 395 | ### Production Configuration | ||
| 422 | 396 | ||||
| 423 | ```toml | 397 | ```toml | ||
| 424 | [server] | 398 | [server] | ||
| 425 | - | port = 8088 | |||
| 426 | registry_port = 5000 | 399 | registry_port = 5000 | ||
| 427 | gordon_domain = "gordon.company.com" | 400 | gordon_domain = "gordon.company.com" | ||
| 428 | data_dir = "/var/lib/gordon" | 401 | data_dir = "/var/lib/gordon" | ||
| 402 | + | ||||
| 403 | + | [entrypoints.edge] | |||
| 404 | + | address = ":443" | |||
| 405 | + | protocol = "smart_tcp" | |||
| 429 | ``` | 406 | ``` | ||
| 430 | 407 | ||||
| 431 | ### Rootless Container Setup | 408 | ### Rootless Container Setup | ||
| 432 | 409 | ||||
| 433 | ```toml | 410 | ```toml | ||
| 434 | [server] | 411 | [server] | ||
| 435 | - | port = 8088 # High port for rootless | |||
| 436 | registry_port = 5000 | 412 | registry_port = 5000 | ||
| 437 | gordon_domain = "gordon.mydomain.com" | 413 | gordon_domain = "gordon.mydomain.com" | ||
| 414 | + | ||||
| 415 | + | [entrypoints.edge] | |||
| 416 | + | address = ":9000" # High port for rootless | |||
| 417 | + | protocol = "smart_tcp" | |||
| 438 | ``` | 418 | ``` | ||
| 439 | 419 | ||||
| 440 | With firewall port forwarding: | 420 | With firewall port forwarding: | ||
| 441 | 421 | ||||
| 442 | ```bash | 422 | ```bash | ||
| 443 | - | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088 | 423 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 |
| 444 | sudo firewall-cmd --reload | 424 | sudo firewall-cmd --reload | ||
| 445 | ``` | 425 | ``` | ||
| 446 | 426 | ||||
M docs/config/traffic.md +87 -43
| @@ -1,25 +1,37 @@ | |||
| 1 | 1 | # Traffic Plane Configuration | |
| 2 | 2 | ||
| 3 | - | Gordon models network listeners as a traffic graph: entrypoints receive connections, routers select a service, and services resolve to backends. | |
| 3 | + | Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends. | |
| 4 | 4 | ||
| 5 | - | ## Entrypoints | |
| 5 | + | ## Smart TCP Edge Entrypoint | |
| 6 | 6 | ||
| 7 | - | `server.port` creates the default HTTP entrypoint `web`. `server.tls_port` creates `websecure`, Gordon's default TLS mux entrypoint. `websecure` serves normal HTTPS routes and can also route TLS passthrough by ClientHello SNI. Use a custom `tls_mux` entrypoint when you want an additional port or isolation from the default HTTPS listener. | |
| 8 | - | ||
| 9 | - | Custom entrypoints use the top-level `entrypoints` table: | |
| 7 | + | Public application traffic is normally exposed with a `smart_tcp` entrypoint. The conventional name is `edge`, but Gordon does not assign a built-in public port. Choose the address that matches your deployment, firewall, and container mapping: | |
| 10 | 8 | ||
| 11 | 9 | ```toml | |
| 12 | - | [entrypoints.postgres] | |
| 13 | - | address = "0.0.0.0:5432" | |
| 14 | - | protocol = "tcp" | |
| 15 | - | trusted_cidrs = ["100.64.0.0/10"] | |
| 16 | - | ||
| 17 | - | [entrypoints.game] | |
| 18 | - | address = "0.0.0.0:7777" | |
| 19 | - | protocol = "udp" | |
| 10 | + | [entrypoints.edge] | |
| 11 | + | address = ":443" | |
| 12 | + | protocol = "smart_tcp" | |
| 13 | + | trusted_cidrs = [] | |
| 20 | 14 | ``` | |
| 21 | 15 | ||
| 22 | - | Protocols are `http`, `tls_mux`, `tcp`, and `udp`. | |
| 16 | + | Do not treat `entrypoints.edge.address` as an HTTP port or an HTTPS port. It is one TCP socket that sniffs each new connection and dispatches the original byte stream. | |
| 17 | + | ||
| 18 | + | Supported entrypoint protocols are `smart_tcp`, `http`, `tls_mux`, `tcp`, and `udp`. `smart_tcp` is the primary public edge model; `tcp` and `udp` are for explicit L4 services, and UDP remains separate from the TCP entrypoint. | |
| 19 | + | ||
| 20 | + | ## Smart TCP Dispatch Order | |
| 21 | + | ||
| 22 | + | For each accepted connection on a `smart_tcp` entrypoint Gordon: | |
| 23 | + | ||
| 24 | + | 1. Applies entrypoint-wide `trusted_cidrs` to the peer socket IP. | |
| 25 | + | 2. Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed. | |
| 26 | + | 3. Peeks a minimal prefix and replays the bytes to the selected handler/backend. | |
| 27 | + | 4. Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler. | |
| 28 | + | 5. Dispatches TLS ClientHello traffic by SNI: | |
| 29 | + | - matching TLS passthrough router -> raw TCP backend without TLS termination; | |
| 30 | + | - no passthrough match -> normal Gordon HTTPS fallback. | |
| 31 | + | 6. Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it. | |
| 32 | + | 7. Refuses the connection when no deterministic handler or allowed fallback exists. | |
| 33 | + | ||
| 34 | + | Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback. | |
| 23 | 35 | ||
| 24 | 36 | ## Network Services | |
| 25 | 37 | ||
| @@ -43,9 +55,63 @@ Service references use: | |||
| 43 | 55 | ||
| 44 | 56 | `static:<name>` is reserved and currently unsupported. | |
| 45 | 57 | ||
| 58 | + | ## TLS Passthrough on Smart TCP | |
| 59 | + | ||
| 60 | + | TLS passthrough routers run on `smart_tcp` or `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. | |
| 61 | + | ||
| 62 | + | ```toml | |
| 63 | + | [entrypoints.edge] | |
| 64 | + | address = ":443" | |
| 65 | + | protocol = "smart_tcp" | |
| 66 | + | ||
| 67 | + | [[traffic.tls.routers]] | |
| 68 | + | name = "raw-tls" | |
| 69 | + | entrypoint = "edge" | |
| 70 | + | sni = "raw.example.com" | |
| 71 | + | service = "network_service:raw:tls" | |
| 72 | + | ``` | |
| 73 | + | ||
| 74 | + | Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts on the same smart TCP entrypoint are rejected at validation time. HTTPS application routes that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection. | |
| 75 | + | ||
| 76 | + | ## Raw TCP Fallback | |
| 77 | + | ||
| 78 | + | Raw fallback is disabled by default. Unknown non-HTTP/non-TLS bytes reach a TCP backend only when the smart TCP entrypoint explicitly names one TCP router as `raw_fallback` and the source policy allows the peer socket IP. | |
| 79 | + | ||
| 80 | + | ```toml | |
| 81 | + | [entrypoints.edge] | |
| 82 | + | address = ":443" | |
| 83 | + | protocol = "smart_tcp" | |
| 84 | + | raw_fallback = "ssh-fallback" | |
| 85 | + | raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |
| 86 | + | ||
| 87 | + | [[traffic.tcp.routers]] | |
| 88 | + | name = "ssh-fallback" | |
| 89 | + | entrypoint = "edge" | |
| 90 | + | service = "network_service:ssh:ssh" | |
| 91 | + | ``` | |
| 92 | + | ||
| 93 | + | Use `raw_fallback_trusted_cidrs` for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly: | |
| 94 | + | ||
| 95 | + | ```toml | |
| 96 | + | [entrypoints.edge] | |
| 97 | + | address = ":443" | |
| 98 | + | protocol = "smart_tcp" | |
| 99 | + | raw_fallback = "public-raw" | |
| 100 | + | allow_public_raw_fallback = true | |
| 101 | + | ``` | |
| 102 | + | ||
| 103 | + | `trusted_cidrs` and `raw_fallback_trusted_cidrs` both use the peer socket IP. They do not use `X-Forwarded-For`. | |
| 104 | + | ||
| 46 | 105 | ## TCP Routers | |
| 47 | 106 | ||
| 107 | + | Plain TCP entrypoints support one TCP router per entrypoint. TCP routers on a `smart_tcp` entrypoint are valid only when referenced by that entrypoint's `raw_fallback`. | |
| 108 | + | ||
| 48 | 109 | ```toml | |
| 110 | + | [entrypoints.postgres] | |
| 111 | + | address = "0.0.0.0:5432" | |
| 112 | + | protocol = "tcp" | |
| 113 | + | trusted_cidrs = ["100.64.0.0/10"] | |
| 114 | + | ||
| 49 | 115 | [traffic.tcp] | |
| 50 | 116 | dial_timeout = "10s" | |
| 51 | 117 | idle_timeout = "5m" | |
| @@ -58,11 +124,17 @@ entrypoint = "postgres" | |||
| 58 | 124 | service = "network_service:postgres:db" | |
| 59 | 125 | ``` | |
| 60 | 126 | ||
| 61 | - | A plain TCP entrypoint supports one TCP router. If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint. | |
| 127 | + | If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint. | |
| 62 | 128 | ||
| 63 | 129 | ## UDP Routers | |
| 64 | 130 | ||
| 131 | + | UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint: | |
| 132 | + | ||
| 65 | 133 | ```toml | |
| 134 | + | [entrypoints.game] | |
| 135 | + | address = "0.0.0.0:7777" | |
| 136 | + | protocol = "udp" | |
| 137 | + | ||
| 66 | 138 | [traffic.udp] | |
| 67 | 139 | idle_timeout = "30s" | |
| 68 | 140 | drain_timeout = "30s" | |
| @@ -76,34 +148,6 @@ service = "network_service:game:game" | |||
| 76 | 148 | ||
| 77 | 149 | UDP sessions are keyed by client address and expire after `idle_timeout`. If `max_sessions` is omitted or set to `0`, Gordon applies the safe runtime default of `4096` active sessions per entrypoint. | |
| 78 | 150 | ||
| 79 | - | ## TLS Passthrough | |
| 80 | - | ||
| 81 | - | TLS passthrough routers run on `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. The default `websecure` entrypoint can host normal HTTPS routes and TLS passthrough routes on the same port as long as their hosts do not conflict. | |
| 82 | - | ||
| 83 | - | ```toml | |
| 84 | - | [[traffic.tls.routers]] | |
| 85 | - | name = "raw-tls" | |
| 86 | - | entrypoint = "websecure" | |
| 87 | - | sni = "raw.example.com" | |
| 88 | - | service = "network_service:raw:tls" | |
| 89 | - | ``` | |
| 90 | - | ||
| 91 | - | Custom `tls_mux` entrypoints are useful for additional ports: | |
| 92 | - | ||
| 93 | - | ```toml | |
| 94 | - | [entrypoints.rawtls] | |
| 95 | - | address = "0.0.0.0:9443" | |
| 96 | - | protocol = "tls_mux" | |
| 97 | - | ||
| 98 | - | [[traffic.tls.routers]] | |
| 99 | - | name = "raw-tls-alt" | |
| 100 | - | entrypoint = "rawtls" | |
| 101 | - | sni = "raw-alt.example.com" | |
| 102 | - | service = "network_service:raw:tls" | |
| 103 | - | ``` | |
| 104 | - | ||
| 105 | - | Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts are rejected at validation time. | |
| 106 | - | ||
| 107 | 151 | ## Related | |
| 108 | 152 | ||
| 109 | 153 | - [Server Settings](./server.md) | |
| @@ -1,25 +1,37 @@ | |||||
| 1 | # Traffic Plane Configuration | 1 | # Traffic Plane Configuration | ||
| 2 | 2 | ||||
| 3 | - | Gordon models network listeners as a traffic graph: entrypoints receive connections, routers select a service, and services resolve to backends. | 3 | + | Gordon models network listeners as a traffic graph: entrypoints receive packets or connections, routers select a service, and services resolve to backends. |
| 4 | 4 | ||||
| 5 | - | ## Entrypoints | 5 | + | ## Smart TCP Edge Entrypoint |
| 6 | 6 | ||||
| 7 | - | `server.port` creates the default HTTP entrypoint `web`. `server.tls_port` creates `websecure`, Gordon's default TLS mux entrypoint. `websecure` serves normal HTTPS routes and can also route TLS passthrough by ClientHello SNI. Use a custom `tls_mux` entrypoint when you want an additional port or isolation from the default HTTPS listener. | 7 | + | Public application traffic is normally exposed with a `smart_tcp` entrypoint. The conventional name is `edge`, but Gordon does not assign a built-in public port. Choose the address that matches your deployment, firewall, and container mapping: |
| 8 | - | ||||
| 9 | - | Custom entrypoints use the top-level `entrypoints` table: | |||
| 10 | 8 | ||||
| 11 | ```toml | 9 | ```toml | ||
| 12 | - | [entrypoints.postgres] | 10 | + | [entrypoints.edge] |
| 13 | - | address = "0.0.0.0:5432" | 11 | + | address = ":443" |
| 14 | - | protocol = "tcp" | 12 | + | protocol = "smart_tcp" |
| 15 | - | trusted_cidrs = ["100.64.0.0/10"] | 13 | + | trusted_cidrs = [] |
| 16 | - | ||||
| 17 | - | [entrypoints.game] | |||
| 18 | - | address = "0.0.0.0:7777" | |||
| 19 | - | protocol = "udp" | |||
| 20 | ``` | 14 | ``` | ||
| 21 | 15 | ||||
| 22 | - | Protocols are `http`, `tls_mux`, `tcp`, and `udp`. | 16 | + | Do not treat `entrypoints.edge.address` as an HTTP port or an HTTPS port. It is one TCP socket that sniffs each new connection and dispatches the original byte stream. |
| 17 | + | ||||
| 18 | + | Supported entrypoint protocols are `smart_tcp`, `http`, `tls_mux`, `tcp`, and `udp`. `smart_tcp` is the primary public edge model; `tcp` and `udp` are for explicit L4 services, and UDP remains separate from the TCP entrypoint. | |||
| 19 | + | ||||
| 20 | + | ## Smart TCP Dispatch Order | |||
| 21 | + | ||||
| 22 | + | For each accepted connection on a `smart_tcp` entrypoint Gordon: | |||
| 23 | + | ||||
| 24 | + | 1. Applies entrypoint-wide `trusted_cidrs` to the peer socket IP. | |||
| 25 | + | 2. Rejects PROXY protocol v1 and v2 prefixes. PROXY headers are not trusted or parsed. | |||
| 26 | + | 3. Peeks a minimal prefix and replays the bytes to the selected handler/backend. | |||
| 27 | + | 4. Dispatches cleartext HTTP/1.x and h2c prior-knowledge to Gordon's HTTP handler. | |||
| 28 | + | 5. Dispatches TLS ClientHello traffic by SNI: | |||
| 29 | + | - matching TLS passthrough router -> raw TCP backend without TLS termination; | |||
| 30 | + | - no passthrough match -> normal Gordon HTTPS fallback. | |||
| 31 | + | 6. Dispatches unknown non-HTTP/non-TLS bytes only to an explicit raw fallback router, after raw-fallback source policy allows it. | |||
| 32 | + | 7. Refuses the connection when no deterministic handler or allowed fallback exists. | |||
| 33 | + | ||||
| 34 | + | Malformed HTTP-looking or TLS-looking traffic is rejected instead of bypassing to raw fallback. | |||
| 23 | 35 | ||||
| 24 | ## Network Services | 36 | ## Network Services | ||
| 25 | 37 | ||||
| @@ -43,9 +55,63 @@ Service references use: | |||||
| 43 | 55 | ||||
| 44 | `static:<name>` is reserved and currently unsupported. | 56 | `static:<name>` is reserved and currently unsupported. | ||
| 45 | 57 | ||||
| 58 | + | ## TLS Passthrough on Smart TCP | |||
| 59 | + | ||||
| 60 | + | TLS passthrough routers run on `smart_tcp` or `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. | |||
| 61 | + | ||||
| 62 | + | ```toml | |||
| 63 | + | [entrypoints.edge] | |||
| 64 | + | address = ":443" | |||
| 65 | + | protocol = "smart_tcp" | |||
| 66 | + | ||||
| 67 | + | [[traffic.tls.routers]] | |||
| 68 | + | name = "raw-tls" | |||
| 69 | + | entrypoint = "edge" | |||
| 70 | + | sni = "raw.example.com" | |||
| 71 | + | service = "network_service:raw:tls" | |||
| 72 | + | ``` | |||
| 73 | + | ||||
| 74 | + | Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts on the same smart TCP entrypoint are rejected at validation time. HTTPS application routes that do not match a passthrough SNI use Gordon's normal HTTPS fallback and certificate selection. | |||
| 75 | + | ||||
| 76 | + | ## Raw TCP Fallback | |||
| 77 | + | ||||
| 78 | + | Raw fallback is disabled by default. Unknown non-HTTP/non-TLS bytes reach a TCP backend only when the smart TCP entrypoint explicitly names one TCP router as `raw_fallback` and the source policy allows the peer socket IP. | |||
| 79 | + | ||||
| 80 | + | ```toml | |||
| 81 | + | [entrypoints.edge] | |||
| 82 | + | address = ":443" | |||
| 83 | + | protocol = "smart_tcp" | |||
| 84 | + | raw_fallback = "ssh-fallback" | |||
| 85 | + | raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |||
| 86 | + | ||||
| 87 | + | [[traffic.tcp.routers]] | |||
| 88 | + | name = "ssh-fallback" | |||
| 89 | + | entrypoint = "edge" | |||
| 90 | + | service = "network_service:ssh:ssh" | |||
| 91 | + | ``` | |||
| 92 | + | ||||
| 93 | + | Use `raw_fallback_trusted_cidrs` for private raw fallback exposure. To expose raw fallback publicly, acknowledge it explicitly: | |||
| 94 | + | ||||
| 95 | + | ```toml | |||
| 96 | + | [entrypoints.edge] | |||
| 97 | + | address = ":443" | |||
| 98 | + | protocol = "smart_tcp" | |||
| 99 | + | raw_fallback = "public-raw" | |||
| 100 | + | allow_public_raw_fallback = true | |||
| 101 | + | ``` | |||
| 102 | + | ||||
| 103 | + | `trusted_cidrs` and `raw_fallback_trusted_cidrs` both use the peer socket IP. They do not use `X-Forwarded-For`. | |||
| 104 | + | ||||
| 46 | ## TCP Routers | 105 | ## TCP Routers | ||
| 47 | 106 | ||||
| 107 | + | Plain TCP entrypoints support one TCP router per entrypoint. TCP routers on a `smart_tcp` entrypoint are valid only when referenced by that entrypoint's `raw_fallback`. | |||
| 108 | + | ||||
| 48 | ```toml | 109 | ```toml | ||
| 110 | + | [entrypoints.postgres] | |||
| 111 | + | address = "0.0.0.0:5432" | |||
| 112 | + | protocol = "tcp" | |||
| 113 | + | trusted_cidrs = ["100.64.0.0/10"] | |||
| 114 | + | ||||
| 49 | [traffic.tcp] | 115 | [traffic.tcp] | ||
| 50 | dial_timeout = "10s" | 116 | dial_timeout = "10s" | ||
| 51 | idle_timeout = "5m" | 117 | idle_timeout = "5m" | ||
| @@ -58,11 +124,17 @@ entrypoint = "postgres" | |||||
| 58 | service = "network_service:postgres:db" | 124 | service = "network_service:postgres:db" | ||
| 59 | ``` | 125 | ``` | ||
| 60 | 126 | ||||
| 61 | - | A plain TCP entrypoint supports one TCP router. If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint. | 127 | + | If `max_connections` is omitted or set to `0`, Gordon applies the safe runtime default of `1024` active connections per entrypoint. |
| 62 | 128 | ||||
| 63 | ## UDP Routers | 129 | ## UDP Routers | ||
| 64 | 130 | ||||
| 131 | + | UDP is not sniffed or unified into smart TCP. Use a separate UDP entrypoint: | |||
| 132 | + | ||||
| 65 | ```toml | 133 | ```toml | ||
| 134 | + | [entrypoints.game] | |||
| 135 | + | address = "0.0.0.0:7777" | |||
| 136 | + | protocol = "udp" | |||
| 137 | + | ||||
| 66 | [traffic.udp] | 138 | [traffic.udp] | ||
| 67 | idle_timeout = "30s" | 139 | idle_timeout = "30s" | ||
| 68 | drain_timeout = "30s" | 140 | drain_timeout = "30s" | ||
| @@ -76,34 +148,6 @@ service = "network_service:game:game" | |||||
| 76 | 148 | ||||
| 77 | UDP sessions are keyed by client address and expire after `idle_timeout`. If `max_sessions` is omitted or set to `0`, Gordon applies the safe runtime default of `4096` active sessions per entrypoint. | 149 | UDP sessions are keyed by client address and expire after `idle_timeout`. If `max_sessions` is omitted or set to `0`, Gordon applies the safe runtime default of `4096` active sessions per entrypoint. | ||
| 78 | 150 | ||||
| 79 | - | ## TLS Passthrough | |||
| 80 | - | ||||
| 81 | - | TLS passthrough routers run on `tls_mux` entrypoints and route by ClientHello SNI without terminating TLS. The default `websecure` entrypoint can host normal HTTPS routes and TLS passthrough routes on the same port as long as their hosts do not conflict. | |||
| 82 | - | ||||
| 83 | - | ```toml | |||
| 84 | - | [[traffic.tls.routers]] | |||
| 85 | - | name = "raw-tls" | |||
| 86 | - | entrypoint = "websecure" | |||
| 87 | - | sni = "raw.example.com" | |||
| 88 | - | service = "network_service:raw:tls" | |||
| 89 | - | ``` | |||
| 90 | - | ||||
| 91 | - | Custom `tls_mux` entrypoints are useful for additional ports: | |||
| 92 | - | ||||
| 93 | - | ```toml | |||
| 94 | - | [entrypoints.rawtls] | |||
| 95 | - | address = "0.0.0.0:9443" | |||
| 96 | - | protocol = "tls_mux" | |||
| 97 | - | ||||
| 98 | - | [[traffic.tls.routers]] | |||
| 99 | - | name = "raw-tls-alt" | |||
| 100 | - | entrypoint = "rawtls" | |||
| 101 | - | sni = "raw-alt.example.com" | |||
| 102 | - | service = "network_service:raw:tls" | |||
| 103 | - | ``` | |||
| 104 | - | ||||
| 105 | - | Exact SNI matches win over wildcard matches. Ambiguous wildcard overlaps and HTTP-host/TLS-passthrough conflicts are rejected at validation time. | |||
| 106 | - | ||||
| 107 | ## Related | 151 | ## Related | ||
| 108 | 152 | ||||
| 109 | - [Server Settings](./server.md) | 153 | - [Server Settings](./server.md) | ||
M docs/getting-started.md +6 -4
| @@ -6,7 +6,7 @@ Deploy your first app with Gordon in under 5 minutes. | |||
| 6 | 6 | ||
| 7 | 7 | - A Linux VPS with Docker or Podman installed | |
| 8 | 8 | - A domain pointing to your VPS (DNS A record) | |
| 9 | - | - HTTPS setup: Cloudflare proxy, Tailscale, or Gordon's internal CA (`server.tls_port = 8443`) | |
| 9 | + | - A public TCP edge address for Gordon (for example external `:443`, mapped to your chosen `entrypoints.edge.address`) | |
| 10 | 10 | - [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized | |
| 11 | 11 | ||
| 12 | 12 | ## 1. Install Gordon | |
| @@ -73,11 +73,13 @@ Edit `~/.config/gordon/gordon.toml`: | |||
| 73 | 73 | ||
| 74 | 74 | ```toml | |
| 75 | 75 | [server] | |
| 76 | - | port = 8088 # HTTP proxy port (default) | |
| 77 | 76 | registry_port = 5000 # Registry port | |
| 78 | - | tls_port = 8443 # HTTPS listener (default) | |
| 79 | 77 | gordon_domain = "gordon.mydomain.com" # Registry + Admin API domain | |
| 80 | 78 | ||
| 79 | + | [entrypoints.edge] | |
| 80 | + | address = ":443" # Public smart TCP edge (choose your bind/mapping) | |
| 81 | + | protocol = "smart_tcp" | |
| 82 | + | ||
| 81 | 83 | [routes] | |
| 82 | 84 | "app.mydomain.com" = "myapp:latest" # Domain → Image mapping | |
| 83 | 85 | ``` | |
| @@ -99,7 +101,7 @@ Why wildcard (`*`)? | |||
| 99 | 101 | ||
| 100 | 102 | If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option. | |
| 101 | 103 | ||
| 102 | - | > **Important for Cloudflare or other proxied setups:** when Gordon's internal CA is enabled (default `tls_port = 8443`), add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic will get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist). If you do not want Gordon to manage TLS at all, set `tls_port = 0`. | |
| 104 | + | > **Important for Cloudflare or other proxied setups:** when Gordon serves HTTP paths behind Cloudflare or another proxy, add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic can get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist). | |
| 103 | 105 | ||
| 104 | 106 | ## 6. Start Gordon as a Service | |
| 105 | 107 | ||
| @@ -6,7 +6,7 @@ Deploy your first app with Gordon in under 5 minutes. | |||||
| 6 | 6 | ||||
| 7 | - A Linux VPS with Docker or Podman installed | 7 | - A Linux VPS with Docker or Podman installed | ||
| 8 | - A domain pointing to your VPS (DNS A record) | 8 | - A domain pointing to your VPS (DNS A record) | ||
| 9 | - | - HTTPS setup: Cloudflare proxy, Tailscale, or Gordon's internal CA (`server.tls_port = 8443`) | 9 | + | - A public TCP edge address for Gordon (for example external `:443`, mapped to your chosen `entrypoints.edge.address`) |
| 10 | - [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized | 10 | - [pass](https://www.passwordstore.org/) (password manager) with GPG key initialized | ||
| 11 | 11 | ||||
| 12 | ## 1. Install Gordon | 12 | ## 1. Install Gordon | ||
| @@ -73,11 +73,13 @@ Edit `~/.config/gordon/gordon.toml`: | |||||
| 73 | 73 | ||||
| 74 | ```toml | 74 | ```toml | ||
| 75 | [server] | 75 | [server] | ||
| 76 | - | port = 8088 # HTTP proxy port (default) | |||
| 77 | registry_port = 5000 # Registry port | 76 | registry_port = 5000 # Registry port | ||
| 78 | - | tls_port = 8443 # HTTPS listener (default) | |||
| 79 | gordon_domain = "gordon.mydomain.com" # Registry + Admin API domain | 77 | gordon_domain = "gordon.mydomain.com" # Registry + Admin API domain | ||
| 80 | 78 | ||||
| 79 | + | [entrypoints.edge] | |||
| 80 | + | address = ":443" # Public smart TCP edge (choose your bind/mapping) | |||
| 81 | + | protocol = "smart_tcp" | |||
| 82 | + | ||||
| 81 | [routes] | 83 | [routes] | ||
| 82 | "app.mydomain.com" = "myapp:latest" # Domain → Image mapping | 84 | "app.mydomain.com" = "myapp:latest" # Domain → Image mapping | ||
| 83 | ``` | 85 | ``` | ||
| @@ -99,7 +101,7 @@ Why wildcard (`*`)? | |||||
| 99 | 101 | ||||
| 100 | If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option. | 102 | If your DNS provider supports wildcard CNAME flattening (Cloudflare does), `* -> gordon.mydomain.com` is usually the cleanest option. | ||
| 101 | 103 | ||||
| 102 | - | > **Important for Cloudflare or other proxied setups:** when Gordon's internal CA is enabled (default `tls_port = 8443`), add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic will get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist). If you do not want Gordon to manage TLS at all, set `tls_port = 0`. | 104 | + | > **Important for Cloudflare or other proxied setups:** when Gordon serves HTTP paths behind Cloudflare or another proxy, add your proxy edge IPs to `server.proxy_allowed_ips` or proxied HTTP traffic can get `403 Forbidden`. See [Installation](./installation.md#proxy-origin-allowlist). |
| 103 | 105 | ||||
| 104 | ## 6. Start Gordon as a Service | 106 | ## 6. Start Gordon as a Service | ||
| 105 | 107 | ||||
M docs/index.md +1 -1
| @@ -81,7 +81,7 @@ Gordon runs on your VPS and provides: | |||
| 81 | 81 | - Domain pointing to your server | |
| 82 | 82 | - Optional Cloudflare account or other reverse proxy for edge TLS | |
| 83 | 83 | ||
| 84 | - | > **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly via its internal CA (enabled by default on `server.tls_port`). Set `tls_port = 0` to disable. | |
| 84 | + | > **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly on a smart TCP edge entrypoint via static certificates, public ACME certificates, or its internal CA. | |
| 85 | 85 | ||
| 86 | 86 | ## Related | |
| 87 | 87 | ||
| @@ -81,7 +81,7 @@ Gordon runs on your VPS and provides: | |||||
| 81 | - Domain pointing to your server | 81 | - Domain pointing to your server | ||
| 82 | - Optional Cloudflare account or other reverse proxy for edge TLS | 82 | - Optional Cloudflare account or other reverse proxy for edge TLS | ||
| 83 | 83 | ||||
| 84 | - | > **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly via its internal CA (enabled by default on `server.tls_port`). Set `tls_port = 0` to disable. | 84 | + | > **Note:** Gordon can run behind Cloudflare/nginx, or terminate TLS directly on a smart TCP edge entrypoint via static certificates, public ACME certificates, or its internal CA. |
| 85 | 85 | ||||
| 86 | ## Related | 86 | ## Related | ||
| 87 | 87 | ||||
M docs/installation.md +14 -10
| @@ -115,7 +115,7 @@ EOF | |||
| 115 | 115 | ||
| 116 | 116 | ## Firewall Configuration | |
| 117 | 117 | ||
| 118 | - | Gordon needs ports accessible for the registry and proxy. | |
| 118 | + | Gordon needs ports accessible for the registry and the public edge entrypoint. | |
| 119 | 119 | ||
| 120 | 120 | ### Using firewalld | |
| 121 | 121 | ||
| @@ -128,9 +128,10 @@ sudo systemctl enable --now firewalld | |||
| 128 | 128 | sudo firewall-cmd --permanent --add-service=http | |
| 129 | 129 | sudo firewall-cmd --permanent --add-service=https | |
| 130 | 130 | ||
| 131 | - | # For rootless containers, redirect privileged ports | |
| 132 | - | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088 | |
| 133 | - | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443 | |
| 131 | + | # For rootless services, bind entrypoints.edge.address to a high port (for example :9000) | |
| 132 | + | # and redirect the external TCP edge ports your deployment uses. | |
| 133 | + | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000 | |
| 134 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 | |
| 134 | 135 | ||
| 135 | 136 | # Apply changes | |
| 136 | 137 | sudo firewall-cmd --reload | |
| @@ -165,10 +166,13 @@ Minimum required configuration: | |||
| 165 | 166 | ||
| 166 | 167 | ```toml | |
| 167 | 168 | [server] | |
| 168 | - | port = 8088 | |
| 169 | 169 | registry_port = 5000 | |
| 170 | 170 | gordon_domain = "gordon.yourdomain.com" | |
| 171 | 171 | ||
| 172 | + | [entrypoints.edge] | |
| 173 | + | address = ":443" | |
| 174 | + | protocol = "smart_tcp" | |
| 175 | + | ||
| 172 | 176 | [routes] | |
| 173 | 177 | "app.yourdomain.com" = "myapp:latest" | |
| 174 | 178 | ``` | |
| @@ -249,22 +253,22 @@ Point your domains to your server: | |||
| 249 | 253 | ||
| 250 | 254 | ## Cloudflare SSL Mode | |
| 251 | 255 | ||
| 252 | - | Gordon serves HTTP on `server.port` and HTTPS on `server.tls_port` by default. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin. | |
| 256 | + | Gordon serves public application traffic through HTTP-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin. | |
| 253 | 257 | ||
| 254 | 258 | | Mode | How it works | Use when | | |
| 255 | 259 | |------|-------------|----------| | |
| 256 | - | | **Flexible** | Cloudflare terminates TLS; connects to Gordon on HTTP | You want edge HTTPS only, or you set `tls_port = 0` | | |
| 260 | + | | **Flexible** | Cloudflare terminates TLS; connects to Gordon with cleartext HTTP | You want edge HTTPS only | | |
| 257 | 261 | | **Full (Strict)** | Cloudflare connects to Gordon over HTTPS with a valid cert | You want end-to-end HTTPS using Gordon's public ACME certs or a static cert (`tls_cert_file` / `tls_key_file`) | | |
| 258 | 262 | ||
| 259 | 263 | Wrong mode causes: **521** (Cloudflare can't connect) or **525** (TLS handshake failed). | |
| 260 | 264 | ||
| 261 | - | > **Important:** When `tls_port != 0`, you must also set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below. | |
| 265 | + | > **Important:** For Cloudflare-proxied HTTP paths, set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below. | |
| 262 | 266 | ||
| 263 | - | > **Rootless note:** Unprivileged users can't bind port 80. Forward port 80→8088 (or your configured port) via firewall — see the firewall section above. | |
| 267 | + | > **Rootless note:** Unprivileged users can't bind privileged ports. Bind `entrypoints.edge.address` to a high port (for example `:9000`) and forward/map external ports to it via firewall or container settings. | |
| 264 | 268 | ||
| 265 | 269 | ## Proxy Origin Allowlist | |
| 266 | 270 | ||
| 267 | - | When Gordon's internal CA is enabled (default: `tls_port = 8443`), HTTP requests from non-localhost IPs are restricted to certificate onboarding paths only. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications. | |
| 271 | + | When Gordon serves HTTP paths through a smart TCP edge, direct non-localhost HTTP requests can be restricted to certificate onboarding paths. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications. | |
| 268 | 272 | ||
| 269 | 273 | Add Cloudflare edge IPs to your `gordon.toml`: | |
| 270 | 274 | ||
| @@ -115,7 +115,7 @@ EOF | |||||
| 115 | 115 | ||||
| 116 | ## Firewall Configuration | 116 | ## Firewall Configuration | ||
| 117 | 117 | ||||
| 118 | - | Gordon needs ports accessible for the registry and proxy. | 118 | + | Gordon needs ports accessible for the registry and the public edge entrypoint. |
| 119 | 119 | ||||
| 120 | ### Using firewalld | 120 | ### Using firewalld | ||
| 121 | 121 | ||||
| @@ -128,9 +128,10 @@ sudo systemctl enable --now firewalld | |||||
| 128 | sudo firewall-cmd --permanent --add-service=http | 128 | sudo firewall-cmd --permanent --add-service=http | ||
| 129 | sudo firewall-cmd --permanent --add-service=https | 129 | sudo firewall-cmd --permanent --add-service=https | ||
| 130 | 130 | ||||
| 131 | - | # For rootless containers, redirect privileged ports | 131 | + | # For rootless services, bind entrypoints.edge.address to a high port (for example :9000) |
| 132 | - | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8088 | 132 | + | # and redirect the external TCP edge ports your deployment uses. |
| 133 | - | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=8443 | 133 | + | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000 |
| 134 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 | |||
| 134 | 135 | ||||
| 135 | # Apply changes | 136 | # Apply changes | ||
| 136 | sudo firewall-cmd --reload | 137 | sudo firewall-cmd --reload | ||
| @@ -165,10 +166,13 @@ Minimum required configuration: | |||||
| 165 | 166 | ||||
| 166 | ```toml | 167 | ```toml | ||
| 167 | [server] | 168 | [server] | ||
| 168 | - | port = 8088 | |||
| 169 | registry_port = 5000 | 169 | registry_port = 5000 | ||
| 170 | gordon_domain = "gordon.yourdomain.com" | 170 | gordon_domain = "gordon.yourdomain.com" | ||
| 171 | 171 | ||||
| 172 | + | [entrypoints.edge] | |||
| 173 | + | address = ":443" | |||
| 174 | + | protocol = "smart_tcp" | |||
| 175 | + | ||||
| 172 | [routes] | 176 | [routes] | ||
| 173 | "app.yourdomain.com" = "myapp:latest" | 177 | "app.yourdomain.com" = "myapp:latest" | ||
| 174 | ``` | 178 | ``` | ||
| @@ -249,22 +253,22 @@ Point your domains to your server: | |||||
| 249 | 253 | ||||
| 250 | ## Cloudflare SSL Mode | 254 | ## Cloudflare SSL Mode | ||
| 251 | 255 | ||||
| 252 | - | Gordon serves HTTP on `server.port` and HTTPS on `server.tls_port` by default. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin. | 256 | + | Gordon serves public application traffic through HTTP-capable entrypoints such as `entrypoints.edge` with `protocol = "smart_tcp"`. Choose the Cloudflare mode that matches how you want Cloudflare to reach your origin. |
| 253 | 257 | ||||
| 254 | | Mode | How it works | Use when | | 258 | | Mode | How it works | Use when | | ||
| 255 | |------|-------------|----------| | 259 | |------|-------------|----------| | ||
| 256 | - | | **Flexible** | Cloudflare terminates TLS; connects to Gordon on HTTP | You want edge HTTPS only, or you set `tls_port = 0` | | 260 | + | | **Flexible** | Cloudflare terminates TLS; connects to Gordon with cleartext HTTP | You want edge HTTPS only | |
| 257 | | **Full (Strict)** | Cloudflare connects to Gordon over HTTPS with a valid cert | You want end-to-end HTTPS using Gordon's public ACME certs or a static cert (`tls_cert_file` / `tls_key_file`) | | 261 | | **Full (Strict)** | Cloudflare connects to Gordon over HTTPS with a valid cert | You want end-to-end HTTPS using Gordon's public ACME certs or a static cert (`tls_cert_file` / `tls_key_file`) | | ||
| 258 | 262 | ||||
| 259 | Wrong mode causes: **521** (Cloudflare can't connect) or **525** (TLS handshake failed). | 263 | Wrong mode causes: **521** (Cloudflare can't connect) or **525** (TLS handshake failed). | ||
| 260 | 264 | ||||
| 261 | - | > **Important:** When `tls_port != 0`, you must also set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below. | 265 | + | > **Important:** For Cloudflare-proxied HTTP paths, set `proxy_allowed_ips` with Cloudflare edge IPs — see [Proxy Origin Allowlist](#proxy-origin-allowlist) below. |
| 262 | 266 | ||||
| 263 | - | > **Rootless note:** Unprivileged users can't bind port 80. Forward port 80→8088 (or your configured port) via firewall — see the firewall section above. | 267 | + | > **Rootless note:** Unprivileged users can't bind privileged ports. Bind `entrypoints.edge.address` to a high port (for example `:9000`) and forward/map external ports to it via firewall or container settings. |
| 264 | 268 | ||||
| 265 | ## Proxy Origin Allowlist | 269 | ## Proxy Origin Allowlist | ||
| 266 | 270 | ||||
| 267 | - | When Gordon's internal CA is enabled (default: `tls_port = 8443`), HTTP requests from non-localhost IPs are restricted to certificate onboarding paths only. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications. | 271 | + | When Gordon serves HTTP paths through a smart TCP edge, direct non-localhost HTTP requests can be restricted to certificate onboarding paths. Cloudflare and other reverse proxies must be listed in `proxy_allowed_ips` to reach your applications. |
| 268 | 272 | ||||
| 269 | Add Cloudflare edge IPs to your `gordon.toml`: | 273 | Add Cloudflare edge IPs to your `gordon.toml`: | ||
| 270 | 274 | ||||
M docs/upgrading.md +56 -3
| @@ -6,6 +6,59 @@ This guide covers breaking changes and migration steps between major versions. | |||
| 6 | 6 | ||
| 7 | 7 | Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values. | |
| 8 | 8 | ||
| 9 | + | ## Next major: Unified smart TCP entrypoints | |
| 10 | + | ||
| 11 | + | ### Breaking: `server.port` / `server.tls_port` no longer define public listeners | |
| 12 | + | ||
| 13 | + | Public application traffic is configured entrypoint-first. Migrate old HTTP/HTTPS listener settings to a smart TCP edge entrypoint. | |
| 14 | + | ||
| 15 | + | **Before:** | |
| 16 | + | ||
| 17 | + | ```toml | |
| 18 | + | [server] | |
| 19 | + | port = 8088 | |
| 20 | + | tls_port = 8443 | |
| 21 | + | registry_port = 5000 | |
| 22 | + | gordon_domain = "gordon.example.com" | |
| 23 | + | ``` | |
| 24 | + | ||
| 25 | + | **After:** | |
| 26 | + | ||
| 27 | + | ```toml | |
| 28 | + | [server] | |
| 29 | + | registry_port = 5000 | |
| 30 | + | gordon_domain = "gordon.example.com" | |
| 31 | + | ||
| 32 | + | [entrypoints.edge] | |
| 33 | + | address = ":443" | |
| 34 | + | protocol = "smart_tcp" | |
| 35 | + | ``` | |
| 36 | + | ||
| 37 | + | `edge` is the conventional route default, but it has no built-in port. `address = ":443"`, `address = ":9000"`, firewall forwarding, and container mappings such as `-p 443:9000` are deployment choices, not protocol modes. The `smart_tcp` protocol accepts TCP, sniffs HTTP/h2c/TLS, routes TLS passthrough by SNI when configured, and otherwise uses normal Gordon HTTPS fallback. | |
| 38 | + | ||
| 39 | + | If you run rootless and cannot bind 443 directly, bind a high port and map external ports to it: | |
| 40 | + | ||
| 41 | + | ```toml | |
| 42 | + | [entrypoints.edge] | |
| 43 | + | address = ":9000" | |
| 44 | + | protocol = "smart_tcp" | |
| 45 | + | ``` | |
| 46 | + | ||
| 47 | + | ```bash | |
| 48 | + | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000 | |
| 49 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 | |
| 50 | + | sudo firewall-cmd --reload | |
| 51 | + | ``` | |
| 52 | + | ||
| 53 | + | Keep `server.registry_port` for Docker/Podman push and pull traffic; it is separate from public app entrypoints. | |
| 54 | + | ||
| 55 | + | ### ACME migration notes | |
| 56 | + | ||
| 57 | + | - DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge. | |
| 58 | + | - HTTP-01 requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated. | |
| 59 | + | - TLS-ALPN-01 is unsupported. | |
| 60 | + | - Normal HTTPS fallback certificate priority is static certificates, then public ACME certificates, then Gordon's internal CA. | |
| 61 | + | ||
| 9 | 62 | ## v2.30.0 to v2.31.0 | |
| 10 | 63 | ||
| 11 | 64 | ### Breaking: Default Port Changes | |
| @@ -117,9 +170,9 @@ If you have automation parsing status-like fields from `routes list`, migrate it | |||
| 117 | 170 | ||
| 118 | 171 | Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`. | |
| 119 | 172 | ||
| 120 | - | - `server.tls_port` must stay enabled (`> 0`) | |
| 121 | - | - `http-01` needs public HTTP reachability for each hostname being validated | |
| 122 | - | - `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes | |
| 173 | + | - Normal HTTPS fallback must be available on a TLS-capable entrypoint | |
| 174 | + | - `http-01` needs an HTTP-capable smart TCP entrypoint reachable on external port 80 for each hostname being validated | |
| 175 | + | - `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes and does not require a special port-80 edge | |
| 123 | 176 | - `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes | |
| 124 | 177 | ||
| 125 | 178 | ### New: DNS Resolver Settings for ACME DNS-01 | |
| @@ -6,6 +6,59 @@ This guide covers breaking changes and migration steps between major versions. | |||||
| 6 | 6 | ||||
| 7 | Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values. | 7 | Route keys must be plain hostnames. Use inline tables like `"app.example.com" = { image = "myapp:latest" }`. Gordon still reads legacy `http://...` route entries for backward compatibility and rewrites them on the next save. Update `[routes]`, CLI commands, and automation that reference the old values. | ||
| 8 | 8 | ||||
| 9 | + | ## Next major: Unified smart TCP entrypoints | |||
| 10 | + | ||||
| 11 | + | ### Breaking: `server.port` / `server.tls_port` no longer define public listeners | |||
| 12 | + | ||||
| 13 | + | Public application traffic is configured entrypoint-first. Migrate old HTTP/HTTPS listener settings to a smart TCP edge entrypoint. | |||
| 14 | + | ||||
| 15 | + | **Before:** | |||
| 16 | + | ||||
| 17 | + | ```toml | |||
| 18 | + | [server] | |||
| 19 | + | port = 8088 | |||
| 20 | + | tls_port = 8443 | |||
| 21 | + | registry_port = 5000 | |||
| 22 | + | gordon_domain = "gordon.example.com" | |||
| 23 | + | ``` | |||
| 24 | + | ||||
| 25 | + | **After:** | |||
| 26 | + | ||||
| 27 | + | ```toml | |||
| 28 | + | [server] | |||
| 29 | + | registry_port = 5000 | |||
| 30 | + | gordon_domain = "gordon.example.com" | |||
| 31 | + | ||||
| 32 | + | [entrypoints.edge] | |||
| 33 | + | address = ":443" | |||
| 34 | + | protocol = "smart_tcp" | |||
| 35 | + | ``` | |||
| 36 | + | ||||
| 37 | + | `edge` is the conventional route default, but it has no built-in port. `address = ":443"`, `address = ":9000"`, firewall forwarding, and container mappings such as `-p 443:9000` are deployment choices, not protocol modes. The `smart_tcp` protocol accepts TCP, sniffs HTTP/h2c/TLS, routes TLS passthrough by SNI when configured, and otherwise uses normal Gordon HTTPS fallback. | |||
| 38 | + | ||||
| 39 | + | If you run rootless and cannot bind 443 directly, bind a high port and map external ports to it: | |||
| 40 | + | ||||
| 41 | + | ```toml | |||
| 42 | + | [entrypoints.edge] | |||
| 43 | + | address = ":9000" | |||
| 44 | + | protocol = "smart_tcp" | |||
| 45 | + | ``` | |||
| 46 | + | ||||
| 47 | + | ```bash | |||
| 48 | + | sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=9000 | |||
| 49 | + | sudo firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toport=9000 | |||
| 50 | + | sudo firewall-cmd --reload | |||
| 51 | + | ``` | |||
| 52 | + | ||||
| 53 | + | Keep `server.registry_port` for Docker/Podman push and pull traffic; it is separate from public app entrypoints. | |||
| 54 | + | ||||
| 55 | + | ### ACME migration notes | |||
| 56 | + | ||||
| 57 | + | - DNS-01 (`cloudflare-dns-01`) does not require a special external port 80 edge. | |||
| 58 | + | - HTTP-01 requires an HTTP-capable `smart_tcp` entrypoint reachable on external port 80 for each hostname being validated. | |||
| 59 | + | - TLS-ALPN-01 is unsupported. | |||
| 60 | + | - Normal HTTPS fallback certificate priority is static certificates, then public ACME certificates, then Gordon's internal CA. | |||
| 61 | + | ||||
| 9 | ## v2.30.0 to v2.31.0 | 62 | ## v2.30.0 to v2.31.0 | ||
| 10 | 63 | ||||
| 11 | ### Breaking: Default Port Changes | 64 | ### Breaking: Default Port Changes | ||
| @@ -117,9 +170,9 @@ If you have automation parsing status-like fields from `routes list`, migrate it | |||||
| 117 | 170 | ||||
| 118 | Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`. | 171 | Gordon can now obtain public certificates with `[tls.acme]` using either `http-01` or `cloudflare-dns-01`. | ||
| 119 | 172 | ||||
| 120 | - | - `server.tls_port` must stay enabled (`> 0`) | 173 | + | - Normal HTTPS fallback must be available on a TLS-capable entrypoint |
| 121 | - | - `http-01` needs public HTTP reachability for each hostname being validated | 174 | + | - `http-01` needs an HTTP-capable smart TCP entrypoint reachable on external port 80 for each hostname being validated |
| 122 | - | - `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes | 175 | + | - `cloudflare-dns-01` needs a Cloudflare token with zone-read and DNS-edit access for every zone used by HTTPS routes and does not require a special port-80 edge |
| 123 | - `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes | 176 | - `obtain_batch_size` limits new ACME orders per reconcile pass to reduce rate-limit spikes | ||
| 124 | 177 | ||||
| 125 | ### New: DNS Resolver Settings for ACME DNS-01 | 178 | ### New: DNS Resolver Settings for ACME DNS-01 | ||
M gordon.toml.example +8 -2
| @@ -2,8 +2,6 @@ | |||
| 2 | 2 | # Copy this file to ~/.config/gordon/gordon.toml and adjust for your domain. | |
| 3 | 3 | ||
| 4 | 4 | [server] | |
| 5 | - | port = 8088 | |
| 6 | - | tls_port = 8443 | |
| 7 | 5 | registry_port = 5000 | |
| 8 | 6 | gordon_domain = "gordon.example.com" | |
| 9 | 7 | # legacy_registry_domains = ["registry.example.com:5000"] | |
| @@ -20,6 +18,14 @@ gordon_domain = "gordon.example.com" | |||
| 20 | 18 | # max_proxy_response_size = "1GB" | |
| 21 | 19 | # max_concurrent_connections = 10000 | |
| 22 | 20 | ||
| 21 | + | [entrypoints.edge] | |
| 22 | + | address = ":443" | |
| 23 | + | protocol = "smart_tcp" | |
| 24 | + | # trusted_cidrs = [] | |
| 25 | + | # raw_fallback = "ssh-fallback" | |
| 26 | + | # raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |
| 27 | + | # allow_public_raw_fallback = false | |
| 28 | + | ||
| 23 | 29 | [dns] | |
| 24 | 30 | resolvers = ["1.1.1.1:53", "8.8.8.8:53"] | |
| 25 | 31 | propagation_timeout = "5m" | |
| @@ -2,8 +2,6 @@ | |||||
| 2 | # Copy this file to ~/.config/gordon/gordon.toml and adjust for your domain. | 2 | # Copy this file to ~/.config/gordon/gordon.toml and adjust for your domain. | ||
| 3 | 3 | ||||
| 4 | [server] | 4 | [server] | ||
| 5 | - | port = 8088 | |||
| 6 | - | tls_port = 8443 | |||
| 7 | registry_port = 5000 | 5 | registry_port = 5000 | ||
| 8 | gordon_domain = "gordon.example.com" | 6 | gordon_domain = "gordon.example.com" | ||
| 9 | # legacy_registry_domains = ["registry.example.com:5000"] | 7 | # legacy_registry_domains = ["registry.example.com:5000"] | ||
| @@ -20,6 +18,14 @@ gordon_domain = "gordon.example.com" | |||||
| 20 | # max_proxy_response_size = "1GB" | 18 | # max_proxy_response_size = "1GB" | ||
| 21 | # max_concurrent_connections = 10000 | 19 | # max_concurrent_connections = 10000 | ||
| 22 | 20 | ||||
| 21 | + | [entrypoints.edge] | |||
| 22 | + | address = ":443" | |||
| 23 | + | protocol = "smart_tcp" | |||
| 24 | + | # trusted_cidrs = [] | |||
| 25 | + | # raw_fallback = "ssh-fallback" | |||
| 26 | + | # raw_fallback_trusted_cidrs = ["100.64.0.0/10"] | |||
| 27 | + | # allow_public_raw_fallback = false | |||
| 28 | + | ||||
| 23 | [dns] | 29 | [dns] | ||
| 24 | resolvers = ["1.1.1.1:53", "8.8.8.8:53"] | 30 | resolvers = ["1.1.1.1:53", "8.8.8.8:53"] | ||
| 25 | propagation_timeout = "5m" | 31 | propagation_timeout = "5m" | ||