relay: support host aliases so in-cluster clients can bind to a community#2527
Open
jedwards27 wants to merge 4 commits into
Open
relay: support host aliases so in-cluster clients can bind to a community#2527jedwards27 wants to merge 4 commits into
jedwards27 wants to merge 4 commits into
Conversation
…nity Blox in-cluster clients can only reach the relay via a cluster-local Service DNS host, which has no communities.host row and 404s under row-zero binding. Host-header spoofing can't fix this: the mesh routes by Host (502 on mismatch) and WebSocket clients can't override it. Add community_host_aliases (unique on lower(host), guarded against collision with communities.host in both directions) and fall back to it in Db::lookup_community_by_host_or_alias after the primary host lookup misses. bind_community still binds the request's own host into TenantContext, so NIP-98/NIP-42 host checks are unaffected by aliasing. Add `buzz-admin community alias add|remove|list` to manage aliases for the tenant resolved from RELAY_URL. Co-Authored-By: Claude <noreply@anthropic.com>
Serialize the community/alias collision triggers with a shared advisory lock keyed on the lowercased host, closing a race where concurrent inserts to communities and community_host_aliases for the same host could both pass their EXISTS checks before either commits. Also make the operator availability endpoint check community_host_aliases so an alias-reserved host reports available: false instead of letting a create attempt fail on the trigger. Found by sq agents review. Co-Authored-By: Claude <noreply@anthropic.com>
…errors A host reserved via community_host_aliases has no communities.host row, so ON CONFLICT (lower(host)) never catches it; the alias-collision trigger raises unique_violation from the INSERT instead. That was bubbling up as a raw database error rather than the typed HostExists result the operator API expects for a taken host. Catch the 23505 and fold it into HostExists. Found by sq agents review. Co-Authored-By: Claude <noreply@anthropic.com>
Two more paths could hit an alias-reserved host without translating migration 0025's trigger error into something callers can act on: - ensure_configured_community (the operator API's legacy convergence mode, and startup community seeding) let the trigger's raw database error propagate as an internal 500 instead of the usual "community already exists" conflict. Added DbError::HostAliasCollision and mapped it in community_provisioning.rs. - resolve_admin_tenant in buzz-admin only checked communities.host, so running buzz-admin with RELAY_URL set to an alias (the exact in-cluster case this feature exists for) couldn't resolve its own tenant. Switched it to lookup_community_by_host_or_alias. Found by a third sq agents review pass; this closes out all findings from all three passes. Co-Authored-By: Claude <noreply@anthropic.com>
jedwards27
marked this pull request as ready for review
July 23, 2026 15:58
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.
relay: support host aliases so in-cluster clients can bind to a community
Problem
Since the multi-tenant communities cutover (Jul 4), the relay binds every request to a community strictly by the request
Hostheader againstcommunities.host. In-cluster clients — e.g. Blox workstations using thesprout-backend-bloxprovider — can only reach the relay via the cluster-local service hostsprout-relay.sprout.svc.cluster.local:3000(Blox egress cannot reach the Cloudflare-fronted external URL). That host has no community mapping, so every in-cluster connection fails closed with:Workarounds don't exist client-side:
Hostheader gets 502'd by the Envoy mesh before the relay ever sees it (the mesh routes by Host).Hostat all, so even a relay-side forwarded-host hack wouldn't cover the primary (WS) traffic.This blocks all Blox-hosted Buzz agents. Re-verified today (Jul 23) from a fresh staging workstation — still reproducible.
Design
Adds a "CNAME equivalent": one community reachable via multiple hostnames.
community_host_aliases(migrations/0025_community_host_aliases.sql, mirrored inschema/schema.sql):(host, community_id → communities.id), unique onlower(host), with guards preventing an alias colliding with anycommunities.host(and vice versa on community create — collision surfaces as a distinguishable error, not a silent failure).HostResolverDB implementation resolves againstcommunities.hostfirst, thencommunity_host_aliases. Fail-closed semantics preserved — unmapped hosts still return the same generic 404; no default tenant introduced.TenantContextstill binds the request host (aliases do not rewritetenant.host()), so NIP-98u-URL matching and AUTH origin checks continue to compare against the host the client actually used. Invariant comments intenant.rs/api/bridge.rsupdated accordingly.buzz-admin community alias add|remove|list, managing aliases for the tenant resolved fromRELAY_URL(admin tenant resolution also resolves through aliases now).Operational rollout
After deploy, map the cluster-local host to the existing community:
No client changes needed — in-cluster agents keep using
ws://sprout-relay.sprout.svc.cluster.local:3000.Testing
create_community_with_ownerpath).replica_fencereproduces identically on unmodifiedmain).conformance_multitenant.rs): with communities A and B, an alias for A binds to A and does not leak into B.cargo fmt/clippyclean; 3 passes ofsq agents reviewcompleted with all findings addressed.🤖 This PR was authored by Jude Edwards' AI agent (reviewed workflow, human-supervised). cc @tlongwell — this is the Blox relay-binding issue from our DM; figured we'd take a crack at the fix rather than just re-ping. Happy to adjust the approach if you had something different in mind for the cname-equivalent.