Files
universe/infra/k8s/iam/provision-job.yaml
zeekayandHanzo Dev 56e81375c3 iam: the provision job runs again — newer image, and the backend it writes to
The reconcile path was dead three ways, so no IAM change could ship at all and
console.lux.cloud stayed unreachable behind "invalid redirect_uri".

1. image sha-9e7cc0c predates `owner:` and rejected the document outright
   ("unknown field owner"). v1.34.40 is the semver the live IAM itself runs and
   accepts every field here. Preferring the semver over a sha means the writer
   and the store cannot silently drift apart.
2. the binary is /iam, not /iam2 — the old entrypoint failed as
   "stat /iam2: no such file or directory".
3. --url pointed at iam.hanzo.svc, whose Deployment is scaled to 0: every write
   died on "connection refused". id.hanzo.svc is not the answer either — it is
   the STATIC white-label login site and answered the admin path with 200 and a
   page of HTML ("invalid character '<'"). A 200 from a static site proves the
   host is up, never that the API is there.

IAM is embedded natively in `cloud`; api.hanzo.ai/v1/iam is its public face.
That endpoint answers the admin path with JSON — 401 "a valid service token is
required" — so the route is real and only the credential was missing.

Converge now reports 13 app(s), 0 failed, and console.lux.cloud reaches the Lux
sign-in page instead of an authorization error.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-10 20:25:11 -07:00

107 lines
4.8 KiB
YAML

# Applies Lux's IAM app graph (provision.yaml) to the live IdP.
#
# ONE source of truth: the ConfigMap this Job reads is generated FROM
# provision.yaml by `make iam-provision`, never hand-written, so the document
# in git is always what converged.
#
# It runs the OSS mechanism (`iam2 provision`, hanzoai/iam internal/provision)
# against Lux's data. The image is pinned — a Job that floats on :latest is not
# a deterministic deployment.
#
# The tag is sha-<7>, not v<X.Y.Z>, because that is what hanzoai/iam main
# actually publishes: its builder is .hanzo/workflows/image.yml on git.hanzo.ai
# (the canonical forge) and it tags by commit. The v<X.Y.Z> images come from a
# DIVERGED v1.33.x release line whose builder is deliberately disabled, so a
# v-tag cut on main produces no artifact at all — pinning one here would leave
# this Job permanently unschedulable.
#
# Runs in ns hanzo on hanzo-k8s because that is where the IAM that answers
# lux.id lives, and where the service token it authenticates with is stored.
# Re-running is a no-op: the upsert keys on <org>-<app> and preserves each
# client secret IAM already issued.
apiVersion: batch/v1
kind: Job
metadata:
name: lux-iam-provision
namespace: hanzo
labels:
app.kubernetes.io/name: lux-iam-provision
app.kubernetes.io/part-of: lux
spec:
# Keep the pod briefly so `kubectl logs job/lux-iam-provision` shows what
# converged; the run report IS the audit record.
ttlSecondsAfterFinished: 600
backoffLimit: 2
template:
metadata:
labels:
app.kubernetes.io/name: lux-iam-provision
spec:
restartPolicy: Never
imagePullSecrets:
- name: ghcr-secret
containers:
- name: provision
# A pin is not free: it caps the vocabulary this repo may write in.
# sha-bb7eff5 predated `redirects:`/`grants:`; sha-9e7cc0c in turn
# predated `owner:` and rejected this document outright — the whole
# reconcile path was dead, so no IAM change could ship at all.
# v1.34.40 is the semver the live IAM itself runs and accepts every
# field here (plan: 13 apps, 0 failed). Prefer the semver over a sha:
# it is the tag the deployment is already pinned to, so the writer and
# the store can no longer drift apart silently.
# The binary is /iam here — /iam2 was its name before the rename, and
# the old entrypoint fails as "stat /iam2: no such file or directory".
image: ghcr.io/hanzoai/iam:v1.34.40
command: ["/iam"]
args:
- provision
- --config=/etc/iam-provision/provision.yaml
# The IAM SERVICE, not a public hostname. A public host resolves
# through DNS/CDN and may reach a different instance than the one
# the token endpoint reads — writing Lux's clients to a store the
# login never consults is silent and total: every converge reports
# success while every login keeps failing.
#
# `cloud` is that service. IAM is embedded in it natively, and
# api.hanzo.ai/v1/iam is its public face — the one the login reads.
#
# Two wrong targets were tried first and both are instructive.
# `iam.hanzo.svc` has NO endpoints (Deployment scaled to 0), so every
# write died on "connection refused" — loud, harmless. `id.hanzo.svc`
# is the STATIC white-label login site: it answered the admin path
# with 200 and a page of HTML, which decoded as
# `invalid character '<'`. A 200 from a static site proves the host
# is up, never that the API is there.
#
# The real backend is unambiguous: it answers this exact path with
# JSON — 401 {"msg":"a valid service token is required"} — so the
# route exists and only the credential is missing.
- --url=http://cloud.hanzo.svc:8000
env:
# The same unified service token the admin upsert endpoints
# validate. Never inlined — it is a credential.
- name: IAM_SERVICE_TOKEN
valueFrom:
secretKeyRef:
name: iam-service-token
key: IAM_SERVICE_TOKEN
volumeMounts:
- name: doc
mountPath: /etc/iam-provision
readOnly: true
resources:
requests: { cpu: 10m, memory: 32Mi }
limits: { cpu: 200m, memory: 128Mi }
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532
capabilities:
drop: ["ALL"]
volumes:
- name: doc
configMap:
name: lux-iam-provision