Implement CA/PKI engine with two-tier X.509 certificate issuance
Add the first concrete engine implementation: a CA (PKI) engine that generates
a self-signed root CA at mount time, issues scoped intermediate CAs ("issuers"),
and signs leaf certificates using configurable profiles (server, client, peer).
Engine framework updates:
- Add CallerInfo struct for auth context in engine requests
- Add config parameter to Engine.Initialize for mount-time configuration
- Export Mount.Engine field; add GetEngine/GetMount on Registry
CA engine (internal/engine/ca/):
- Two-tier PKI: root CA → issuers → leaf certificates
- 10 operations: get-root, get-chain, get-issuer, create/delete/list issuers,
issue, get-cert, list-certs, renew
- Certificate profiles with user-overridable TTL, key usages, and key algorithm
- Private keys never stored in barrier; zeroized from memory on seal
- Supports ECDSA, RSA, and Ed25519 key types via goutils/certlib/certgen
Server routes:
- Wire up engine mount/request handlers (replace Phase 1 stubs)
- Add public PKI routes (/v1/pki/{mount}/ca, /ca/chain, /issuer/{name})
for unauthenticated TLS trust bootstrapping
Also includes: ARCHITECTURE.md, deploy config updates, operational tooling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
57
RUNBOOK.md
57
RUNBOOK.md
@@ -51,9 +51,10 @@ This creates:
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `/usr/local/bin/metacrypt` | Binary |
|
||||
| `/etc/metacrypt/metacrypt.toml` | Configuration |
|
||||
| `/etc/metacrypt/certs/` | TLS certificates |
|
||||
| `/var/lib/metacrypt/` | Database and backups |
|
||||
| `/srv/metacrypt/metacrypt.toml` | Configuration |
|
||||
| `/srv/metacrypt/certs/` | TLS certificates |
|
||||
| `/srv/metacrypt/backups/` | Database backups |
|
||||
| `/srv/metacrypt/metacrypt.db` | Database (created on first run) |
|
||||
|
||||
### Docker Install
|
||||
|
||||
@@ -65,7 +66,7 @@ make docker
|
||||
docker compose -f deploy/docker/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
The Docker container mounts a single volume at `/data` which must contain:
|
||||
The Docker container mounts a single volume at `/srv/metacrypt` which must contain:
|
||||
|
||||
| File | Required | Description |
|
||||
|---|---|---|
|
||||
@@ -81,8 +82,8 @@ To prepare a Docker volume:
|
||||
docker volume create metacrypt-data
|
||||
|
||||
# Copy files into the volume
|
||||
docker run --rm -v metacrypt-data:/data -v $(pwd)/deploy/examples:/src alpine \
|
||||
sh -c "cp /src/metacrypt-docker.toml /data/metacrypt.toml && mkdir -p /data/certs"
|
||||
docker run --rm -v metacrypt-data:/srv/metacrypt -v $(pwd)/deploy/examples:/src alpine \
|
||||
sh -c "cp /src/metacrypt-docker.toml /srv/metacrypt/metacrypt.toml && mkdir -p /srv/metacrypt/certs"
|
||||
|
||||
# Then copy your TLS certs into the volume the same way
|
||||
```
|
||||
@@ -96,7 +97,7 @@ Configuration is loaded from TOML. The config file location is determined by (in
|
||||
1. `--config` flag
|
||||
2. `METACRYPT_CONFIG` environment variable (via viper)
|
||||
3. `metacrypt.toml` in the current directory
|
||||
4. `/etc/metacrypt/metacrypt.toml`
|
||||
4. `/srv/metacrypt/metacrypt.toml`
|
||||
|
||||
All config values can be overridden via environment variables with the `METACRYPT_` prefix (e.g., `METACRYPT_SERVER_LISTEN_ADDR`).
|
||||
|
||||
@@ -131,7 +132,7 @@ For production, use certificates from your internal CA or a public CA.
|
||||
### Option A: CLI (recommended for servers)
|
||||
|
||||
```bash
|
||||
metacrypt init --config /etc/metacrypt/metacrypt.toml
|
||||
metacrypt init --config /srv/metacrypt/metacrypt.toml
|
||||
```
|
||||
|
||||
This prompts for a seal password, generates the master encryption key, and stores the encrypted MEK in the database. The service is left in the unsealed state.
|
||||
@@ -161,7 +162,7 @@ sudo systemctl start metacrypt
|
||||
docker compose -f deploy/docker/docker-compose.yml up -d
|
||||
|
||||
# Manual
|
||||
metacrypt server --config /etc/metacrypt/metacrypt.toml
|
||||
metacrypt server --config /srv/metacrypt/metacrypt.toml
|
||||
```
|
||||
|
||||
The service starts **sealed**. It must be unsealed before it can serve requests.
|
||||
@@ -230,7 +231,7 @@ Users with the MCIAS `admin` role automatically get admin privileges in Metacryp
|
||||
|
||||
```bash
|
||||
# CLI
|
||||
metacrypt snapshot --config /etc/metacrypt/metacrypt.toml --output /var/lib/metacrypt/backups/metacrypt-$(date +%Y%m%d).db
|
||||
metacrypt snapshot --config /srv/metacrypt/metacrypt.toml --output /srv/metacrypt/backups/metacrypt-$(date +%Y%m%d).db
|
||||
|
||||
# Using the backup script (with 30-day retention)
|
||||
deploy/scripts/backup.sh 30
|
||||
@@ -249,8 +250,8 @@ This runs a backup daily at 02:00 with up to 5 minutes of jitter.
|
||||
### Restoring from Backup
|
||||
|
||||
1. Stop the service: `systemctl stop metacrypt`
|
||||
2. Replace the database: `cp /var/lib/metacrypt/backups/metacrypt-20260314.db /var/lib/metacrypt/metacrypt.db`
|
||||
3. Fix permissions: `chown metacrypt:metacrypt /var/lib/metacrypt/metacrypt.db && chmod 0600 /var/lib/metacrypt/metacrypt.db`
|
||||
2. Replace the database: `cp /srv/metacrypt/backups/metacrypt-20260314.db /srv/metacrypt/metacrypt.db`
|
||||
3. Fix permissions: `chown metacrypt:metacrypt /srv/metacrypt/metacrypt.db && chmod 0600 /srv/metacrypt/metacrypt.db`
|
||||
4. Start the service: `systemctl start metacrypt`
|
||||
5. Unseal with the original seal password
|
||||
|
||||
@@ -290,8 +291,8 @@ journalctl -u metacrypt --priority=err --since="1 hour ago"
|
||||
|---|---|---|
|
||||
| Service state | `GET /v1/status` | `state != "unsealed"` for more than a few minutes after restart |
|
||||
| TLS certificate expiry | External cert checker | < 30 days to expiry |
|
||||
| Database file size | `stat /var/lib/metacrypt/metacrypt.db` | Unexpectedly large growth |
|
||||
| Backup age | `find /var/lib/metacrypt/backups -name '*.db' -mtime +2` | No backup in 48 hours |
|
||||
| Database file size | `stat /srv/metacrypt/metacrypt.db` | Unexpectedly large growth |
|
||||
| Backup age | `find /srv/metacrypt/backups -name '*.db' -mtime +2` | No backup in 48 hours |
|
||||
| MCIAS connectivity | Login attempt | Auth failures not caused by bad credentials |
|
||||
|
||||
---
|
||||
@@ -303,7 +304,7 @@ journalctl -u metacrypt --priority=err --since="1 hour ago"
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `config: server.tls_cert is required` | Missing or invalid config | Check config file path and contents |
|
||||
| `db: create file: permission denied` | Wrong permissions on data dir | `chown -R metacrypt:metacrypt /var/lib/metacrypt` |
|
||||
| `db: create file: permission denied` | Wrong permissions on data dir | `chown -R metacrypt:metacrypt /srv/metacrypt` |
|
||||
| `server: tls: failed to find any PEM data` | Bad cert/key files | Verify PEM format: `openssl x509 -in server.crt -text -noout` |
|
||||
|
||||
### Unseal fails
|
||||
@@ -326,14 +327,14 @@ journalctl -u metacrypt --priority=err --since="1 hour ago"
|
||||
|
||||
```bash
|
||||
# Check database integrity
|
||||
sqlite3 /var/lib/metacrypt/metacrypt.db "PRAGMA integrity_check;"
|
||||
sqlite3 /srv/metacrypt/metacrypt.db "PRAGMA integrity_check;"
|
||||
|
||||
# Check WAL mode
|
||||
sqlite3 /var/lib/metacrypt/metacrypt.db "PRAGMA journal_mode;"
|
||||
sqlite3 /srv/metacrypt/metacrypt.db "PRAGMA journal_mode;"
|
||||
# Should return: wal
|
||||
|
||||
# Check file permissions
|
||||
ls -la /var/lib/metacrypt/metacrypt.db
|
||||
ls -la /srv/metacrypt/metacrypt.db
|
||||
# Should be: -rw------- metacrypt metacrypt
|
||||
```
|
||||
|
||||
@@ -360,18 +361,18 @@ Sealing the service (`POST /v1/seal`) explicitly zeroizes all key material from
|
||||
|
||||
| Path | Mode | Owner |
|
||||
|---|---|---|
|
||||
| `/etc/metacrypt/metacrypt.toml` | 0640 | metacrypt:metacrypt |
|
||||
| `/etc/metacrypt/certs/server.key` | 0600 | metacrypt:metacrypt |
|
||||
| `/var/lib/metacrypt/metacrypt.db` | 0600 | metacrypt:metacrypt |
|
||||
| `/var/lib/metacrypt/backups/` | 0700 | metacrypt:metacrypt |
|
||||
| `/srv/metacrypt/metacrypt.toml` | 0640 | metacrypt:metacrypt |
|
||||
| `/srv/metacrypt/certs/server.key` | 0600 | metacrypt:metacrypt |
|
||||
| `/srv/metacrypt/metacrypt.db` | 0600 | metacrypt:metacrypt |
|
||||
| `/srv/metacrypt/backups/` | 0700 | metacrypt:metacrypt |
|
||||
|
||||
### systemd Hardening
|
||||
|
||||
The provided service unit applies: `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, `PrivateTmp`, `PrivateDevices`, `MemoryDenyWriteExecute`, and namespace restrictions. Only `/var/lib/metacrypt` is writable.
|
||||
The provided service unit applies: `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, `PrivateTmp`, `PrivateDevices`, `MemoryDenyWriteExecute`, and namespace restrictions. Only `/srv/metacrypt` is writable.
|
||||
|
||||
### Docker Security
|
||||
|
||||
The container runs as a non-root `metacrypt` user. The `/data` volume should be owned by the container's metacrypt UID (determined at build time). Do not run the container with `--privileged`.
|
||||
The container runs as a non-root `metacrypt` user. The `/srv/metacrypt` volume should be owned by the container's metacrypt UID (determined at build time). Do not run the container with `--privileged`.
|
||||
|
||||
---
|
||||
|
||||
@@ -388,7 +389,7 @@ The container runs as a non-root `metacrypt` user. The `/data` volume should be
|
||||
|
||||
There is no online password rotation in Phase 1. To change the seal password:
|
||||
|
||||
1. Create a backup: `metacrypt snapshot --output pre-rotation.db`
|
||||
1. Create a backup: `metacrypt snapshot --output /srv/metacrypt/backups/pre-rotation.db`
|
||||
2. Stop the service
|
||||
3. Re-initialize with a new database and password
|
||||
4. Migrate data from the old barrier (requires custom tooling or a future `metacrypt rekey` command)
|
||||
@@ -398,8 +399,8 @@ There is no online password rotation in Phase 1. To change the seal password:
|
||||
If the server is lost but you have a database backup and the seal password:
|
||||
|
||||
1. Install Metacrypt on a new server (see Installation)
|
||||
2. Copy the backup database to `/var/lib/metacrypt/metacrypt.db`
|
||||
3. Fix ownership: `chown metacrypt:metacrypt /var/lib/metacrypt/metacrypt.db`
|
||||
2. Copy the backup database to `/srv/metacrypt/metacrypt.db`
|
||||
3. Fix ownership: `chown metacrypt:metacrypt /srv/metacrypt/metacrypt.db`
|
||||
4. Start the service and unseal with the original password
|
||||
|
||||
The database backup contains the encrypted MEK and all barrier data. No additional secrets beyond the seal password are needed for recovery.
|
||||
@@ -407,7 +408,7 @@ The database backup contains the encrypted MEK and all barrier data. No addition
|
||||
### Upgrading Metacrypt
|
||||
|
||||
1. Build or download the new binary
|
||||
2. Create a backup: `metacrypt snapshot --output pre-upgrade.db`
|
||||
2. Create a backup: `metacrypt snapshot --output /srv/metacrypt/backups/pre-upgrade.db`
|
||||
3. Replace the binary: `install -m 0755 metacrypt /usr/local/bin/metacrypt`
|
||||
4. Restart: `systemctl restart metacrypt`
|
||||
5. Unseal and verify
|
||||
|
||||
Reference in New Issue
Block a user