- T19: Scenario B tests — IAM swap correctness (7 tests: profile safety, client mapping, user/group preservation) - T20: Scenario C tests — full expansion correctness (6 tests: LDIF round-trip, target differences, MFA orthogonality) - CI scripts: test-scenario-b.sh, test-scenario-c.sh - README: complete documentation with quick start, endpoints, migration guide - Workplan: all acceptance criteria checked off All 23 tasks done. 15 test packages, all green. go vet clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.8 KiB
KeyCape
Prepare for Keycloak without Keycloak
KeyCape is the lightweight IAM component of NetKingdom. It implements the NetKingdom IAM Profile — a versioned OIDC/PKCE contract — by orchestrating Authelia, LLDAP, and privacyIDEA. The same profile is implemented by Keycloak in expanded-mode deployments.
Applications integrate against the profile, not against Keycape internals. This makes the lightweight → expanded migration a tested, automated operation rather than a rewrite.
Status
Implementation complete (v0.1). All 23 workplan tasks implemented and tested.
21 test packages, all green. See workplans/KEY-WP-0001-keycape-implementation.md.
Architecture
Application
│ (NetKingdom IAM Profile)
▼
KeyCape ←── profile enforcement, claim normalization, telemetry
/ | \
Auth LLDAP privacyIDEA
elia
Expanded mode: Replace KeyCape with Keycloak. Same profile, same tests pass.
Quick Start
# Start the dev stack (KeyCape + LLDAP + Authelia + privacyIDEA)
make dev
# Build the server binary
make build
# Run all tests
make test
Configuration
KeyCape uses a YAML config file. See config/dev-config.yaml for a full example.
issuer: "https://auth.netkingdom.local"
port: 8080
tokenLifetime: "15m"
privateKeyPem: "/etc/keycape/key.pem"
environment: "production"
lldap:
url: "ldap://lldap:389"
bindDN: "cn=admin,dc=netkingdom,dc=local"
bindPW: "secret"
baseDN: "dc=netkingdom,dc=local"
authelia:
baseURL: "https://authelia.local"
clientId: "keycape"
clientSecret: "secret"
redirectURI: "https://auth.netkingdom.local/authorize/callback"
privacyidea:
baseURL: "https://privacyidea.local"
adminToken: "secret"
realm: "netkingdom"
clients:
- clientId: "my-app"
displayName: "My Application"
redirectUris: ["https://myapp.local/callback"]
allowedScopes: ["openid", "profile", "email", "groups"]
grantTypes: ["authorization_code"]
clientType: "public"
Config is validated at startup — the server exits 1 with validation errors if config is invalid.
Endpoints
| Endpoint | Description |
|---|---|
GET /.well-known/openid-configuration |
OIDC discovery document |
GET /jwks |
RS256 public key in JWK Set format |
GET /authorize |
Authorization endpoint (PKCE required) |
GET /authorize/callback |
Authelia callback handler |
POST /token |
Token exchange (authorization_code only) |
GET /userinfo |
Userinfo endpoint (Bearer token required) |
GET /healthz |
Health check → {"status":"ok","version":"0.1.0"} |
Profile Constraints
KeyCape enforces the NetKingdom IAM Profile. Violations return structured errors:
| Error type | Meaning |
|---|---|
feature_not_supported_by_profile |
Feature is outside the profile entirely |
available_in_keycloak_mode_only |
Available in expanded mode, not lightweight |
rejected_for_profile_safety |
Would weaken security guarantees |
invalid_profile_usage |
Supported feature used incorrectly |
Enforced boundaries: no implicit flow, no wildcard redirect URIs, no dynamic client registration, no identity brokering, PKCE S256 required.
Migration Tools
KeyCape ships migration tools for the two orthogonal migration dimensions:
IAM migration (KeyCape → Keycloak):
# Export canonical data from LLDAP
./lldap-export --url ldap://lldap:389 --bind-dn cn=admin,... --output canonical-export.yaml
# Transform to Keycloak realm import
./keycape-to-keycloak --input canonical-export.yaml --realm netkingdom --output keycloak-realm-import.json
Directory migration (LLDAP → OpenLDAP / 389DS / AD):
./lldap-to-ldap --input canonical-export.yaml --target openldap --base-dn dc=netkingdom,dc=local --output migration.ldif
Both migrations are independent. Perform either or both without affecting privacyIDEA MFA enrollment.
LDAP Schema Validator
# Validate in CI mode (strict)
./validator --mode ci --input directory-snapshot.yaml
# Validate before provisioning
./validator --mode provisioning --input users.yaml
Validates: DN structure, required attributes, no unknown attributes, user references, no cyclic groups, username uniqueness, email format.
Repo Structure
src/
cmd/ # Binary entrypoints
keycape/ # Main server
validator/ # LDAP schema validator
lldap-export/ # Migration: LLDAP → canonical
keycape-to-keycloak/ # Migration: canonical → Keycloak
lldap-to-ldap/ # Migration: canonical → LDIF
internal/
config/ # Config loading and validation
domain/ # Canonical identity model (Go types)
errors/ # Profile error taxonomy
adapters/ # Backend adapters (Authelia, LLDAP, privacyIDEA)
server/ # OIDC handlers + telemetry + enforcement
migration/ # Migration logic
validator/ # LDAP schema validation
tests/
profile/ # Scenario A: lightweight baseline
negative/ # Scenario D: unsupported feature rejection
migration/ # Scenarios B & C: replacement tests
spec/
canonical-model.yaml # Source of truth for all identity data
ldap-schema.yaml # Canonical LDAP schema rules
docs/adr/ # Architecture Decision Records
workplans/ # Implementation workplans
wiki/ # Specifications
Key Documents
wiki/KeyCapeSpecification_v0.1.md— Architecture, design intent, objectiveswiki/KeyCapeSpecificationPack_v0.1.md— Normative implementation specdocs/adr/ADR-0001-choose-go-for-keycape.md— Language decision (Go vs Rust)
Domain
Part of the NetKingdom domain. Tracked in the Custodian State Hub under
domain netkingdom, repo slug key-cape.
See CLAUDE.md for agent session protocol and workplan conventions.