Skip to content

fix(provision): authenticate to password-protected shared Redis; stop leaking cluster DNS into mongo/nats customer URLs - #62

Merged
mastermanas805 merged 1 commit into
masterfrom
fix/redis-auth-and-public-hosts
Aug 12, 2026
Merged

fix(provision): authenticate to password-protected shared Redis; stop leaking cluster DNS into mongo/nats customer URLs#62
mastermanas805 merged 1 commit into
masterfrom
fix/redis-auth-and-public-hosts

Conversation

@mastermanas805

Copy link
Copy Markdown
Member

Both bugs found by live-testing the Azure AKS cluster on 2026-08-12.

1. /cache/new returned 503 — 100% broken on any fresh cluster

redis-provision gained --requirepass, but the shared cache backend built its admin
client as goredis.Options{Addr: redisHost} with no password field, so ACL SETUSER
failed with ERR Protocol error: unauthenticated multibulk length.

This was never a regression. DigitalOcean ran the dedicated Redis backend (a pod per
token — 188 such namespaces were counted live), so the shared path was never in the
production request path. Switching Azure to shared backends executed it for the first time.

New env REDIS_PROVISION_URL (redis://[user]:pass@host:port[/db], rediss:// for TLS),
parsed with goredis.ParseURL. Unset → falls back to the existing REDIS_PROVISION_HOST
Addr form, so current behaviour is unchanged. Malformed → error log with userinfo redacted,
then the same fallback.

Chosen over REDIS_PROVISION_PASSWORD to match every other backend — PostgresCustomersURL
and MongoAdminURI are already credentialed URLs in config.Config — and because it carries
username, db index, TLS and dial options a bare password cannot.

2. /nosql/new and /queue/new emitted unreachable URLs

mongodb://usr_…@mongodb.instant-data.svc.cluster.local:27017/…
nats://nats.instant-data.svc.cluster.local:4222

publicHost was applied only inside the case "k8s" branch, so the shared backend leaked
raw cluster DNS to customers. The postgres shared backend already did this correctly, which
is why /db/new returned pg.instanode.dev and these two did not. Mirrors
postgres.buildDBURL/publicHostPort exactly rather than inventing a third pattern:
env resolved at Provision time, "" sentinel so the caller falls back to the in-cluster
host, and no built-in default so a dev box keeps emitting localhost.

Ops impact: one new value, REDIS_PROVISION_URL. Bug 2 needs no config change —
K8S_MONGO_PUBLIC_HOST and K8S_NATS_PUBLIC_HOST are already set in the cluster and are
now honoured by the shared backends.

Call-site enumeration (rule 16)

  • newLocalBackend( — 3 prod sites in redis/backend.go (k8s-init-failure fallback,
    default arm, shared-carve) + 8 test sites. All updated.
  • redis.NewBackendserver.go:163, pool/factory.go:24, server_live_roundtrip_test.go:84.
  • NewSharedCarveBackendserver.go:165, dispatch_test.go:342.
  • Customer-URL emitters: exactly one broken shared emitter each for mongodb:// and
    nats://; the k8s emitters were already correct. Both fixed.

Rule-18 registry-iterating guards added: TestSharedBackendConstructors_AllHonourAdminURL
iterates every constructor yielding a shared LocalBackend.

Verification

make gate green — all 17 packages ok, golangci-lint run: 0 issues.
Every new function at 100% coverage. One Mongo-gated line in mongo.go could not be proved
locally (no Docker on the dev machine); it sits in a block covered by TestLocalProvision_Happy,
which runs against the CI service container.

Known, deliberately out of scope

  • Three pre-existing slog.Warn sites log a ParseURL error, and net/url echoes the full
    URL including the password. New code redacts; those three don't. Separate PR.
  • The shared /queue/new URL carries no credentials (subject-prefix isolation only), so
    pointing it at a public host exposes an unauthenticated broker. Reachability fix is correct;
    the exposure decision is an operator call.

🤖 Generated with Claude Code

…p leaking cluster DNS into mongo/nats customer URLs

Two customer-facing breakages found by live-testing the Azure cluster.

BUG 1 — /cache/new 503s on every call.
redis-provision now runs with --requirepass, but the local backend's admin
client was built as goredis.Options{Addr: REDIS_PROVISION_HOST} — no password
field anywhere in the path — so ACL SETUSER failed with "ERR Protocol error:
unauthenticated multibulk length" and Provision correctly fell closed rather
than handing out a credential-less shared URL.

New env REDIS_PROVISION_URL ("redis://[user]:password@host:port[/db]", parsed
with goredis.ParseURL) supplies the credentials. Unset → the existing
REDIS_PROVISION_HOST Addr form, byte-for-byte today's behaviour. Malformed →
logged (with the userinfo redacted, since net/url echoes the whole URL back in
its error) and the same fallback, because the factory chain has no error
channel and refusing to boot over one typo is worse than the 503.

Threaded as a separate constructor argument rather than read from env inside
the backend, mirroring mongo.NewBackend(backendType, adminURI, mongoHost):
credentialed admin endpoint and customer-facing host stay distinct inputs, and
the precedence rule lives in exactly one function. The backend records
opts.Addr as its host, never the admin URL — otherwise the admin password would
be interpolated into customer connection strings.

BUG 2 — /nosql/new and /queue/new emitted internal cluster DNS
(mongodb://…@mongodb.instant-data.svc.cluster.local:27017/…,
nats://nats.instant-data.svc.cluster.local:4222), unreachable for customers.
The public host was applied only inside the `case "k8s"` branch of each
NewBackend, and the cluster runs MONGO_PROVISION_BACKEND=local /
QUEUE_PROVISION_BACKEND=local.

Both now use the postgres local backend's mechanism verbatim: a buildXURL
helper (mirroring postgres.buildDBURL) over an env-resolved publicHostPort()
that falls back to the in-cluster admin host — never to an empty host. The
resolution order adds K8S_MONGO_PUBLIC_HOST / K8S_NATS_PUBLIC_HOST as a source,
so the already-configured cluster env fixes prod with no ops change, and takes
no built-in default, so a dev box keeps emitting localhost.

Call sites (rg -F, all updated): redis.NewBackend x2 prod (server.New,
pool.NewWithConfig) + 1 test; redis.NewSharedCarveBackend x1 prod + 1 test;
newLocalBackend x3 in the redis factory + 8 in tests. Customer-URL emitters:
one per shared backend (mongo.go Provision, queue/local.go Provision); the k8s
emitters were already correct.

Tests: table-driven over REDIS_PROVISION_URL set/unset/malformed and mongo+nats
public host set/unset, plus registry-iterating guards that every shared-carve
constructor honours the admin URL (rule 18) and that the admin password never
reaches a log line or a customer URL. New funcs are at 100% statement coverage;
make gate green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mastermanas805
mastermanas805 merged commit 962befa into master Aug 12, 2026
11 of 14 checks passed
@mastermanas805
mastermanas805 deleted the fix/redis-auth-and-public-hosts branch August 12, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant