generated from coulomb/repo-seed
Some checks failed
Build and Publish Container Image / build-and-push (push) Has been cancelled
262 lines
8.0 KiB
Markdown
262 lines
8.0 KiB
Markdown
# KeyCape
|
|
|
|
*Prepare for Keycloak without Keycloak*
|
|
|
|
KeyCape is the lightweight IAM component of [NetKingdom](../net-kingdom/). It
|
|
implements lightweight mode for the **NetKingdom IAM Profile** — a versioned
|
|
OIDC/PKCE contract whose canonical core is now
|
|
`../net-kingdom/canon/standards/iam-profile_v0.2.md` — 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 v0.2)
|
|
▼
|
|
KeyCape ←── profile enforcement, claim normalization, telemetry
|
|
/ | \
|
|
Auth LLDAP privacyIDEA
|
|
elia
|
|
```
|
|
|
|
**Expanded mode:** Replace KeyCape with Keycloak. Same profile contract, same
|
|
conformance suite in `../net-kingdom/tools/iam-profile-conformance/`.
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
```yaml
|
|
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: "http://authelia.sso.svc.cluster.local:9091"
|
|
browserBaseURL: "https://authelia.local"
|
|
tokenBaseURL: "http://authelia.sso.svc.cluster.local:9091"
|
|
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"
|
|
- clientId: "netkingdom-bootstrap-console"
|
|
displayName: "NetKingdom Bootstrap Console"
|
|
redirectUris:
|
|
- "http://127.0.0.1:8876/oidc/callback"
|
|
- "http://localhost:8876/oidc/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.
|
|
|
|
`browserBaseURL` is used only for the human browser redirect to Authelia.
|
|
`tokenBaseURL` is used for server-side code exchange. If either is omitted,
|
|
KeyCape falls back to `baseURL`.
|
|
|
|
## 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 /authorize/callback` | privacyIDEA MFA challenge submission |
|
|
| `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. Profile v0.2
|
|
also requires normalized tenant, principal type, groups, roles, scopes, and
|
|
assurance evidence in tokens consumed by applications and flex-auth.
|
|
|
|
## Migration Tools
|
|
|
|
KeyCape ships migration tools for the two orthogonal migration dimensions:
|
|
|
|
**IAM migration (KeyCape → Keycloak):**
|
|
```bash
|
|
# 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):**
|
|
```bash
|
|
./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
|
|
|
|
```bash
|
|
# 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, objectives
|
|
- `wiki/KeyCapeSpecificationPack_v0.1.md` — Normative implementation spec
|
|
- `docs/adr/ADR-0001-choose-go-for-keycape.md` — Language decision (Go vs Rust)
|
|
|
|
## Container Image
|
|
|
|
The KeyCape image is published to the Gitea OCI registry on CoulombCore.
|
|
|
|
**Registry:** `92.205.130.254:32166`
|
|
**Image:** `92.205.130.254:32166/coulomb/key-cape`
|
|
|
|
### Pull
|
|
|
|
```bash
|
|
docker pull 92.205.130.254:32166/coulomb/key-cape:latest
|
|
```
|
|
|
|
The registry runs over plain HTTP. Configure Docker to allow it:
|
|
|
|
```json
|
|
// /etc/docker/daemon.json
|
|
{ "insecure-registries": ["92.205.130.254:32166"] }
|
|
```
|
|
|
|
### Build and push locally
|
|
|
|
```bash
|
|
# Build with default tag (latest)
|
|
make image
|
|
|
|
# Build with a specific tag
|
|
IMAGE_TAG=dev make image
|
|
|
|
# Push to registry (requires prior docker login)
|
|
docker login 92.205.130.254:32166
|
|
make push
|
|
|
|
# Push with a specific tag
|
|
IMAGE_TAG=v1.0.0 make push
|
|
```
|
|
|
|
### Tags
|
|
|
|
| Trigger | Tags |
|
|
|---------|------|
|
|
| Push to `main` | `latest`, `main-<short-sha>` |
|
|
| Tag `v1.2.3` | `1.2.3`, `1.2`, `1`, `latest` |
|
|
|
|
### CI (Gitea Actions)
|
|
|
|
The workflow at `.gitea/workflows/image.yaml` builds and publishes automatically
|
|
on every push to `main` and on semver tags (`v*`).
|
|
|
|
Required Gitea Actions secrets on the `key-cape` repo:
|
|
|
|
| Secret | Value |
|
|
|--------|-------|
|
|
| `REGISTRY_USER` | Gitea username or machine account (e.g. `ci-netkingdom`) |
|
|
| `REGISTRY_TOKEN` | Gitea personal access token with `write:packages` scope |
|
|
|
|
## 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.
|