Files
key-cape/README.md
Bernd Worsch 7822ba0703
Some checks failed
Build and Publish Container Image / build-and-push (push) Has been cancelled
feat(image): KEY-WP-0002 T01/T02/T06 — Makefile image targets, Gitea Actions workflow, README CI docs
- Makefile: add IMAGE_REGISTRY/IMAGE_REPO/IMAGE_TAG vars + image, push, image-tag targets
- .gitea/workflows/image.yaml: build+push on main push and v* tags via metadata-action
- README: Container Image section with pull/build/push/CI secret docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:27:39 +00:00

242 lines
7.0 KiB
Markdown

# KeyCape
*Prepare for Keycloak without Keycloak*
KeyCape is the lightweight IAM component of [NetKingdom](../net-kingdom/). 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
```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: "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):**
```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/netkingdom/key-cape`
### Pull
```bash
docker pull 92.205.130.254:32166/netkingdom/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.