Implement a two-level key hierarchy: the MEK now wraps per-engine DEKs
stored in a new barrier_keys table, rather than encrypting all barrier
entries directly. A v2 ciphertext format (0x02) embeds the key ID so the
barrier can resolve which DEK to use on decryption. v1 ciphertext remains
supported for backward compatibility.
Key changes:
- crypto: EncryptV2/DecryptV2/ExtractKeyID for v2 ciphertext with key IDs
- barrier: key registry (CreateKey, RotateKey, ListKeys, MigrateToV2, ReWrapKeys)
- seal: RotateMEK re-wraps DEKs without re-encrypting data
- engine: Mount auto-creates per-engine DEK
- REST + gRPC: barrier/keys, barrier/rotate-mek, barrier/rotate-key, barrier/migrate
- proto: BarrierService (v1 + v2) with ListKeys, RotateMEK, RotateKey, Migrate
- db: migration v2 adds barrier_keys table
Also includes: security audit report, CSRF protection, engine design specs
(sshca, transit, user), path-bound AAD migration tool, policy engine
enhancements, and ARCHITECTURE.md updates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add PUT /v1/policy/rule endpoint for updating policy rules; expose
full policy CRUD through the web UI with a dedicated policy page
- Add certificate revoke, delete, and get-cert to CA engine and wire
REST + gRPC routes; fix missing interceptor registrations
- Update ARCHITECTURE.md to reflect v2 gRPC as the active implementation,
document ACME endpoints, correct CA permission levels, and add policy/cert
management route tables
- Add POLICY.md documenting the priority-based ACL engine design
- Add web/templates/policy.html for policy management UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admins can now revoke or delete certificate records from the cert detail
page in the web UI. Revoked certificates display a [REVOKED] badge and
show revocation metadata (time and actor). Deletion redirects to the
issuer page.
The REST API gains three new authenticated endpoints that mirror the
gRPC surface:
GET /v1/ca/{mount}/cert/{serial} (auth required)
POST /v1/ca/{mount}/cert/{serial}/revoke (admin only)
DELETE /v1/ca/{mount}/cert/{serial} (admin only)
The CA engine stores revocation state (revoked, revoked_at, revoked_by)
directly in the existing CertRecord barrier entry. The proto CertRecord
message is extended with the same three fields (field numbers 10–12).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add SignCSR RPC to v2 CA proto and regenerate; implement handleSignCSR
in CA engine and caServer gRPC layer; add SignCSR client method and
POST /pki/sign-csr web route with result display in pki.html
- Fix issuer detail cert listing: template was using map-style index on
CertSummary structs; switch to struct field access and populate
IssuedBy/IssuedAt fields from proto response
- Add certificate detail view (cert_detail.html) with GET /cert/{serial}
and GET /cert/{serial}/download routes
- Update Makefile proto target to generate both v1 and v2 protos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Log Info-level audit events on success for:
- system: Init, Unseal, Seal
- auth: Login, Logout
- engine: Mount, Unmount
- policy: CreatePolicy, DeletePolicy
- ca: ImportRoot, CreateIssuer, DeleteIssuer, IssueCert, RenewCert
Each log line includes relevant identifiers (mount, issuer, serial, CN,
SANs, username) so that certificate issuance and other privileged
operations are traceable in the server logs.
Co-authored-by: Junie <junie@jetbrains.com>
- Add buf.yaml with STANDARD lint rules and FILE-level breaking change detection
- Add proto-lint Makefile target (buf lint + buf breaking --against master)
- Add lint Makefile target (golangci-lint) and include it in all
- Fix proto target: use module= option so protoc writes to gen/ not proto/
- engine.proto: rename rpc Request→Execute and message types accordingly
- acme.proto: drop redundant ACME prefix from SetConfig/ListAccounts/ListOrders messages
- policy.proto: add CreatePolicyResponse/GetPolicyResponse wrappers instead of returning PolicyRule directly from multiple RPCs
- Update grpcserver and webserver/client.go to match renamed types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The vault server holds in-memory unsealed state (KEK, engine keys) that
is lost on restart, requiring a full unseal ceremony. Previously the web
UI ran inside the vault process, so any UI change forced a restart and
re-unseal.
This change extracts the web UI into a separate metacrypt-web binary
that communicates with the vault over an authenticated gRPC connection.
The web server carries no sealed state and can be restarted freely.
- gen/metacrypt/v1/: generated Go bindings from proto/metacrypt/v1/
- internal/grpcserver/: full gRPC server implementation (System, Auth,
Engine, PKI, Policy, ACME services) with seal/auth/admin interceptors
- internal/webserver/: web server with gRPC vault client; templates
embedded via web/embed.go (no runtime web/ directory needed)
- cmd/metacrypt-web/: standalone binary entry point
- internal/config: added [web] section (listen_addr, vault_grpc, etc.)
- internal/server/routes.go: removed all web UI routes and handlers
- cmd/metacrypt/server.go: starts gRPC server alongside HTTP server
- Deploy: Dockerfile builds both binaries, docker-compose adds
metacrypt-web service, new metacrypt-web.service systemd unit,
Makefile gains proto/metacrypt-web targets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>