feat(local-identity): add NK-WP-0002 workplan and LocalIdentity.md

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 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 23:49:06 +01:00
parent 873fbcf052
commit 6ed0061962
3 changed files with 416 additions and 24 deletions

155
docs/LocalIdentity.md Normal file
View File

@@ -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 | `<fullname>+test1` | `<fullname>+test2` |
| email | configured | `<user>+test1@…` | `<user>+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 <user>` 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 <username> # display user file
local-identity export <username> # 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).

View File

@@ -45,10 +45,10 @@ Two are pending and require further investigation (see Open Questions).
| ID | Decision | Status | Outcome / Notes | | ID | Decision | Status | Outcome / Notes |
|----|----------|--------|-----------------| |----|----------|--------|-----------------|
| D1 | Vault backend | **Resolved** | KeePassXC pre-cluster → HashiCorp Vault in-cluster. | | 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. | | 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. | | 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: separate repo vs defer vs existing tool | **Pending** | Full SWOT in State Hub. Preliminary recommendation: evaluate Keycloak Docker Compose first. | | D5 | File-based bootstrap user store | **Resolved** | **Implement in-repo as `local-identity`.** Staged workplan: NK-WP-0002. See `docs/LocalIdentity.md`. |
## Architecture ## Architecture
@@ -118,15 +118,16 @@ manifests). Store offsite.
**Phase 0b — HashiCorp Vault in-cluster (after T02, once K3s is running):** **Phase 0b — HashiCorp Vault in-cluster (after T02, once K3s is running):**
Deploy HashiCorp Vault in the cluster (Helm chart). Migrate secrets from Deploy HashiCorp Vault in the cluster (Helm chart). Migrate secrets from
KeePassXC into Vault. Enable K8s encryption-at-rest. Choose and implement KeePassXC into Vault. Enable K8s encryption-at-rest. Deploy External Secrets
secret injection strategy: External Secrets Operator + Vault backend, or Operator (ESO) — **decided D4**: ESO reconciles Vault secrets into standard
Vault Agent Injector (ESO preferred for GitOps alignment). KeePassXC K8s Secrets, compatible with plain Helm charts without Vault-specific
remains the source of truth for dev/test/sandbox systems that do not connect annotations. KeePassXC remains the source of truth for dev/test/sandbox
to the cluster Vault. systems that do not connect to the cluster Vault.
**Done when:** KeePassXC created and all secrets generated (0a). Vault **Done when:** KeePassXC created and all secrets generated (0a). Vault
deployed in-cluster, secrets migrated, injection strategy operational (0b). deployed in-cluster, secrets migrated, ESO operational and injecting secrets
Encrypted ops bundle exported and stored offsite. 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 events → centralized logging (ELK/Loki or equivalent). Token lifecycle
policies: enrollment, revocation, re-enrollment on device loss. policies: enrollment, revocation, re-enrollment on device loss.
**Bootstrap user management (D2 extension — scope TBD):** **Bootstrap user management (D2 + D5 — Local Identity):**
D2 also specifies a file-based lightweight user store for pre-Keycloak The pre-Keycloak user store is implemented as the `local-identity` capability.
systems (dev/test/sandbox that do not connect to the cluster). Users stored See [NK-WP-0002](NK-WP-0002-local-identity.md) and
as files in a secure subdirectory of the Linux home directory; auto-generates [docs/LocalIdentity.md](../docs/LocalIdentity.md). NK-WP-0002 Stage 2
two test users with `N` / `+testN` username and email suffixes. Test users produces Keycloak-compatible user exports (`local-identity export --all`)
must not spill over into other systems; a mapping mechanism from sandbox that feed the realm bulk-import during T06. Once T06 is operational,
identities to production should be provided. This scope is not yet captured Local Identity should be explicitly migrated away from for that instance.
in a task — see Open Questions.
**Done when:** policies documented and applied, self-service portal live, **Done when:** policies documented and applied, self-service portal live,
audit logs flowing, Keycloak resolver configured. 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 (D1D3). See `DECISIONS.md` for the three resolved decisions (D1D3).
Two pending decisions have been raised; see State Hub for full detail. Two pending decisions have been raised; see State Hub for full detail.
| # | Item | State Hub artefact | Status | All five decisions are now resolved. See `DECISIONS.md` for D1D3 rationale;
|---|------|--------------------|--------| State Hub decisions `aca69951` (D4) and `d74e2b11` (D5) for the full records.
| 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 | | Artefact | Item | Status |
| — | 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 | | 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 |

View File

@@ -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 (T01T08) 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/
├── <user>.yaml # primary user (derived from Linux identity)
├── <user>1.yaml # test user 1 (generated)
└── <user>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 <user>` — 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 <user>` — 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 <jti>` 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.