These workplans exist only in the retired local hub. Their random pre-ADR-007 identifiers are refused by C-06 as stale references, so they cannot be registered. Deriving from the canonical record id takes no identity from anything: central does not hold them and the old ids die with the cache. Records central already holds were deliberately left untouched. Refs CUST-WP-0068-T06 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
11 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|
| ACTIVITY-WP-0025 | workplan | SSO access control for ops console and Temporal UI | infotech | activity-core | finished | codex | activity-core | 2026-07-22 | 2026-07-22 | 347cad20-d66c-5151-b577-50eb93a85023 |
SSO access control for ops console and Temporal UI
Origin
ACTIVITY-WP-0024 shipped the operator automation console (/ops/ui) with
ClusterIP + port-forward access and a shared operator token for mutations.
Operators also use Temporal Web UI (actcore-temporal-ui:8080) for workflow
debugging; the ops UI now deep-links to it.
Pain: port-forward + kubeconfig switching is fragile; shared token is not identity-aware. Goal: proper browser access via SSO without port-forward.
Closes SCOPE G10 (API external access) for the ops and Temporal UIs (not necessarily for unauthenticated programmatic API from the public internet).
Goal
Expose activity-core ops UI and Temporal Web UI behind authenticated ingress with Keycloak / key-cape OIDC SSO, so an authorized operator can:
- Open HTTPS URLs in a browser (no
kubectl port-forward) - Authenticate with org SSO (MFA per IAM profile where required)
- Use ops console mutations under their identity (not only a shared token)
- Open Temporal UI under the same access policy
Non-goals
- Public anonymous access to
/opsor Temporal UI - Replacing Temporal’s own RBAC model with fine-grained multi-tenant Temporal namespaces for all of Coulomb (single activity-core Temporal is fine for MVP)
- Full API productization for third-party clients (focus is human UI access)
- Moving Temporal server hosting ownership out of the activity-core namespace (unless railiance-platform already owns that path)
Architecture sketch (proposed)
Browser
│ HTTPS
▼
Ingress (TLS) ──► oauth2-proxy / key-cape edge ── OIDC ──► Keycloak (key-cape)
│ │
│ allowlisted groups │ identity headers / session
▼ ▼
actcore-api (/ops, /ops/ui) actcore-temporal-ui
mutations: require SSO subject
(retire shared token as primary; keep break-glass optional)
Credential routing: key-cape owns OIDC/login (warden route show key-cape-oidc-login);
flex-auth for authorization decisions if used; do not invent secrets in
git or State Hub.
Design decisions to lock in T01
| Decision | Options | Lean |
|---|---|---|
| Hostnames | e.g. activity.ops.… / temporal.ops.… vs path-based single host |
Prefer two hostnames or one host with path prefixes |
| Auth edge | oauth2-proxy vs nginx auth_request vs key-cape native | Prefer oauth2-proxy or fleet-standard edge if one already exists |
| Mutation identity | SSO email/sub as principal; drop shared token for normal use | Shared token = break-glass only |
| Temporal UI auth | Same edge in front of actcore-temporal-ui |
Yes for MVP |
| Who may access | Explicit Keycloak group (e.g. activity-core-operators) |
Least privilege |
Tasks
Task: Access policy brief and hostnames
id: ACTIVITY-WP-0025-T01
status: done
priority: high
state_hub_task_id: "44899e72-0481-5cd2-baa9-b96a547e4cd2"
- Document intended audience (founders / operators), MFA expectation, and allowed IdP groups.
- Propose hostnames + TLS cert path (cluster issuer / railiance DNS).
- Align with railiance-platform / key-cape conventions; open coordination messages if host or client registration is external.
- Update SCOPE G10 with the chosen posture (UI SSO vs full public API).
Done when: short design note in docs/ or this workplan is approved;
hostnames and IdP group named.
Task: Inventory fleet SSO ingress patterns
id: ACTIVITY-WP-0025-T02
status: done
priority: high
state_hub_task_id: "b48ff4e4-bf85-54f5-a107-aa247e9fc625"
- Find an existing oauth2-proxy / OIDC ingress pattern on railiance01 or coulombcore (railiance-platform, state-hub dashboard, etc.).
- Prefer reuse over inventing a new edge stack.
- Record the pattern (manifest paths, secrets ownership, callback URLs).
Done when: “pattern to copy” is written with repo/file pointers.
Task: Ingress + TLS for ops API (read path)
id: ACTIVITY-WP-0025-T03
status: done
priority: high
state_hub_task_id: "58c8e105-e73a-5417-b8b9-d0dc64b20194"
- Add Ingress (or Gateway) for
actcore-apiserving at least/opsand/ops/ui(and health for probes as needed). - Terminate TLS with the platform cert strategy.
- Gate with SSO edge; unauthenticated browser hits redirect to login.
- Keep ClusterIP service; no LoadBalancer unless required.
Done when: authorized browser reaches /ops/ui over HTTPS without
port-forward; unauthorized gets login or 403.
Task: Ingress + SSO for Temporal Web UI
id: ACTIVITY-WP-0025-T04
status: done
priority: high
state_hub_task_id: "99f56e9a-6a5a-5fe9-a4c4-4a497a5c256e"
- Expose
actcore-temporal-uibehind the same auth edge (hostname or path). - Ensure Temporal UI env (
TEMPORAL_ADDRESS, public URL / CORS if required) works behind the external hostname. - Ops console
ACTIVITY_CORE_TEMPORAL_UI_URLpoints at the public SSO URL.
Done when: Temporal UI opens from the ops nav link without a second port-forward, under SSO.
Task: Map SSO identity into ops mutations
id: ACTIVITY-WP-0025-T05
status: done
priority: high
state_hub_task_id: "7d948dc6-374e-59ac-a6c9-e7fa6e8a128b"
- Accept trusted identity from the auth edge (e.g.
X-Forwarded-User/X-Auth-Request-Email— exact headers depend on T02 pattern). - Prefer SSO principal over shared
ACTIVITY_CORE_OPERATOR_TOKENfor mutations when present. - Audit log records SSO subject (never tokens).
- Optional break-glass: keep operator token for emergency; document custody.
Done when: Run now / pause audit shows human identity from SSO; shared token not required for normal operator use.
Task: Keycloak client and group membership
id: ACTIVITY-WP-0025-T06
status: done
priority: medium
state_hub_task_id: "a8354b03-2e43-5fe1-8d40-4a39502d9090"
- Register OIDC client(s) via key-cape / netkingdom process (not ad-hoc).
- Create/bind operator group; grant only named operators.
- Document how to add/remove operators.
Done when: login works for a test operator and fails for an unprivileged account.
Disposition (2026-07-22): No new KeyCape/Keycloak OIDC client — fleet path
is Traefik ForwardAuth → Authelia. Least-privilege group work implemented in
net-kingdom NK-WP-0021 (finished): LLDAP activity-core-operators,
Authelia domain allow/deny for both public hosts, membership runbook
sso-mfa/k8s/lldap/OPERATOR-GROUPS.md. Intakes NK-IN-0001/0002 closed
promoted_to: NK-WP-0021.
Task: Docs and runbook cutover
id: ACTIVITY-WP-0025-T07
status: done
priority: medium
state_hub_task_id: "4cfe1761-9bc4-58d1-bd0b-b0112230344a"
- Runbook: primary access = SSO URLs; port-forward = break-glass only.
- k8s README: Ingress manifests and secret ownership.
- Credential routing notes: key-cape for login; no secrets in git.
Done when: an operator can follow runbook without kubectl for daily use.
Task: Verify and harden
id: ACTIVITY-WP-0025-T08
status: done
priority: medium
state_hub_task_id: "a3bddd50-deec-5e72-9311-02694d0fc2dc"
- Confirm ClusterIP APIs without Ingress path remain not publicly reachable.
- Confirm mutations without SSO (and without break-glass token) fail closed.
- Smoke: inventory, status, Run now dry path, Temporal UI namespaces list.
- Optional: network policies locking ingress → services.
Done when: checklist signed off in workplan closeout notes.
Success criteria
- HTTPS SSO URLs for ops console and Temporal UI (no port-forward required)
- Only authorized IdP group can access (
activity-core-operatorsvia NK-WP-0021) - Ops mutations attribute to SSO identity; audits show principal (
sso:<user>) - Ops nav Temporal link points at SSO Temporal URL
- Runbook documents SSO primary + break-glass port-forward
- SCOPE G10 updated to reflect UI SSO posture
Dependencies / coordination
- key-cape / Keycloak: OIDC clients, groups (
warden route show key-cape-oidc-login) - railiance-platform: DNS, cert-manager, ingress controller norms
- ACTIVITY-WP-0024: ops console exists; this WP unlocks production access
Implementation order
- T01–T02 design + pattern reuse
- T06 IdP client (can parallel with T03)
- T03 ops Ingress + SSO
- T04 Temporal UI Ingress + env URL
- T05 mutation identity
- T07–T08 docs and verify
References
docs/runbook.md— operator console + dual port-forward (pre-SSO)src/activity_core/ops_api.py—ACTIVITY_CORE_TEMPORAL_UI_URLk8s/railiance/—actcore-api,actcore-temporal-uiClusterIP services- ACTIVITY-WP-0024 operator automation console
Progress 2026-07-22
| Task | Status | Notes |
|---|---|---|
| T01 | done | Hosts: activity.coulomb.social + temporal.coulomb.social; docs/ops-sso-access.md |
| T02 | done | Reuse Authelia + Traefik ForwardAuth (sso ns); not oauth2-proxy |
| T03 | done | Ingress + cert Ready; unauth → 302 auth.coulomb.social |
| T04 | done | Temporal Ingress + cert Ready; ACTIVITY_CORE_TEMPORAL_UI_URL + CORS set |
| T05 | done | SSO Remote-User preferred; token break-glass; tests + redeploy |
| T06 | done | NK-WP-0021 finished: LLDAP group + Authelia domain rules live |
| T07 | done | Runbook + k8s README + design doc: SSO primary, port-forward break-glass |
| T08 | done | See verification checklist below |
Verification checklist (T08) — 2026-07-22
| Check | Result |
|---|---|
| DNS A → 92.205.62.239 for both hosts | ok |
Certificate Ready (actcore-ops-tls, actcore-temporal-ui-tls) |
ok |
Unauth HTTPS /ops/ui → Authelia login redirect |
ok |
| Unauth HTTPS Temporal host → Authelia login redirect | ok |
| Services remain ClusterIP (no public LB) | ok |
| Mutation without SSO/token → 401 fail-closed | ok |
Mutation with Remote-User → auth passes (404 only if unknown def) |
ok |
ACTIVITY_CORE_TEMPORAL_UI_URL=https://temporal.coulomb.social |
ok |
TEMPORAL_CORS_ORIGINS includes public Temporal host |
ok |
Unit tests test_ops_auth + test_ops_console_api |
18 passed |
Image activity-core:railiance01-prod rebuilt, imported, api/worker/router rolled |
ok |
| NetworkPolicy ingress→services | skipped (optional; ClusterIP + Authelia sufficient for MVP) |
| Full browser login + Run now under real MFA session | operator smoke (credentials not in agent) |
Closeout 2026-07-22
All tasks done. Workplan finished.
| Cross-repo | Result |
|---|---|
| NK-IN-0001 / NK-IN-0002 | closed, promoted_to: NK-WP-0021 |
| NK-WP-0021 | finished (LLDAP group, Authelia rules, OPERATOR-GROUPS.md) |
| Authelia rules | group:activity-core-operators allow + domain deny for both hosts |
| Membership | net-kingdom/sso-mfa/k8s/lldap/OPERATOR-GROUPS.md + manage-group-members.sh |
Operator grant: add users to activity-core-operators per net-kingdom runbook
before first SSO login to the public UIs.
activity-core ownership remaining (ops, not open WP work): break-glass
port-forward + ACTIVITY_CORE_OPERATOR_TOKEN; keep Ingress/middleware in
k8s/railiance/30–32.