generated from coulomb/repo-seed
CloudNativePG Cluster CR (net-kingdom-pg, PostgreSQL 16) with two application databases: keycloak_db (owner: keycloak) and privacyidea_db (owner: privacyidea). Passwords managed continuously via managed.roles. WAL archiving section stubbed and commented; activate when object storage is available. ScheduledBackup CR included (daily 02:00 UTC, 7d retention). Also: sync workplan status for T01 (Phase 0a done), T02 (manifests done), T03 (manifests done, restore drill pending); close NK-WP-0002. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
241 lines
8.4 KiB
Markdown
241 lines
8.4 KiB
Markdown
---
|
||
id: NK-WP-0002
|
||
type: workplan
|
||
title: "Local Identity — Bootstrap User Store & Minimal OIDC"
|
||
domain: netkingdom
|
||
status: completed
|
||
owner: worsch
|
||
topic_slug: netkingdom
|
||
state_hub_workstream_id: 7c9021b1-319c-4b4a-a8be-0642239a1893
|
||
created: "2026-03-01"
|
||
updated: "2026-03-05"
|
||
---
|
||
|
||
# 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/
|
||
├── <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: done
|
||
priority: high
|
||
commit: 4491bea
|
||
```
|
||
|
||
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: done
|
||
priority: high
|
||
commit: dad8365
|
||
```
|
||
|
||
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: done
|
||
priority: medium
|
||
commit: d35823d
|
||
```
|
||
|
||
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: done
|
||
priority: medium
|
||
commit: e7bafd6
|
||
```
|
||
|
||
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
|
||
|
||
- [x] `~/.local-identity/` store initialised from Linux identity; test users generated
|
||
- [x] `local-identity list / show / export` working; Keycloak export validated
|
||
- [x] Minimal OIDC server passes conformance smoke test; binds localhost only
|
||
- [x] Filesystem permissions enforced on startup; `security-check` passes
|
||
- [x] Audit log recording all auth events
|
||
- [x] `docs/LocalIdentity.md` complete with import procedure and security model
|
||
- [x] 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.
|