gateway-domain: give a tailnet box a real public domain

An agent on a box with no public IP frequently needs a reachable HTTPS URL —
an OAuth callback, a webhook receiver, a demo link. The testing gateway
already holds a wildcard record for *.gtest.commoninternet.net and forwards
by SNI, but nothing here knew that, so every agent had to be told by hand.

    tools/gateway-domain.py add myapp
    #   myapp.gtest.commoninternet.net  ->  100.84.190.30

The backend defaults to the running box's own tailscale IP, which is the case
that comes up almost every time.

The admin password comes from gateway.admin_password in the secret store; the
tool reads it itself, so no caller handles the value and there is no second
copy to drift or get committed.

Two things the tool refuses to do, both learned by doing them:

Backends must be a literal IPv4 address. The gateway's validate_ip accepts a
hostname, but put_domain/remove_domain only match lines whose backend is
numeric ([\d.:]+). A hostname mapping can therefore be written once and never
updated or removed through the admin UI — it becomes an orphan that only a
hand-edit of tunnel_map.conf clears. One got created while testing this.

Verification re-reads the mapping table instead of trusting the POST body.
The admin app mutates its in-memory dict and renders that, so a delete that
silently failed still renders as success. Checking the response alone
reported "removed" for an entry that was still on disk.

skills/gateway-domain/ carries the rest: that the gateway does NOT terminate
TLS (your box serves the cert for that name), how ACME still works through
it, that only 22/80/443 are open at the edge, and how to recover if an
interrupted e2e run leaves the admin password reseeded.
This commit is contained in:
2026-08-20 17:10:28 +00:00
parent bb03bed218
commit 22bd897a86
3 changed files with 361 additions and 0 deletions
+16
View File
@@ -379,6 +379,22 @@ so reviewers read the pre-fixup code and no interdiff exists — with no warning
push itself succeeded. Always: push, resubmit, then reply with the printed interdiff URL. See
`machine-docs/PR-WORKFLOW.md`, "A push does NOT advance the round".
## The testing gateway — public domains for tailnet boxes
A box on the tailnet with no public IP can still have a real HTTPS domain: the shared testing
gateway holds a wildcard record for `*.gtest.commoninternet.net` and forwards by SNI.
```bash
python3 engine/tools/gateway-domain.py add myapp # -> myapp.gtest.commoninternet.net -> this box
python3 engine/tools/gateway-domain.py list
python3 engine/tools/gateway-domain.py remove myapp
```
The admin password is `gateway.admin_password` in the secret store below; the tool reads it
itself. **The gateway does not terminate TLS** — it proxies the encrypted stream, so your box
serves the certificate for that name. Full detail, including how to get a cert and why hostname
backends are refused, is in the `gateway-domain` skill (`skills/gateway-domain/SKILL.md`).
## Secrets — one encrypted store, never in git
**Every credential on an orchestrator host lives in one sops+age encrypted file. Do not put a