activity-core/workplans/ACTIVITY-WP-0025-ops-ui-sso-access.md
codex 1fa9dc18ab
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix(workplans): adopt ADR-007 derived identifiers for unregistered records
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
2026-08-25 20:05:21 +02:00

11 KiB
Raw Blame History

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:

  1. Open HTTPS URLs in a browser (no kubectl port-forward)
  2. Authenticate with org SSO (MFA per IAM profile where required)
  3. Use ops console mutations under their identity (not only a shared token)
  4. Open Temporal UI under the same access policy

Non-goals

  • Public anonymous access to /ops or Temporal UI
  • Replacing Temporals 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"
  1. Document intended audience (founders / operators), MFA expectation, and allowed IdP groups.
  2. Propose hostnames + TLS cert path (cluster issuer / railiance DNS).
  3. Align with railiance-platform / key-cape conventions; open coordination messages if host or client registration is external.
  4. 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"
  1. Find an existing oauth2-proxy / OIDC ingress pattern on railiance01 or coulombcore (railiance-platform, state-hub dashboard, etc.).
  2. Prefer reuse over inventing a new edge stack.
  3. 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"
  1. Add Ingress (or Gateway) for actcore-api serving at least /ops and /ops/ui (and health for probes as needed).
  2. Terminate TLS with the platform cert strategy.
  3. Gate with SSO edge; unauthenticated browser hits redirect to login.
  4. 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"
  1. Expose actcore-temporal-ui behind the same auth edge (hostname or path).
  2. Ensure Temporal UI env (TEMPORAL_ADDRESS, public URL / CORS if required) works behind the external hostname.
  3. Ops console ACTIVITY_CORE_TEMPORAL_UI_URL points 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"
  1. Accept trusted identity from the auth edge (e.g. X-Forwarded-User / X-Auth-Request-Email — exact headers depend on T02 pattern).
  2. Prefer SSO principal over shared ACTIVITY_CORE_OPERATOR_TOKEN for mutations when present.
  3. Audit log records SSO subject (never tokens).
  4. 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"
  1. Register OIDC client(s) via key-cape / netkingdom process (not ad-hoc).
  2. Create/bind operator group; grant only named operators.
  3. 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"
  1. Runbook: primary access = SSO URLs; port-forward = break-glass only.
  2. k8s README: Ingress manifests and secret ownership.
  3. 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"
  1. Confirm ClusterIP APIs without Ingress path remain not publicly reachable.
  2. Confirm mutations without SSO (and without break-glass token) fail closed.
  3. Smoke: inventory, status, Run now dry path, Temporal UI namespaces list.
  4. 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-operators via 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

  1. T01T02 design + pattern reuse
  2. T06 IdP client (can parallel with T03)
  3. T03 ops Ingress + SSO
  4. T04 Temporal UI Ingress + env URL
  5. T05 mutation identity
  6. T07T08 docs and verify

References

  • docs/runbook.md — operator console + dual port-forward (pre-SSO)
  • src/activity_core/ops_api.pyACTIVITY_CORE_TEMPORAL_UI_URL
  • k8s/railiance/actcore-api, actcore-temporal-ui ClusterIP 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/3032.