Core

Kiwi ID

wraps Kanidm

Central identity provider for the entire KiwiStack platform. SSO via OIDC, WebAuthn passkeys, user & group management — every service delegates authentication to Kiwi ID.

License
MPL-2.0
Protocol
OIDC
Port
8443
Language
Rust

Features

Kiwi ID has no MCP tools — it provides infrastructure that every other service relies on.

🔐
SSO / OIDC
Standards-compliant OpenID Connect provider. Every KiwiStack service authenticates through a single sign-on flow.
🔑
WebAuthn / Passkeys
Passwordless authentication using FIDO2 hardware keys, platform authenticators, and passkeys synced across devices.
👥
User & Group Management
Create and manage users, groups, and access policies. RBAC with hierarchical group nesting.
🖥️
Self-Service WebUI
Users manage their own profile, credentials, and active sessions through Kanidm’s built-in web interface.
⌨️
CLI Admin
Full administration via kanidm CLI. Scriptable user provisioning, group management, and policy configuration.
📖
LDAPS Read-Only
Legacy LDAP compatibility for services that require it. Read-only bind with TLS. Write operations go through the native API.

Architecture

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 container layout
┌──────────────────────────────────┐
  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 
└──────────────────────────────────┘

Config Reference

Wrapper configuration — lives at /etc/kiwi/config.toml.

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"
KeyTypeDescription
listen_addrstringBind address for the wrapper (private bridge)
upstream_addrstringKanidm HTTPS endpoint on localhost
upstream_binpathPath to the Kanidm daemon binary
upstream_configpathPath to Kanidm’s server.toml
jwt_private_keypathPrivate key for signing JWTs (Kiwi ID only)
jwt_public_keypathPublic key shared with all other wrappers
log_levelstringTracing level: error, warn, info, debug, trace

Filesystem Layout

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/

License

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.

OIDC Endpoints

Discovery and token request examples.

Discovery
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"]
}
Token Request
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

Compatibility

Pinned versions — from compatibility.toml.

kiwi-id/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"