Central identity provider for the entire KiwiStack platform. SSO via OIDC, WebAuthn passkeys, user & group management — every service delegates authentication to Kiwi ID.
Kiwi ID has no MCP tools — it provides infrastructure that every other service relies on.
kanidm CLI. Scriptable user provisioning, group management, and policy configuration.Every service delegates authentication to Kiwi ID via standard OIDC.
Kanidm runs on 127.0.0.1:8443 inside the LXC container and handles all identity operations:
user authentication, token issuance, session management, and credential storage. The Kiwi ID wrapper sits
in front, binding to 10.10.20.10:8443 on the private bridge.
When a user logs in to any KiwiStack service, they are redirected to Kiwi ID’s OIDC authorization endpoint. After authentication (password, passkey, or MFA), Kanidm issues a JWT signed with the instance’s private key. Every other wrapper validates this JWT using the corresponding public key — no network call to Kiwi ID required for token validation.
Flow: User → Service → redirect to Kiwi ID → authenticate → JWT issued → redirect back → Service validates JWT locally.
┌──────────────────────────────────┐ │ LXC: kiwi-id │ │ │ │ Kanidm: 127.0.0.1:8443 │ ← MPL-2.0 │ ↓ localhost HTTPS │ │ Wrapper: 10.10.20.10:8443 │ ← Apache-2.0 │ │ │ Only wrapper is on the network │ └──────────────────────────────────┘
Wrapper configuration — lives at /etc/kiwi/config.toml.
# kiwi-id wrapper configuration listen_addr = "10.10.20.10:8443" upstream_addr = "https://127.0.0.1:8443" upstream_bin = "/opt/upstream/bin/kanidmd" upstream_config = "/opt/upstream/etc/server.toml" health_check_interval = "5s" health_check_timeout = "60s" jwt_private_key = "/etc/kiwi/jwt-private.pem" jwt_public_key = "/etc/kiwi/jwt-public.pem" log_level = "info"
| Key | Type | Description |
|---|---|---|
| listen_addr | string | Bind address for the wrapper (private bridge) |
| upstream_addr | string | Kanidm HTTPS endpoint on localhost |
| upstream_bin | path | Path to the Kanidm daemon binary |
| upstream_config | path | Path to Kanidm’s server.toml |
| jwt_private_key | path | Private key for signing JWTs (Kiwi ID only) |
| jwt_public_key | path | Public key shared with all other wrappers |
| log_level | string | Tracing level: error, warn, info, debug, trace |
How files are organized inside the LXC container.
/ ├── opt/ │ ├── upstream/ # Kanidm (MPL-2.0) │ │ ├── bin/kanidmd # Kanidm server daemon │ │ ├── bin/kanidm # CLI admin tool │ │ └── etc/server.toml # Kanidm config (TLS, DB path) │ └── kiwi/ # Kiwi wrapper (Apache 2.0) │ ├── bin/kiwi-id # wrapper binary (static musl build) │ └── version # "0.1.0" ├── etc/ │ └── kiwi/ │ ├── config.toml # wrapper config (see above) │ ├── jwt-private.pem # JWT signing key (Kiwi ID only) │ └── jwt-public.pem # JWT verification key (shared) ├── var/ │ ├── lib/ │ │ └── upstream/ # Kanidm data │ │ └── kanidm.db # identity database │ └── log/ │ └── kiwi/ # JSON structured logs └── run/
Understanding the MPL-2.0 boundary.
Kanidm is licensed under MPL-2.0 — a file-level copyleft license. If you modify any MPL-licensed file, you must share your changes to that file. However, MPL explicitly allows combining MPL code with code under other licenses (including proprietary) in a “Larger Work.”
KiwiStack runs the unmodified Kanidm binary and communicates with it over HTTPS. No MPL files are modified, so there is no obligation to share any KiwiStack code. The wrapper is purely Apache-2.0.
Rule: If you modify Kanidm source files, you must share those specific file changes. Unmodified binary usage is fully permissible.
Discovery and token request examples.
GET https://id.example.com/.well-known/openid-configuration { "issuer": "https://id.example.com", "authorization_endpoint": "https://id.example.com/oauth2/authorize", "token_endpoint": "https://id.example.com/oauth2/token", "userinfo_endpoint": "https://id.example.com/oauth2/userinfo", "jwks_uri": "https://id.example.com/oauth2/keys", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "subject_types_supported": ["public"] }
POST https://id.example.com/oauth2/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTH_CODE_HERE &redirect_uri=https://mail.example.com/callback &client_id=kiwi-mail
Pinned versions — from compatibility.toml.
# kiwi-id/compatibility.toml [upstream] name = "kanidm" version = "1.5.0" checksum = "sha256:..." [wrapper] version = "0.1.0" rust_edition = "2024" msrv = "1.85" [tested] date = "2026-02-28"