Support routes.

This commit is contained in:
2026-07-10 05:21:43 +00:00
parent 5c26c7530a
commit 43253ca120
8 changed files with 597 additions and 85 deletions

View File

@@ -21,12 +21,66 @@ The domain is the unique key. One registration per domain.
|-------|------|----------|---------|-------------|
| `domain` | string | yes | — | FQDN to route. Unique key. |
| `source` | string | no | `"manual"` | Who registered this: `wild-cloud`, `wild-works`, `manual`. |
| `backend.address` | string | yes | — | Target host:port (e.g., `192.168.8.240:443`). |
| `backend.type` | string | yes | — | `tcp-passthrough` or `http`. |
| `backend.address` | string | yes* | — | Target host:port (e.g., `192.168.8.240:80`). |
| `backend.type` | string | yes* | — | `tcp-passthrough` or `http`. |
| `backend.health` | string | no | — | Health check path for L7 services (e.g., `/health`). |
| `subdomains` | bool | no | `false` | If true, also routes `*.domain` traffic to this backend. |
| `subdomains` | bool | no | `false` | If true, also routes `*.domain` traffic. |
| `public` | bool | no | `false` | If true, domain gets public DNS + internet exposure. |
| `tls` | string | no | inferred | `passthrough` or `terminate`. Defaults based on backend type. |
| `routes` | array | no* | — | Path-based multi-backend routing (see below). |
*A service must have either `backend` (simple) or `routes` (multi-backend), not both.
## Simple vs multi-backend services
### Simple (single backend)
Most services use a single backend. All traffic for the domain goes to one place:
```json
{
"domain": "my-api.payne.io",
"backend": {"address": "192.168.8.60:9001", "type": "http"},
"tls": "terminate"
}
```
### Multi-backend (path-based routing)
When different URL paths on the same domain need to reach different backends, use `routes`. Each route maps path prefixes to a backend with its own L7 options. Routes are evaluated in order — first match wins. A route with empty `paths` is a catch-all (should be last).
```json
{
"domain": "payne.io",
"public": true,
"tls": "terminate",
"routes": [
{
"paths": ["/_matrix", "/.well-known/matrix"],
"backend": {"address": "192.168.8.60:8008", "type": "http", "health": "/health"}
},
{
"backend": {"address": "192.168.8.240:80", "type": "http"}
}
]
}
```
Here `/_matrix` and `/.well-known/matrix` go to a Matrix server, everything else goes to a website. Both backends can be on different hosts.
### Route fields
Each route in the `routes` array supports:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `paths` | []string | no | Path prefixes to match (empty = catch-all). |
| `backend.address` | string | yes | Target host:port. |
| `backend.type` | string | yes | `http` (routes only support L7). |
| `backend.health` | string | no | Health check path. |
| `headers.request` | map | no | Request headers to set (e.g., `X-Forwarded-Proto: https`). |
| `headers.response` | map | no | Response headers to set (e.g., CORS headers). |
| `ipAllow` | []string | no | CIDR whitelist — deny requests not matching (e.g., `["10.0.0.0/8"]`). |
## User-facing concepts
@@ -50,7 +104,7 @@ The API fields map to three user-visible controls:
`tls: "passthrough"` or `tls: "terminate"`
- **Passthrough**: Central forwards encrypted traffic directly to the backend. The backend handles its own TLS certificates (e.g., a k8s cluster with traefik). Maps to `backend.type: "tcp-passthrough"`.
- **Passthrough**: Central forwards encrypted traffic directly to the backend. The backend handles its own TLS certificates. Maps to `backend.type: "tcp-passthrough"`.
- **Terminate**: Central provisions a TLS certificate and handles HTTPS. Traffic is decrypted at Central and proxied as HTTP to the backend. Maps to `backend.type: "http"`.
## What Central does per registration
@@ -62,24 +116,26 @@ When a service is registered, Central automatically:
| **LAN DNS** | `address=/<domain>/<backend-IP>` — direct to backend | `address=/<domain>/<central-IP>` — through Central |
| **LAN DNS (private)** | Also `local=/<domain>/` — prevents upstream forwarding | Same |
| **Public DNS** | DDNS A record if public | Same |
| **Proxy** | L4 SNI passthrough. Subdomains adds `*.domain` matching. | L7 HTTP reverse proxy by Host header. |
| **Proxy** | L4 SNI passthrough. Subdomains adds `*.domain` matching. | L7 HTTP reverse proxy by Host header. Routes add path-based ACLs. |
| **TLS** | Backend handles — Central passes through | Central provisions cert via Let's Encrypt |
## Examples
### k8s cluster (passthrough, public, with subdomains)
### Wild Cloud k8s cluster (terminate, public, wildcard)
Wild Cloud registers its instances as HTTP terminate services. Central handles TLS and forwards plain HTTP to the cluster's Traefik ingress controller.
```json
{
"domain": "cloud.payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"},
"backend": {"address": "192.168.8.240:80", "type": "http"},
"subdomains": true,
"public": true
}
```
Central creates: LAN DNS → 192.168.8.240, public DNS A record, HAProxy L4 SNI + wildcard *.cloud.payne.io, TLS passthrough.
Central creates: LAN DNS → Central IP, public DNS A record, HAProxy L7 TLS terminate + wildcard `*.cloud.payne.io`, cert via certbot.
### Internal web app (terminate, private)
@@ -93,19 +149,60 @@ Central creates: LAN DNS → 192.168.8.240, public DNS A record, HAProxy L4 SNI
Central creates: LAN DNS → Central IP (with `local=/`), HAProxy L7 reverse proxy, TLS cert via certbot. No public DNS.
### Custom domain (passthrough, public, exact match)
### Service with custom response headers
```json
{
"domain": "keila.cloud.payne.io",
"source": "wild-cloud",
"routes": [{
"backend": {"address": "192.168.8.240:80", "type": "http"},
"headers": {
"response": {
"Access-Control-Allow-Origin": "https://cloud.payne.io"
}
}
}],
"public": true
}
```
### Service with IP whitelisting (internal only)
```json
{
"domain": "headlamp.internal.cloud.payne.io",
"source": "wild-cloud",
"routes": [{
"backend": {"address": "192.168.8.240:80", "type": "http"},
"ipAllow": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}]
}
```
### L4 passthrough (non-HTTP services)
For services that handle their own TLS (e.g., standalone apps with built-in cert management):
```json
{
"domain": "payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"},
"subdomains": false,
"public": true
}
```
Central creates: LAN DNS → 192.168.8.240, public DNS A record, HAProxy L4 SNI exact match only. No wildcard.
Central creates: LAN DNS → backend IP, public DNS A record, HAProxy L4 SNI passthrough. TLS handled by backend.
## Wild Cloud integration
Wild Cloud instances with `central.registration: per-app` in their instance config use a client-go Ingress watcher that automatically registers domains with Central:
- One wildcard registration per instance domain (e.g., `*.cloud.payne.io` with `subdomains: true`)
- One wildcard registration per internal domain (e.g., `*.internal.cloud.payne.io`)
- Individual registrations for apps with special routing needs (custom headers, IP whitelisting, extra domains)
- Apps define special routing via a `central` section in their manifest config
## What is NOT a registration