From 6ed0061962e3a8e5951871307f5f8f338b4515e9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 1 Mar 2026 23:49:06 +0100 Subject: [PATCH] feat(local-identity): add NK-WP-0002 workplan and LocalIdentity.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows resolved decisions D4 and D5 (2026-03-01, Tegwick): D4 — ESO chosen as secret injection strategy. NK-WP-0001 T01 Phase 0b updated to specify ESO; T01 done-criteria updated to require a working ESO test injection. D5 — Local Identity implemented in-repo (not a separate repo). Four deliverables: - docs/LocalIdentity.md: capability overview, design principles, user schema, OIDC provider description, risk mitigations, scope boundaries - workplans/NK-WP-0002-local-identity.md: four-stage implementation plan (core file store, bootstrap integration, minimal OIDC, security hardening) with State Hub task IDs - NK-WP-0001 updated: D2/D4/D5 rows resolved, T07 bootstrap section now references NK-WP-0002 and documents the export→Keycloak migration path, Open Questions condensed to two remaining artefacts Co-Authored-By: Claude Sonnet 4.6 --- docs/LocalIdentity.md | 155 +++++++++++++++ workplans/NK-WP-0001-sso-mfa-platform.md | 49 ++--- workplans/NK-WP-0002-local-identity.md | 236 +++++++++++++++++++++++ 3 files changed, 416 insertions(+), 24 deletions(-) create mode 100644 docs/LocalIdentity.md create mode 100644 workplans/NK-WP-0002-local-identity.md diff --git a/docs/LocalIdentity.md b/docs/LocalIdentity.md new file mode 100644 index 0000000..90e8fa6 --- /dev/null +++ b/docs/LocalIdentity.md @@ -0,0 +1,155 @@ +# Local Identity + +Local Identity is a zero-dependency, file-based user management capability +for net-kingdom bootstrap environments — systems that do not yet have (or do +not need) a running Keycloak instance. + +## Why it exists + +In net-kingdom, Keycloak is the production identity provider. But Keycloak +requires a running Kubernetes cluster, a database, and a configured realm +before it can authenticate anyone. This creates a bootstrapping paradox: + +> You need identity to set up infrastructure, but the infrastructure provides +> identity. + +Local Identity breaks this cycle. An operator with only a Linux home directory +can establish their identity, generate deterministic test users, and run +dev/test applications with OIDC authentication — before any service is +deployed. + +## Design principles + +1. **Zero dependencies** — only the Linux filesystem; no Docker, no K8s, no + running services required. +2. **Derived identity** — the primary user is derived from `$USER`, + `/etc/passwd` (GECOS), and a configured email address. No manual setup + required for the basic case. +3. **Deterministic test users** — two test users are auto-generated from the + primary user at `init` time using `N` and `+testN` suffixes: + + | Field | Primary | Test 1 | Test 2 | + |----------|-------------|---------------------|---------------------| + | username | `$USER` | `${USER}1` | `${USER}2` | + | fullname | GECOS field | `+test1` | `+test2` | + | email | configured | `+test1@…` | `+test2@…` | + + Email aliases follow the Gmail `+xxx` convention so test emails route to + the operator's inbox without extra accounts. + +4. **Hard isolation** — test users carry `environment: local`; production + connectors reject this flag by default. Test users cannot authenticate in + production without an explicit override. +5. **Minimal OIDC** — a lightweight native OIDC provider backed by the file + store, for apps that require OIDC in dev/test without a running Keycloak. + Tokens carry `iss: local-identity`; production systems are configured to + reject this issuer. +6. **Secure by default** — `~/.local-identity/` is created with mode `700`; + individual user files with mode `600`; the tool validates permissions on + every startup and refuses to run if the store is world-readable. + +## What it is not + +- **Not a production identity provider.** Local Identity is never exposed to + the internet. It has no MFA. It is not hardened for public traffic. +- **Not a replacement for Keycloak.** Once a cluster is operational, Keycloak + is the IdP. Local Identity provides an on-ramp, not an alternative. +- **Not multi-user.** Local Identity is single-operator: one primary user + derived from the Linux session, plus generated test users. +- **Not an LDAP/AD/Entra bridge.** Enterprise federation is handled by + Keycloak. See EP-NK-001 in the State Hub. +- **No MFA.** Second factors are out of scope; this is intentionally minimal. + +## User schema + +Users are stored as YAML files under `~/.local-identity/users/`: + +```yaml +# ~/.local-identity/users/tegwick.yaml +schema_version: "1" +username: tegwick +fullname: "Bernd Worsch" +email: "bernd.worsch@gmail.com" +environment: local # never "production" for local-identity users +generated: false # true for auto-generated test users +production_identity: # optional: maps this user to a production identity + username: tegwick + realm: net-kingdom +``` + +Test users are generated at `init` time and stored alongside: + +```yaml +# ~/.local-identity/users/tegwick1.yaml +schema_version: "1" +username: tegwick1 +fullname: "Bernd Worsch+test1" +email: "bernd.worsch+test1@gmail.com" +environment: local +generated: true +source_user: tegwick +production_identity: # optional: can map to a test/staging account + username: tegwick-test1 + realm: net-kingdom +``` + +## Sandbox → production mapping + +Each user file can optionally carry a `production_identity` block. When an +entity owned by a local-identity user needs to be transferred to a production +environment (e.g. a resource created during local development), the mapping +provides the correct production user ID. + +`local-identity export ` produces a Keycloak-compatible user JSON that +respects this mapping. The schema is validated against the Keycloak user +representation to prevent silent drift. + +## CLI reference + +``` +local-identity init # derive primary user, generate test users +local-identity list # list all users in the store +local-identity show # display user file +local-identity export # emit Keycloak-compatible JSON +local-identity security-check # validate filesystem permissions and config +``` + +## OIDC provider (Stage 3) + +When running `local-identity serve`, a minimal OIDC Authorization Code flow +server starts on localhost. It supports: + +- `GET /.well-known/openid-configuration` — discovery document +- Authorization endpoint, token endpoint, userinfo endpoint +- JWT tokens with `iss: local-identity` (hard-coded; production systems + reject this issuer by default) +- Auto-generated self-signed TLS certificate + +This allows dev/test applications to use standard OIDC libraries against +Local Identity without any Keycloak dependency. + +**Security note:** the OIDC server binds to `127.0.0.1` only. Never expose +it on a public interface. + +## Risks and mitigations + +| Risk | Mitigation | +|------|------------| +| World-readable credential files | `~/.local-identity/` mode `700`; startup check fails loudly | +| Test users leaking into production | `environment: local` flag; production connectors reject by default | +| Local Identity tokens accepted in production | `iss: local-identity`; configure production Keycloak to reject this issuer | +| File schema drifting from Keycloak model | `export` command validates against Keycloak representation; schema is versioned | +| Bootstrap store becoming a long-lived crutch | Explicit scope limit: once Keycloak is operational, migrate and stop using Local Identity | + +## Relationship to the SSO platform + +Local Identity is a complementary workstream to the SSO & MFA Platform +(NK-WP-0001). The SSO platform provides production-grade identity; Local +Identity provides the bootstrap path that allows the SSO platform itself to +be set up and tested. + +When the Keycloak realm (NK-WP-0001 T06) is operational, primary and test +users can be exported from Local Identity into Keycloak using +`local-identity export` and the Keycloak admin API. + +Implementation: see [NK-WP-0002](../workplans/NK-WP-0002-local-identity.md). diff --git a/workplans/NK-WP-0001-sso-mfa-platform.md b/workplans/NK-WP-0001-sso-mfa-platform.md index 5e1a09f..4ad2ea3 100644 --- a/workplans/NK-WP-0001-sso-mfa-platform.md +++ b/workplans/NK-WP-0001-sso-mfa-platform.md @@ -45,10 +45,10 @@ Two are pending and require further investigation (see Open Questions). | ID | Decision | Status | Outcome / Notes | |----|----------|--------|-----------------| | D1 | Vault backend | **Resolved** | KeePassXC pre-cluster → HashiCorp Vault in-cluster. | -| D2 | Identity source of truth | **Resolved** | Hybrid: Keycloak-internal + LDAP/Entra for enterprise tier. File-based bootstrap user store deferred pending D5. | +| D2 | Identity source of truth | **Resolved** | Hybrid: Keycloak-internal + LDAP/Entra for enterprise tier. File-based bootstrap user store → Local Identity (NK-WP-0002). | | D3 | GitOps tooling | **Resolved** | Plain Helm first, upgrade to Flux when warranted. AI-first philosophy (TDD, API-first, MCP, CLI; UI separate repos) — ecosystem ADR requested from custodian. | -| D4 | Secret injection: ESO vs Vault Agent Injector | **Pending** | Gates T01 Phase 0b. Tegwick to investigate. | -| D5 | File-based bootstrap user store: separate repo vs defer vs existing tool | **Pending** | Full SWOT in State Hub. Preliminary recommendation: evaluate Keycloak Docker Compose first. | +| D4 | Secret injection: ESO vs Vault Agent Injector | **Resolved** | **ESO.** GitOps-aligned; standard K8s Secrets consumable by plain Helm. Monitor dynamic-secret gaps; revisit if needed. | +| D5 | File-based bootstrap user store | **Resolved** | **Implement in-repo as `local-identity`.** Staged workplan: NK-WP-0002. See `docs/LocalIdentity.md`. | ## Architecture @@ -118,15 +118,16 @@ manifests). Store offsite. **Phase 0b — HashiCorp Vault in-cluster (after T02, once K3s is running):** Deploy HashiCorp Vault in the cluster (Helm chart). Migrate secrets from -KeePassXC into Vault. Enable K8s encryption-at-rest. Choose and implement -secret injection strategy: External Secrets Operator + Vault backend, or -Vault Agent Injector (ESO preferred for GitOps alignment). KeePassXC -remains the source of truth for dev/test/sandbox systems that do not connect -to the cluster Vault. +KeePassXC into Vault. Enable K8s encryption-at-rest. Deploy External Secrets +Operator (ESO) — **decided D4**: ESO reconciles Vault secrets into standard +K8s Secrets, compatible with plain Helm charts without Vault-specific +annotations. KeePassXC remains the source of truth for dev/test/sandbox +systems that do not connect to the cluster Vault. **Done when:** KeePassXC created and all secrets generated (0a). Vault -deployed in-cluster, secrets migrated, injection strategy operational (0b). -Encrypted ops bundle exported and stored offsite. +deployed in-cluster, secrets migrated, ESO operational and injecting secrets +into at least one test workload (0b). Encrypted ops bundle exported and +stored offsite. --- @@ -326,14 +327,13 @@ Configure auditing and log shipping: privacyIDEA audit logs + Keycloak events → centralized logging (ELK/Loki or equivalent). Token lifecycle policies: enrollment, revocation, re-enrollment on device loss. -**Bootstrap user management (D2 extension — scope TBD):** -D2 also specifies a file-based lightweight user store for pre-Keycloak -systems (dev/test/sandbox that do not connect to the cluster). Users stored -as files in a secure subdirectory of the Linux home directory; auto-generates -two test users with `N` / `+testN` username and email suffixes. Test users -must not spill over into other systems; a mapping mechanism from sandbox -identities to production should be provided. This scope is not yet captured -in a task — see Open Questions. +**Bootstrap user management (D2 + D5 — Local Identity):** +The pre-Keycloak user store is implemented as the `local-identity` capability. +See [NK-WP-0002](NK-WP-0002-local-identity.md) and +[docs/LocalIdentity.md](../docs/LocalIdentity.md). NK-WP-0002 Stage 2 +produces Keycloak-compatible user exports (`local-identity export --all`) +that feed the realm bulk-import during T06. Once T06 is operational, +Local Identity should be explicitly migrated away from for that instance. **Done when:** policies documented and applied, self-service portal live, audit logs flowing, Keycloak resolver configured. @@ -402,9 +402,10 @@ documented and tested, HSTS and NetworkPolicies verified. See `DECISIONS.md` for the three resolved decisions (D1–D3). Two pending decisions have been raised; see State Hub for full detail. -| # | Item | State Hub artefact | Status | -|---|------|--------------------|--------| -| D4 | Secret injection: ESO vs Vault Agent Injector | Decision `aca69951` | Pending — Tegwick to investigate | -| D5 | File-based bootstrap user store | Decision `d74e2b11` (full SWOT) | Pending — evaluate Keycloak Docker Compose first | -| — | AI-first ecosystem ADR | Task `007415ef` → [repo:custodian] | Recommended; custodian to create | -| EP-NK-001 | LDAP/AD/Entra federation | Extension point `513a7644` | Open; enterprise tier | +All five decisions are now resolved. See `DECISIONS.md` for D1–D3 rationale; +State Hub decisions `aca69951` (D4) and `d74e2b11` (D5) for the full records. + +| Artefact | Item | Status | +|----------|------|--------| +| Task `007415ef` → [repo:custodian] | Create ecosystem ADR for AI-first principles (D3) | Open; custodian to action | +| EP-NK-001 (`513a7644`) | LDAP/AD/Entra federation | Open; enterprise tier | diff --git a/workplans/NK-WP-0002-local-identity.md b/workplans/NK-WP-0002-local-identity.md new file mode 100644 index 0000000..7b25245 --- /dev/null +++ b/workplans/NK-WP-0002-local-identity.md @@ -0,0 +1,236 @@ +--- +id: NK-WP-0002 +type: workplan +title: "Local Identity — Bootstrap User Store & Minimal OIDC" +domain: netkingdom +status: active +owner: worsch +topic_slug: netkingdom +state_hub_workstream_id: 7c9021b1-319c-4b4a-a8be-0642239a1893 +created: "2026-03-01" +updated: "2026-03-01" +--- + +# Local Identity — Bootstrap User Store & Minimal OIDC + +## Summary + +Implement a zero-dependency, file-based user management capability for +net-kingdom environments that do not yet have (or do not need) a running +Keycloak instance. Local Identity derives the primary user from the Linux +identity, auto-generates test users, provides a sandbox→production mapping +mechanism, and (in Stage 3) a minimal native OIDC provider for dev/test use. + +See [docs/LocalIdentity.md](../docs/LocalIdentity.md) for the full capability +description, design principles, user schema, and risk mitigations. + +## Context + +Resolved from Decision D5 (2026-03-01, Tegwick). The decision chose to +implement Local Identity in-repo (not as a separate repository) in staged +workplan form, with a clear scope boundary and explicit out-of-scope +limitations. The minimal OIDC provider is to be implemented natively to avoid +heavy dependencies, keeping the bootstrap footprint minimal. + +## Relationship to NK-WP-0001 + +Local Identity is complementary to the SSO & MFA Platform (NK-WP-0001). It +is not a blocking dependency: the SSO platform core deployment (T01–T08) does +not require Local Identity to be complete. However: + +- NK-WP-0001 T07 (user management) references Local Identity for the + pre-Keycloak bootstrap use case. +- Stage 2 of this workplan produces Keycloak-compatible user exports, which + feed the NK-WP-0001 T06 realm configuration. +- Once NK-WP-0001 is fully operational, Local Identity is no longer needed + for new instances and should be explicitly migrated away from. + +## Architecture + +``` +~/.local-identity/ +├── config.yaml # operator email, optional overrides +└── users/ + ├── .yaml # primary user (derived from Linux identity) + ├── 1.yaml # test user 1 (generated) + └── 2.yaml # test user 2 (generated) + +local-identity CLI +├── init # derive + generate users +├── list / show # read operations +├── export # Keycloak-compatible JSON +├── security-check # permissions validation +└── serve # Stage 3: minimal OIDC server (localhost only) +``` + +**Secret injection:** Local Identity does not use Vault or K8s Secrets — +it operates entirely at the filesystem level, pre-cluster. This is by design. + +## Tasks + +### T01 — Stage 1: Core file store + +```task +id: NK-WP-0002-T01 +state_hub_task_id: 656652dd-05af-4fa4-95b2-17ce029ac7bd +status: todo +priority: high +``` + +Define YAML user schema (`schema_version`, `username`, `fullname`, `email`, +`environment`, `generated`, `source_user`, `production_identity`). + +Implement: +- `local-identity init` — read `$USER`, `/etc/passwd` GECOS, prompt for + email if not in config; write primary user file; auto-generate two test + users with `N` / `+testN` suffixes +- `local-identity list` — tabular output of all users in the store +- `local-identity show ` — pretty-print user YAML + +File store: +- Create `~/.local-identity/` with mode `700` +- Create user files with mode `600` +- Refuse to overwrite existing store without `--force` + +Unit tests: +- GECOS name parsing edge cases (missing fields, non-ASCII) +- Test user derivation: username suffix, email `+testN` insertion +- Idempotency: `init` twice with `--force` produces identical output + +**Done when:** init/list/show work; files created with correct permissions; +unit tests passing. + +--- + +### T02 — Stage 2: Bootstrap integration + +```task +id: NK-WP-0002-T02 +state_hub_task_id: 5ea6e68d-7ebe-4ea7-b92e-61aac17ff04c +status: todo +priority: high +``` + +Extend user schema with optional `production_identity` block (`username`, +`realm`). Test users carry `environment: local` and `generated: true`. + +Implement: +- `local-identity export ` — emit Keycloak-compatible user JSON + (Keycloak Admin REST API representation); apply `production_identity` + mapping if present +- Schema validation: run against Keycloak user JSON schema on export; fail + with a clear diff if schema has drifted + +Bootstrap tooling integration: +- `local-identity export --all` produces a bulk import file compatible with + Keycloak's partial import endpoint +- Document the import procedure in `docs/LocalIdentity.md` + +Isolation guarantee: +- Production connectors (Keycloak, future services) must reject users with + `environment: local` — document the configuration required on the + Keycloak side (e.g. custom attribute check in authentication flow) + +**Done when:** export produces valid Keycloak JSON; schema validation +catches drift; bulk import procedure documented and tested against a local +Keycloak dev instance. + +--- + +### T03 — Stage 3: Minimal native OIDC provider + +```task +id: NK-WP-0002-T03 +state_hub_task_id: eb09d287-8e08-4c88-8bd1-6f0501ef5fc8 +status: todo +priority: medium +``` + +Implement `local-identity serve` — a minimal OIDC Authorization Code flow +server, implemented natively (no heavy OIDC library dependencies). Target: +a single binary or script that can be invoked without installing an +application framework. + +Endpoints required: +- `GET /.well-known/openid-configuration` — OIDC discovery document +- `GET /auth` — authorization endpoint (redirects with `code`) +- `POST /token` — token endpoint (exchanges `code` for JWT) +- `GET /userinfo` — userinfo endpoint + +Token requirements: +- JWT signed with a local key (generated on first `serve` invocation; + stored in `~/.local-identity/keys/`) +- Claims: `sub`, `iss: local-identity`, `aud`, `exp`, `iat`, `email`, + `name`, `preferred_username` +- `iss: local-identity` is intentionally non-routable; configure production + Keycloak to reject tokens with this issuer + +TLS: +- Auto-generate a self-signed certificate on first run; store in + `~/.local-identity/tls/` +- Bind to `127.0.0.1` only; document that external binding is explicitly + unsupported + +Scope: +- Supports `openid`, `profile`, `email` scopes +- No refresh tokens (stateless; re-auth required after expiry) +- No client secret validation (dev-mode only; all registered clients are + trusted) + +**Done when:** a standard OIDC client library can authenticate against +`local-identity serve`; discovery, auth, token, and userinfo endpoints +pass an OIDC conformance smoke test; server refuses to bind to 0.0.0.0. + +--- + +### T04 — Stage 4: Security hardening + +```task +id: NK-WP-0002-T04 +state_hub_task_id: 936de7fa-dfb4-48a2-804f-6b9bd7271a05 +status: todo +priority: medium +``` + +Permission enforcement: +- On every startup, validate `~/.local-identity/` mode `700` and all user + files mode `600`; fail loudly (exit 1 + clear error) if violated +- `local-identity security-check` command: explicit security audit with + per-check output (pass / warn / fail) + +Audit log: +- Append-only log at `~/.local-identity/audit.log`; mode `600` +- Log entries: timestamp, command, username, outcome +- For `serve`: log every authentication event (auth request, token issued, + userinfo call) + +Token hardening (for Stage 3 OIDC server): +- Configurable token TTL (default: 1 hour) +- Token revocation list stored in `~/.local-identity/revoked.json` +- `local-identity revoke-token ` command + +Documentation: +- Optional SELinux/AppArmor label guidance added to `docs/LocalIdentity.md` +- Security model section: threat model, assumptions, explicit non-guarantees + +**Done when:** security-check passes cleanly on a correct install; audit +log records all auth events; startup fails on incorrect permissions; token +expiry and revocation functional. + +--- + +## Deliverables Checklist + +- [ ] `~/.local-identity/` store initialised from Linux identity; test users generated +- [ ] `local-identity list / show / export` working; Keycloak export validated +- [ ] Minimal OIDC server passes conformance smoke test; binds localhost only +- [ ] Filesystem permissions enforced on startup; `security-check` passes +- [ ] Audit log recording all auth events +- [ ] `docs/LocalIdentity.md` complete with import procedure and security model +- [ ] NK-WP-0001 T07 migration procedure documented (Local Identity → Keycloak) + +## Open Questions + +None at this stage. All decisions resolved. Stage 3 language selection +(implementation language for the OIDC server) is a task-level detail to be +determined in T03.