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
286 lines
12 KiB
Markdown
286 lines
12 KiB
Markdown
---
|
||
id: NK-WP-0021
|
||
type: workplan
|
||
title: "Activity-core ops/Temporal UI least-privilege SSO"
|
||
domain: infotech
|
||
repo: net-kingdom
|
||
status: finished
|
||
owner: codex
|
||
topic_slug: netkingdom
|
||
priority: high
|
||
created: "2026-07-22"
|
||
updated: "2026-07-22"
|
||
origin: "intake:019f88ff-c5f1-7433-9d13-4ffe37dfef00+019f88ff-cc9e-76b4-8ce3-eb354aabefe7"
|
||
origin_ref: "ACTIVITY-WP-0025-T06"
|
||
state_hub_workstream_id: "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
|
||
|
||
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 pattern** — `activity-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 30–32).
|
||
- 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)
|
||
|
||
- [x] Group `activity-core-operators` exists in LLDAP and is re-seeded by bootstrap.
|
||
- [x] Membership procedure is documented under `sso-mfa` docs (and README pointers).
|
||
- [x] Authelia denies non-members and allows members on both hosts after login
|
||
(`authelia access-control check-policy` on live config 2026-07-22).
|
||
- [x] Unauthenticated users still hit the Authelia login portal (not open backends)
|
||
(live 302 → `auth.coulomb.social` for both hosts).
|
||
- [x] activity-core break-glass remains independent of Authelia group membership
|
||
(unchanged; documented in OPERATOR-GROUPS.md).
|
||
- [x] 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
|
||
|
||
```task
|
||
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
|
||
|
||
```task
|
||
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
|
||
|
||
```task
|
||
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 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
|
||
|
||
```task
|
||
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
|
||
|
||
```task
|
||
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)
|
||
|
||
```yaml
|
||
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`, …).
|
||
)
|