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:
2026-03-14 21:57:52 -07:00
parent 4ddd32b117
commit 8f77050a84
26 changed files with 2980 additions and 129 deletions

View File

@@ -8,13 +8,13 @@ services:
ports:
- "8443:8443"
volumes:
- metacrypt-data:/data
# To populate /data before first run, use an init container or
- metacrypt-data:/srv/metacrypt
# To populate /srv/metacrypt before first run, use an init container or
# bind-mount a host directory instead of a named volume:
# volumes:
# - ./data:/data
# - ./data:/srv/metacrypt
healthcheck:
test: ["CMD", "metacrypt", "status", "--addr", "https://localhost:8443", "--ca-cert", "/data/certs/ca.crt"]
test: ["CMD", "metacrypt", "status", "--addr", "https://localhost:8443", "--ca-cert", "/srv/metacrypt/certs/ca.crt"]
interval: 30s
timeout: 5s
retries: 3

View File

@@ -1,17 +1,17 @@
# Metacrypt configuration for Docker deployment.
# Place this file at /data/metacrypt.toml inside the container volume.
# Place this file at /srv/metacrypt/metacrypt.toml inside the container volume.
[server]
listen_addr = ":8443"
tls_cert = "/data/certs/server.crt"
tls_key = "/data/certs/server.key"
tls_cert = "/srv/metacrypt/certs/server.crt"
tls_key = "/srv/metacrypt/certs/server.key"
[database]
path = "/data/metacrypt.db"
path = "/srv/metacrypt/metacrypt.db"
[mcias]
server_url = "https://mcias.metacircular.net:8443"
# ca_cert = "/data/certs/mcias-ca.crt"
# ca_cert = "/srv/metacrypt/certs/mcias-ca.crt"
[seal]
# argon2_time = 3

View File

@@ -1,18 +1,18 @@
# Metacrypt production configuration
# Copy to /etc/metacrypt/metacrypt.toml and adjust for your environment.
# Copy to /srv/metacrypt/metacrypt.toml and adjust for your environment.
[server]
# Address to listen on. Use "0.0.0.0:8443" to listen on all interfaces.
listen_addr = ":8443"
# TLS certificate and key. Metacrypt always terminates TLS.
tls_cert = "/etc/metacrypt/certs/server.crt"
tls_key = "/etc/metacrypt/certs/server.key"
tls_cert = "/srv/metacrypt/certs/server.crt"
tls_key = "/srv/metacrypt/certs/server.key"
[database]
# SQLite database path. Created automatically on first run.
# The directory must be writable by the metacrypt user.
path = "/var/lib/metacrypt/metacrypt.db"
path = "/srv/metacrypt/metacrypt.db"
[mcias]
# MCIAS server URL for authentication.
@@ -20,7 +20,7 @@ server_url = "https://mcias.metacircular.net:8443"
# CA certificate for verifying the MCIAS server's TLS certificate.
# Omit if MCIAS uses a publicly trusted certificate.
# ca_cert = "/etc/metacrypt/certs/mcias-ca.crt"
# ca_cert = "/srv/metacrypt/certs/mcias-ca.crt"
[seal]
# Argon2id parameters for key derivation.

View File

@@ -7,8 +7,8 @@
#
set -euo pipefail
CONFIG="${METACRYPT_CONFIG:-/etc/metacrypt/metacrypt.toml}"
BACKUP_DIR="${METACRYPT_BACKUP_DIR:-/var/lib/metacrypt/backups}"
CONFIG="${METACRYPT_CONFIG:-/srv/metacrypt/metacrypt.toml}"
BACKUP_DIR="${METACRYPT_BACKUP_DIR:-/srv/metacrypt/backups}"
RETENTION_DAYS="${1:-30}"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP_FILE="${BACKUP_DIR}/metacrypt-${TIMESTAMP}.db"

View File

@@ -8,9 +8,9 @@ set -euo pipefail
BINARY="${1:?Usage: $0 /path/to/metacrypt}"
INSTALL_DIR="/usr/local/bin"
CONFIG_DIR="/etc/metacrypt"
DATA_DIR="/var/lib/metacrypt"
BACKUP_DIR="${DATA_DIR}/backups"
SRV_DIR="/srv/metacrypt"
BACKUP_DIR="${SRV_DIR}/backups"
CERTS_DIR="${SRV_DIR}/certs"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEPLOY_DIR="$(dirname "$SCRIPT_DIR")"
@@ -19,25 +19,24 @@ if ! getent group metacrypt >/dev/null 2>&1; then
groupadd --system metacrypt
fi
if ! getent passwd metacrypt >/dev/null 2>&1; then
useradd --system --gid metacrypt --home-dir "$DATA_DIR" --shell /usr/sbin/nologin metacrypt
useradd --system --gid metacrypt --home-dir "$SRV_DIR" --shell /usr/sbin/nologin metacrypt
fi
echo "==> Installing binary"
install -m 0755 "$BINARY" "$INSTALL_DIR/metacrypt"
echo "==> Creating directories"
install -d -m 0750 -o metacrypt -g metacrypt "$CONFIG_DIR"
install -d -m 0750 -o metacrypt -g metacrypt "$CONFIG_DIR/certs"
install -d -m 0700 -o metacrypt -g metacrypt "$DATA_DIR"
install -d -m 0750 -o metacrypt -g metacrypt "$SRV_DIR"
install -d -m 0750 -o metacrypt -g metacrypt "$CERTS_DIR"
install -d -m 0700 -o metacrypt -g metacrypt "$BACKUP_DIR"
echo "==> Installing configuration"
if [ ! -f "$CONFIG_DIR/metacrypt.toml" ]; then
install -m 0640 -o metacrypt -g metacrypt "$DEPLOY_DIR/examples/metacrypt.toml" "$CONFIG_DIR/metacrypt.toml"
echo " Installed default config to $CONFIG_DIR/metacrypt.toml"
if [ ! -f "$SRV_DIR/metacrypt.toml" ]; then
install -m 0640 -o metacrypt -g metacrypt "$DEPLOY_DIR/examples/metacrypt.toml" "$SRV_DIR/metacrypt.toml"
echo " Installed default config to $SRV_DIR/metacrypt.toml"
echo " >>> Edit this file before starting the service <<<"
else
echo " Config already exists at $CONFIG_DIR/metacrypt.toml — skipping"
echo " Config already exists at $SRV_DIR/metacrypt.toml — skipping"
fi
echo "==> Installing systemd units"
@@ -49,8 +48,8 @@ systemctl daemon-reload
echo "==> Done"
echo ""
echo "Next steps:"
echo " 1. Place TLS cert and key in $CONFIG_DIR/certs/"
echo " 2. Edit $CONFIG_DIR/metacrypt.toml"
echo " 3. Initialize: metacrypt init --config $CONFIG_DIR/metacrypt.toml"
echo " 1. Place TLS cert and key in $CERTS_DIR/"
echo " 2. Edit $SRV_DIR/metacrypt.toml"
echo " 3. Initialize: metacrypt init --config $SRV_DIR/metacrypt.toml"
echo " 4. Start: systemctl enable --now metacrypt"
echo " 5. Backups: systemctl enable --now metacrypt-backup.timer"

View File

@@ -6,10 +6,10 @@ After=metacrypt.service
Type=oneshot
User=metacrypt
Group=metacrypt
ExecStart=/usr/local/bin/metacrypt snapshot --config /etc/metacrypt/metacrypt.toml --output /var/lib/metacrypt/backups/metacrypt-%i.db
ExecStart=/usr/local/bin/metacrypt snapshot --config /srv/metacrypt/metacrypt.toml --output /srv/metacrypt/backups/metacrypt-%i.db
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/metacrypt
ReadWritePaths=/srv/metacrypt

View File

@@ -9,7 +9,7 @@ Type=simple
User=metacrypt
Group=metacrypt
ExecStart=/usr/local/bin/metacrypt server --config /etc/metacrypt/metacrypt.toml
ExecStart=/usr/local/bin/metacrypt server --config /srv/metacrypt/metacrypt.toml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
@@ -30,8 +30,8 @@ LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
# Allow write access to the database directory and log
ReadWritePaths=/var/lib/metacrypt
# Allow write access to the data directory
ReadWritePaths=/srv/metacrypt
# Limit file descriptor count
LimitNOFILE=65535