feat!: regional-only deployment — drop legacy append-user support - #33
Merged
Conversation
One stack, two roles: unset keeps the legacy central behavior (append-user naming, argv-identical default), regional boxes set --force-requested-subdomains so VMs bind their bare hostname (<hostname>.<region>.tunnels.cde.glueopshosted.com). Documents the role matrix, CloudFront origin relationship, and credential sources. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
The stack now deploys only per-datacenter regional tunnel servers: --force-requested-subdomains is hardcoded (bare-hostname binds, taken names fail the bind), the SISH_SUBDOMAIN_FLAGS toggle is gone, and the fork-migration runbook is removed. The legacy central box must stay pinned to its current checkout (pre-0.4) — repo tip no longer carries the append-user naming it depends on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
Supersedes renovate PRs #28/#29/#30 (taking 3.14 over 3.13). All pins verified on the pinned digest: cp314 musllinux wheels present (MarkupSafe C speedups active), Flask/Werkzeug CI-test 3.14 upstream; gunicorn 26.0.0 lacks official 3.14 metadata but the sync-worker path is exercised and passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
Expert review of the TOFU authorizer found and reproduced: a non-string
auth_key left an empty key file behind, permanently locking the username
out; distinct raw usernames collapsing to one key file via
secure_filename ("vm 1" vs "vm_1"); and a create-then-write window
exposing empty files to concurrent workers. Registration now validates
JSON shape/types, rejects any username the sanitizer would alter, and
links a fully-written temp file into place atomically (same-key race
losers still allowed). Also: 16KB request cap, remote_addr in audit
lines, PYTHONUNBUFFERED so those lines actually flush.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
Second expert pass (Python + sish v2.23.0 source review): cap usernames at the 63-char hostname label limit (oversized attacker-chosen SSH usernames reached the filesystem and turned every retry into a traceback-spamming 500 from the public SSH port), deny on any OSError reading a stored key instead of 500ing, and strip/reject whitespace-only keys. Compose: authentication-key-request-timeout 15s -> 3s (sish holds a global lock across the webhook call; a hung authenticator serialized all auth on the instance) and point authentication-keys-directory at a tmpfs so the webhook-bypass path is empty by construction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
Each audit line is now a single write syscall (4 workers share stdout; print()'s separate message+newline writes could interleave mid-line), and a registration collision against an unreadable existing path (e.g. a dangling symlink in sish_users/) audits "stored key unreadable" instead of mislabeling it a race. Non-root container and a TOFU registration cap were considered and deliberately skipped (compose runs as root; bind-mount ownership would break registrations). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
TUNNEL_ALLOWED_CIDRS (default: all private ranges) is enforced by the authenticator using the client address sish reports on every SSH auth — so it gates tunnel registration/connection only, and browsers/CloudFront (:80/:443) are unaffected. Unparseable or missing sources fail closed while the list is active; empty value disables the check; a malformed CIDR crashes the authenticator at startup. sish's own --whitelisted-ips was deliberately not used: it is global across SSH and HTTP/S and would have blocked public web access. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
venkatamutyala
force-pushed
the
feat/regional-tunnel-endpoints
branch
from
August 2, 2026 09:10
0aa78cb to
09fa290
Compare
100.64.0.0/10 joins the RFC 1918 defaults for TUNNEL_ALLOWED_CIDRS — codespace VMs join the tailnet at boot, so tailnet-sourced tunnel connections are first-class. Also drops the host-firewall documentation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
sish v2.23.0 source review confirmed the allowlist gates every tunnel-creation path — but an unqualified port publish also binds [::], and docker-proxy relays IPv6 clients into the IPv4-only compose network with their source rewritten to the bridge gateway, a 172.x address inside the private-range allowlist. Binding 0.0.0.0 closes the laundering path; tunnel DNS is A-record-only so no legitimate client connects over v6. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ErBUiAYTosnpbF9hvUj3Dn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes this repo regional-only: the stack deploys per-datacenter tunnel servers for the regional CDE rollout, and legacy support is removed rather than parameterized (an earlier revision of this PR had a
SISH_SUBDOMAIN_FLAGStoggle with a legacy default — dropped by decision).--force-requested-subdomainsis hardcoded: a codespace VM binds its bare hostname, URLs arehttps://<hostname>.<region>.tunnels.cde.glueopshosted.com(no prefix), and a taken name fails the bind instead of silently going random. The append-user flags are gone..env.exampletargets a regional deployment:DOMAIN=<region>.tunnels.cde.glueopshosted.com, AWS creds from theacme-dns01-cde.glueopshosted.comIAM user (cde_acme_*outputs of the CDE assets workspace).origin.<region>..., covered by the same wildcard cert; 443 deliberately stays directly reachable), and a smoke test that catches a wrong-version deployment by URL shape.MIGRATION.md(old-fork → this stack, central-box content) is removed; it remains in git history and pre-0.4 tags.The legacy central box (
tunnels.glueopshosted.com) is unaffected — it stays on its current pre-0.4 checkout and needs no changes.Also folded in: Renovate updates + auth hardening
Supersedes the open Renovate PRs (#28, #29, #30): base image →
python:3.14-alpine(taking #29 over #28's 3.13) andclick==8.4.2. A Python-expert review verified every pin on the exact digest (cp314 musllinux wheels present with MarkupSafe's C speedups active; Flask/Werkzeug CI-test 3.14 upstream; gunicorn 26.0.0 lacks official 3.14 metadata but its sync-worker path is exercised and passing) — Renovate will auto-close those PRs once this merges.The same review found three live-reproducible defects in the TOFU authorizer, fixed here in
auth/auth.py:auth_keycrashed mid-registration and left an empty key file, permanently locking that username out — JSON shape/types are now validated up front;secure_filename("vm 1"vs"vm_1") — any username the sanitizer would alter is now rejected outright (VM hostnames always pass unchanged);Plus: 16 KB request cap,
remote_addrin the audit log lines, andPYTHONUNBUFFERED=1so those lines actually flush. Full flow re-tested in the built image: register/allow/deny, poisoning attempt then legitimate registration, collision rejection, path traversal, no temp/empty leftovers, oversized body → 413.A second review pass (fresh Python expert + a sish expert working from the tagged v2.23.0 source) confirmed the wire contract exactly (payload shape, comment-free/newline-free key format, per-key deny semantics, TOFU re-checked on every reconnect, adversarial 32-way race tests producing exactly one registration) and added:
OSErrorreading a stored key now denies instead of 500ing; whitespace-only keys rejected.--authentication-key-request-timeout15s → 3s — sish holds a global lock across the webhook call, so a hung authenticator serialized all auth on the instance; 3s bounds the stall (the webhook answers in ms).--authentication-keys-directorynow points at a tmpfs, making sish's webhook-bypass path (any key file in that directory is accepted without consulting the authenticator) empty by construction.-o IdentitiesOnly=yeson the autossh invocation — without it, a forwarded ssh-agent's keys are offered first and TOFU can permanently pin the wrong key on a VM's first connection.Deploying a region
.env: regionalDOMAIN,cde_acme_*creds,ACME_EMAIL. No naming flag — the stack is regional by construction.docker compose up -d— certbot needs thecde.glueopshosted.comNS delegation live to validate (aws-dns-production#150).ssh -p 2222 -R smoketest:80:localhost:3000 <region>.tunnels.cde.glueopshosted.com→ the printed URL must behttps://smoketest.<region>...with no<user>-prefix.🤖 Generated with Claude Code