net-kingdom/workplans/NK-WP-0021-activity-core-ops-sso-operators.md
codex 82452d655f
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:14:28 +02:00

12 KiB
Raw Permalink Blame History

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 → T01T02
NK-IN-0002 019f88ff-cc9e-76b4-8ce3-eb354aabefe7 Authelia domain rules Accept → T03T04

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

  1. 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.
  2. No KeyCape / Keycloak OIDC clients — explicit non-goal. Path is Traefik ForwardAuth → Authelia /api/verify only (already shipped in activity-core).
  3. MFA posture unchanged — keep Authelia one_factor; MFA remains KeyCape + privacyIDEA. Do not set Authelia two_factor for these hosts.
  4. App-scoped operator group patternactivity-core-operators extends the existing net-kingdom-users / net-kingdom-admins seed style for a consumer app, not platform-wide admin. Document as the template for future app operator groups.
  5. Membership is operator policy — scripts create the empty group and document add/remove; named founder assignment is a live operator step (not committed identity).
  6. Verification matrix — unauthenticated → login; authenticated non-member → deny; member → pass; break-glass remains independent of Authelia groups (activity-core runbook).
  7. 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_factor for the rest of the fleet without a separate decision.

Context (current state)

  • Authelia SSO edge for both hosts is live (activity-core manifests 3032).
  • Authelia access_control today: default_policy: one_factor, no domain rules (sso-mfa/k8s/authelia/configmap.yaml).
  • LLDAP bootstrap seeds only net-kingdom-users and net-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.social and temporal.coulomb.social requiring 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-operators exists in LLDAP and is re-seeded by bootstrap.
  • Membership procedure is documented under sso-mfa docs (and README pointers).
  • Authelia denies non-members and allows members on both hosts after login (authelia access-control check-policy on live config 2026-07-22).
  • Unauthenticated users still hit the Authelia login portal (not open backends) (live 302 → auth.coulomb.social for 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.sh to create activity-core-operators (idempotent create_group).
  • Update sso-mfa/k8s/lldap/README.md and post-deploy hints in create-secrets.sh to 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.sh only 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 / removeUserFromGroup using the same auth pattern as bootstrap-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 thin sso-mfa/k8s/lldap/OPERATOR-GROUPS.md if 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_factor for the rest of the fleet.
  • Add rules (or equivalent for the deployed Authelia version) for:
    • activity.coulomb.social
    • temporal.coulomb.social
  • Require subject group activity-core-operators with policy one_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_factor here.
  • Update sso-mfa/k8s/authelia/README.md with the new domain-rule pattern so the next app can copy it.

Depends on: T01 (group name must resolve from LLDAP via Authelias 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:

  1. Ensure group exists (run bootstrap if needed) and assign one known operator (operator-confirmed username).

  2. Apply Authelia ConfigMap; restart/reload Authelia in namespace sso.

  3. 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
  4. If verification fails, roll back ConfigMap to previous access_control and 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 workplans 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 versions 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, …). )