# 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 | ## Keycloak import procedure Once the Keycloak realm is operational (NK-WP-0001 T06), migrate the primary user from Local Identity into Keycloak using the partial import endpoint. **1. Export the primary user:** ```bash local-identity export --all --realm net-kingdom > /tmp/li-import.json # By default, only the primary user is exported (test users are excluded). # Check: the Note line on stderr confirms how many test users were skipped. ``` **2. Import via the Keycloak Admin REST API:** ```bash # Requires a Keycloak admin token TOKEN=$(curl -s -X POST https://keycloak.yourdomain.com/realms/master/protocol/openid-connect/token \ -d "client_id=admin-cli&grant_type=password&username=admin&password=" \ | jq -r .access_token) curl -s -X POST \ https://keycloak.yourdomain.com/admin/realms/net-kingdom/partialImport \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d @/tmp/li-import.json ``` **3. Set a password in Keycloak** (Local Identity does not export credentials): ```bash curl -s -X PUT \ https://keycloak.yourdomain.com/admin/realms/net-kingdom/users//reset-password \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"type":"password","value":"","temporary":false}' ``` **4. Retire Local Identity for this instance:** Once the user is operational in Keycloak, stop using `local-identity serve` for this environment and remove the store: `rm -rf ~/.local-identity`. ## Isolation guarantee All users exported by Local Identity carry the attribute: ```json "attributes": { "local_identity_environment": ["local"] } ``` Generated test users additionally carry `local_identity_generated: ["true"]`. ### Configuring Keycloak to reject local-identity users Add a condition to the Keycloak browser authentication flow that denies login for any user with `local_identity_environment = local`. This is a defence-in-depth measure: even if test users are accidentally imported into a production realm, they cannot authenticate. In Keycloak 23+, use a **Conditional Authenticator** with a User Attribute Condition: 1. In the realm's **Authentication → Flows → browser** flow, add a sub-flow. 2. Add the **Condition - User Attribute** authenticator. 3. Configure: attribute = `local_identity_environment`, value = `local`, negation = **false** (matches when attribute equals the value). 4. Set the sub-flow to **DENY** when the condition is true. Alternatively, use a Keycloak script authenticator or a custom policy enforcer. ### Production identity mapping Before importing, you can assign a `production_identity` block to the user so the Keycloak username differs from the local username: ```yaml # ~/.local-identity/users/tegwick.yaml production_identity: username: bworsch # the username used in the production realm realm: net-kingdom ``` Re-run `local-identity export --all` — the exported JSON will use `bworsch` as the Keycloak username and a deterministic UUID derived from `net-kingdom/bworsch`. ## 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. Implementation: see [NK-WP-0002](../workplans/NK-WP-0002-local-identity.md).