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
12 KiB
| id | type | title | domain | repo | status | owner | topic_slug | priority | created | updated | origin | origin_ref | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| NK-WP-0021 | workplan | Activity-core ops/Temporal UI least-privilege SSO | infotech | net-kingdom | finished | codex | netkingdom | high | 2026-07-22 | 2026-07-22 | intake:019f88ff-c5f1-7433-9d13-4ffe37dfef00+019f88ff-cc9e-76b4-8ce3-eb354aabefe7 | ACTIVITY-WP-0025-T06 | eba95b9e-372c-5efd-b69a-870f11582149 |
NK-WP-0021 — Activity-core ops/Temporal UI least-privilege SSO
Goal
Close the residual least-privilege gap for public activity-core operator UIs
by owning the directory group and Authelia access rules in this repo
(SSO/MFA stack). Browser access to activity.coulomb.social and
temporal.coulomb.social should require membership in
activity-core-operators, not merely “any authenticated Authelia user”.
Origin and review
| Intake | Hub UUID | Title | Decision |
|---|---|---|---|
| NK-IN-0001 | 019f88ff-c5f1-7433-9d13-4ffe37dfef00 |
LLDAP group + membership runbook | Accept → T01–T02 |
| NK-IN-0002 | 019f88ff-cc9e-76b4-8ce3-eb354aabefe7 |
Authelia domain rules | Accept → T03–T04 |
Cross-repo handoff from activity-core ACTIVITY-WP-0025-T06 (residual wait).
Source design: activity-core/docs/ops-sso-access.md. File intakes:
docs/intakes/activity-core-ops-sso-operators.md.
Scope / intent fit
| Lens | Assessment |
|---|---|
| SCOPE in | SSO/MFA platform (Authelia + LLDAP), identity as control plane, least-privilege access for platform operators |
| SCOPE out | activity-core Ingress/middleware (already live), KeyCape OIDC clients, Temporal/API application code, railiance runtime |
| INTENT | Principle 2 (identity is the control plane) and 6 (minimize exposure / least privilege from the start). Progressive capability: tighten MVP “any Authelia user” to named operator group without changing auth edge pattern |
| Repo boundary | net-kingdom owns LLDAP groups + Authelia access_control; activity-core owns ForwardAuth middleware, break-glass token, and app authorization |
Refinements vs raw intakes
- Single workplan — both intakes share one deploy surface (LLDAP → Authelia) and a hard dependency (group must exist before domain rules). One plan avoids split status and double-apply risk.
- No KeyCape / Keycloak OIDC clients — explicit non-goal. Path is Traefik ForwardAuth → Authelia
/api/verifyonly (already shipped in activity-core). - MFA posture unchanged — keep Authelia
one_factor; MFA remains KeyCape + privacyIDEA. Do not set Autheliatwo_factorfor these hosts. - App-scoped operator group pattern —
activity-core-operatorsextends the existingnet-kingdom-users/net-kingdom-adminsseed style for a consumer app, not platform-wide admin. Document as the template for future app operator groups. - Membership is operator policy — scripts create the empty group and document add/remove; named founder assignment is a live operator step (not committed identity).
- Verification matrix — unauthenticated → login; authenticated non-member → deny; member → pass; break-glass remains independent of Authelia groups (activity-core runbook).
- Default policy stays open for other hosts — only the two public activity-core hosts get restrictive domain rules; do not change global
default_policy: one_factorfor the rest of the fleet without a separate decision.
Context (current state)
- Authelia SSO edge for both hosts is live (activity-core manifests 30–32).
- Authelia
access_controltoday:default_policy: one_factor, no domain rules (sso-mfa/k8s/authelia/configmap.yaml). - LLDAP bootstrap seeds only
net-kingdom-usersandnet-kingdom-admins(sso-mfa/k8s/lldap/bootstrap-users.sh). - Any authenticated Authelia user can currently open both public UIs.
In scope
- Create LLDAP group
activity-core-operators(bootstrap script + README). - Document how operators add/remove humans (WebUI + scripted GraphQL path).
- Authelia domain rules for
activity.coulomb.socialandtemporal.coulomb.socialrequiring that group. - Apply/reload Authelia; verify member / non-member / unauthenticated paths.
- Close intakes as promoted; notify activity-core so T06 can finish.
Out of scope
- New OIDC clients in KeyCape or Keycloak for these hosts.
- Changing activity-core Ingress, middleware, or ops API authorization.
- Platform-root / break-glass LLDAP accounts.
- Enterprise federation / SAML (NK-WP-0011) or tutorials backlog (NK-WP-0009).
- Automatically granting named users without operator confirmation.
Acceptance (workplan-level)
- Group
activity-core-operatorsexists in LLDAP and is re-seeded by bootstrap. - Membership procedure is documented under
sso-mfadocs (and README pointers). - Authelia denies non-members and allows members on both hosts after login
(
authelia access-control check-policyon live config 2026-07-22). - Unauthenticated users still hit the Authelia login portal (not open backends)
(live 302 →
auth.coulomb.socialfor both hosts). - activity-core break-glass remains independent of Authelia group membership (unchanged; documented in OPERATOR-GROUPS.md).
- Intakes NK-IN-0001 / NK-IN-0002 closed as promoted to this workplan.
Live verification evidence (2026-07-22)
| Check | Result |
|---|---|
LLDAP group activity-core-operators |
id=7; seeded live; bootstrap script updated |
| Initial member | platform-root (conscious bootstrap grant) |
Unauth activity / temporal |
HTTP 302 → Authelia login |
check-policy member + group |
rule #1 one_factor (activity); rule #3 temporal |
check-policy auth non-member |
rule #2 / #4 deny |
check-policy other host (kc) |
default one_factor (fleet unaffected) |
| Authelia rollout | v4.38.19 Startup complete with rules loaded |
Tasks
T01 — LLDAP group activity-core-operators in bootstrap
id: NK-WP-0021-T01
status: done
priority: high
state_hub_task_id: "4b4a93fc-ad63-59ae-9658-ad59c2a2bfbb"
Extend LLDAP group seeding so the operator group is created the same way as platform groups:
- Update
sso-mfa/k8s/lldap/bootstrap-users.shto createactivity-core-operators(idempotentcreate_group). - Update
sso-mfa/k8s/lldap/README.mdand post-deploy hints increate-secrets.shto list the third group and its purpose (activity-core ops console + Temporal UI only — not platform admin). - Optional small helper or flag on
create-user.shonly if it stays simple (e.g. documented GraphQL one-liner is enough for v1).
Done when: re-running bootstrap is safe/idempotent and the group exists (or will exist on next bootstrap) with clear docs.
Promotes: NK-IN-0001 (group half).
T02 — Operator membership runbook
id: NK-WP-0021-T02
status: done
priority: high
state_hub_task_id: "032d6d66-20bc-52e8-8490-aa3a77275ad0"
Document least-privilege membership for founders/operators only:
- WebUI path:
https://lldap.coulomb.social→ add/remove group members. - Scripted path: GraphQL
addUserToGroup/removeUserFromGroupusing the same auth pattern asbootstrap-users.sh/break-glass.sh(no secrets in git or workplan). - Explicit: do not auto-add all
net-kingdom-admins; membership is a conscious grant for activity-core ops. - Point activity-core operators at this runbook from a short note in
docs/intakes/activity-core-ops-sso-operators.md(or a thinsso-mfa/k8s/lldap/OPERATOR-GROUPS.mdif the README grows too large).
Done when: an operator can add and remove a test user without reading activity-core internals.
Promotes: NK-IN-0001 (runbook half).
T03 — Authelia domain rules for activity + temporal hosts
id: NK-WP-0021-T03
status: done
priority: high
state_hub_task_id: "17543b91-39aa-5b03-971c-b696f8f36489"
Implement access control in the live Authelia ConfigMap:
- File:
sso-mfa/k8s/authelia/configmap.yaml - Keep
default_policy: one_factorfor the rest of the fleet. - Add
rules(or equivalent for the deployed Authelia version) for:activity.coulomb.socialtemporal.coulomb.social
- Require subject group
activity-core-operatorswith policyone_factor(Authelia subject syntax:subject: "group:activity-core-operators"— confirm against the running Authelia major version before apply). - Comment in-file: MFA remains KeyCape/privacyIDEA; do not use Authelia
two_factorhere. - Update
sso-mfa/k8s/authelia/README.mdwith the new domain-rule pattern so the next app can copy it.
Depends on: T01 (group name must resolve from LLDAP via Authelia’s LDAP backend). Rules may be committed before the group exists, but live apply of enforcement should wait until the group is present.
Done when: config is reviewed and ready to apply; no accidental lockdown of unrelated hosts (auth portal, KeyCape, LLDAP admin, etc.).
Promotes: NK-IN-0002 (config half).
T04 — Live apply and verification matrix
id: NK-WP-0021-T04
status: done
priority: high
state_hub_task_id: "0bbdf44c-283a-57be-8bd4-4b8b42b39803"
Roll out and prove least privilege on the live railiance01 SSO stack:
-
Ensure group exists (run bootstrap if needed) and assign one known operator (operator-confirmed username).
-
Apply Authelia ConfigMap; restart/reload Authelia in namespace
sso. -
Verification matrix (record non-secret results in progress notes):
Case Expected Unauthenticated → both hosts Redirect to Authelia login Authenticated non-member Denied (403 / Authelia unauthorized) Authenticated member Pass-through to ops UI and Temporal UI Break-glass port-forward + operator token Still works per activity-core runbook -
If verification fails, roll back ConfigMap to previous
access_controland re-open the blocking task — do not leave partial rules that lock out all operators without a recovery path.
Done when: matrix passes for both hosts; recovery path still documented.
Promotes: NK-IN-0002 (verify half).
T05 — Close loop with intakes and activity-core
id: NK-WP-0021-T05
status: done
priority: medium
state_hub_task_id: "327c4a7c-94a6-50cd-8f36-7a452428a70d"
- Keep intake source file statuses in sync (
promoted/ closed) after hub close (done at workplan registration for the intake close step; re-check if reopened). - Message
activity-core(State Hub) that NK-WP-0021 covers residual T06 and can move T06 to done once T04 verification lands. - Brief progress event on finish.
Done when: activity-core residual task has a clear unblock path and this workplan’s hub registration is consistent.
2026-07-22: Intakes closed promoted; activity-core notified at registration and again on finish. Residual ACTIVITY-WP-0025-T06 unblocked.
Implementation notes
Suggested Authelia rule shape (confirm version)
access_control:
default_policy: one_factor
rules:
- domain: activity.coulomb.social
subject: "group:activity-core-operators"
policy: one_factor
- domain: temporal.coulomb.social
subject: "group:activity-core-operators"
policy: one_factor
If Authelia requires an explicit deny for non-matching subjects on those domains, use the version’s documented pattern (subject rules are exclusive when matched; default_policy must not re-open the host). Prefer a deny-by-default domain rule only if the running version needs it — verify in staging or with a short maintenance window.
Non-goals reminder
Do not invent KeyCape OIDC clients, oauth2-proxy, or Keycloak realm
clients for this path. activity-core already uses ForwardAuth headers
(Remote-User, Remote-Groups, …).
)