Files
zeekayandHanzo Dev d86f10847a feat(iam): Zoo's own app graph — 5 clients, one issuer, proven end to end
Zoo had exactly one working OAuth client (zoo-cloud) and no declaration of it
anywhere. This ports the Lux shape into this repo: the brand-neutral mechanism
stays in hanzoai/iam (internal/provision), Zoo owns its DATA here, and
`make iam-provision` is the only supported way that data reaches the IdP.

ISSUER. id.zoo.network serves iss=https://zoolabs.id — host and issuer really do
disagree, and it is resolved in favour of zoolabs.id: IAM_ISSUER_MAP already
maps all three Zoo hosts to it, discovery agrees from either host, every sibling
brand's issuer is its <brand>.id apex (hanzo.id / lux.id / pars.id) with
id.<brand>.network as the alias, and zoo.id has no DNS at all so it cannot be
the issuer. The remaining defect is the DISCOVERY DOCUMENT (OIDC Discovery §4.3
wants issuer == fetch prefix), which is a frontend/ingress fix, not a data one.
Reasoning is in the file header, not just here.

Five clients, each declared only on evidence measured against the live surface:
zoo-cloud, zoo-wallet, zoo-dao, zoo-explore, zoo-id. Hosts that carry no OAuth
wiring, hosts served by shared Hanzo backends, and hosts with no DNS are listed
too, so each omission is a recorded decision.

Two defects fixed rather than described:

  cert. The upsert copies req.Cert verbatim on CREATE, so an omitted cert means
  an EMPTY cert, and a client with no cert cannot mint an id_token — the login
  dies AFTER a successful code exchange. Four Zoo clients were in exactly that
  state. Every app now names cert-zoo (convention cert-<org>: lux-cloud has
  cert-lux, hanzo-cloud has cert-hanzo); for zoo-cloud it is a no-op.

  cloud.zoo.network is no longer declared. It has an ingress but NO DNS record
  at 1.1.1.1 or 8.8.8.8, so no browser can reach it; a redirect URI for a host
  that cannot exist is the drift this document exists to prevent. Lux keeps
  cloud.lux.network because that host does resolve.

Image pin moved forward to sha-f9813f6: sha-4bec5ef predates the `cert` field
and the strict parser rejects the document outright. f9813f6 is a strict
descendant of the public-client fixes and of 4b0f4e93, and is the newest sha-
tag in GHCR that resolves to a known commit.

PROVEN, not asserted:
  - converge run twice; the five records are byte-identical apart from
    updatedAt, and no client secret was issued (all five stay public/PKCE)
  - real login as [email protected] -> authorization code -> PKCE token exchange with
    NO client secret, on all five clients
  - id_token minted for each: kid=cert-zoo, iss=https://zoolabs.id,
    aud=<the client>, and the signature verifies against the published JWKS
  - the dropped redirect now correctly 400s: invalid redirect_uri

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 19:02:15 -07:00

88 lines
4.1 KiB
Makefile

# Zoo Universe — thin wrapper over the lux CLI.
#
# All per-env parameters (networkID, ports, dataDir, genesis path) live
# in chain.yaml. This Makefile carries only the brand name and the env
# list. Verbs delegate to the lux CLI which resolves `<brand>/<env>` to
# a RuntimeProfile via the brand registry (~/work/<brand>/universe).
#
# Equivalent invocations (Make is optional):
# make up env=devnet ≡ lux up zoo/devnet
# make cycle env=mainnet ≡ lux cycle zoo/mainnet
SHELL := /bin/bash
.DEFAULT_GOAL := help
BRAND := zoo
ENVS := localnet devnet testnet mainnet
LUX := lux
# Single parametric verbs: `make up env=devnet`, etc.
.PHONY: up down snap cycle ps ctx
up : ; @$(LUX) up $(BRAND)/$(env)
down : ; @$(LUX) down $(BRAND)/$(env)
snap : ; @$(LUX) snap $(BRAND)/$(env)
cycle : ; @$(LUX) cycle $(BRAND)/$(env)
ps : ; @$(LUX) ps
ctx : ; @$(LUX) ctx
# Fanout: cycle every env in sequence.
.PHONY: cycle-all
cycle-all: ; @for e in $(ENVS); do $(LUX) cycle $(BRAND)/$$e || exit 1; done
# DOKS cluster overlays (separate concern: K8s, not chain lifecycle).
KCTX := do-sfo3-zoo-k8s
.PHONY: k8s-apply k8s-pods
k8s-apply: ; @kubectl --context $(KCTX) apply -k k8s/zoo-$(env)/
k8s-pods : ; @kubectl --context $(KCTX) -n zoo-$(env) get pods -o wide
# IAM — Zoo's identity app graph (deterministic, from this repo only).
#
# infra/k8s/iam/provision.yaml is the ONE source of truth for Zoo's OAuth
# clients. These targets are the ONLY supported way to change them — never
# hand-edit an application in a console, or the next converge silently reverts
# it and the drift is invisible until a login breaks.
#
# IAM_CTX/IAM_NS are hanzo-k8s/hanzo, NOT the Zoo cluster: the IdP that answers
# zoolabs.id runs there, and so does the service token it authenticates with.
IAM_DOC := infra/k8s/iam/provision.yaml
IAM_CTX := do-sfo3-hanzo-k8s
IAM_NS := hanzo
.PHONY: iam-provision iam-provision-plan iam-provision-logs
iam-provision-plan: ## Derive Zoo's OAuth clients and print them; writes nothing
@kubectl --context $(IAM_CTX) -n $(IAM_NS) run zoo-iam-plan-$$$$ --rm -i --restart=Never \
--image=$$(awk -F'image: ' '/image: ghcr.io/{print $$2; exit}' infra/k8s/iam/provision-job.yaml) \
--overrides='{"spec":{"imagePullSecrets":[{"name":"ghcr-secret"}]}}' \
--command -- /iam2 provision --config=/dev/stdin --url=https://zoolabs.id --dry-run < $(IAM_DOC)
iam-provision: ## Converge zoolabs.id onto provision.yaml (idempotent; never rotates a secret)
@kubectl --context $(IAM_CTX) -n $(IAM_NS) create configmap zoo-iam-provision \
--from-file=provision.yaml=$(IAM_DOC) --dry-run=client -o yaml | \
kubectl --context $(IAM_CTX) apply -f -
@kubectl --context $(IAM_CTX) -n $(IAM_NS) delete job zoo-iam-provision --ignore-not-found
@kubectl --context $(IAM_CTX) apply -f infra/k8s/iam/provision-job.yaml
@kubectl --context $(IAM_CTX) -n $(IAM_NS) wait --for=condition=complete job/zoo-iam-provision --timeout=180s \
|| { echo "provision did not complete — logs:"; $(MAKE) iam-provision-logs; exit 1; }
@$(MAKE) iam-provision-logs
iam-provision-logs: ## Show what the last converge actually did
@kubectl --context $(IAM_CTX) -n $(IAM_NS) logs job/zoo-iam-provision --tail=100 2>/dev/null || true
.PHONY: help
help: ## Show this help
@printf " Chain verbs (env=<localnet|devnet|testnet|mainnet>)\n"
@printf " make up env=devnet boot zoo/<env>\n"
@printf " make down env=devnet stop zoo/<env>\n"
@printf " make snap env=devnet snapshot zoo/<env>\n"
@printf " make cycle env=devnet boot → deploy → stop → snap\n"
@printf " make cycle-all cycle every env\n"
@printf " make ps / make ctx registry inspection\n"
@printf "\n K8s (DOKS $(KCTX))\n"
@printf " make k8s-apply env=mainnet apply k8s/zoo-<env>/ overlay\n"
@printf " make k8s-pods env=mainnet list pods in namespace\n"
@printf "\n IAM (zoolabs.id, via $(IAM_CTX)/$(IAM_NS))\n"
@printf " make iam-provision-plan derive OAuth clients, write nothing\n"
@printf " make iam-provision converge zoolabs.id onto $(IAM_DOC)\n"
@printf " make iam-provision-logs what the last converge did\n"