Add policy CRUD, cert management, and web UI updates

- 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>
This commit is contained in:
2026-03-15 19:41:11 -07:00
parent 02ee538213
commit fbd6d1af04
17 changed files with 1055 additions and 58 deletions

View File

@@ -71,6 +71,7 @@ internal/
policy/ Priority-based ACL engine
engine/ Pluggable engine registry & interface
ca/ CA (PKI) engine — X.509 certificate issuance
acme/ ACME protocol handler (RFC 8555); EAB, accounts, orders
server/ REST API HTTP server, routes, middleware
grpcserver/ gRPC server, interceptors, per-service handlers
webserver/ Web UI HTTP server, routes, HTMX handlers
@@ -78,6 +79,7 @@ proto/metacrypt/
v1/ Original gRPC proto definitions (generic Execute RPC)
v2/ Typed gRPC proto definitions (per-operation RPCs, Timestamp fields)
gen/metacrypt/v1/ Generated Go gRPC/protobuf code (v1)
gen/metacrypt/v2/ Generated Go gRPC/protobuf code (v2)
web/
templates/ Go HTML templates (layout, init, unseal, login, dashboard, PKI)
static/ CSS, HTMX
@@ -384,10 +386,10 @@ Certificate generation uses the `certgen` package from
| `create-issuer` | Admin | Generate intermediate CA signed by root |
| `delete-issuer` | Admin | Remove issuer and zeroize its key |
| `list-issuers` | Any auth | List issuer names |
| `issue` | User/Admin | Issue leaf cert from named issuer |
| `get-cert` | Any auth | Get cert record by serial |
| `list-certs` | Any auth | List issued cert summaries |
| `renew` | User/Admin | Re-issue cert with same attributes |
| `issue` | Admin | Issue leaf cert from named issuer |
| `get-cert` | User/Admin | Get cert record by serial |
| `list-certs` | User/Admin | List issued cert summaries |
| `renew` | Admin | Re-issue cert with same attributes |
#### Certificate Profiles
@@ -472,6 +474,16 @@ kept in sync — every operation available via REST has a corresponding gRPC RPC
| POST | `/v1/engine/unmount` | Remove engine mount | Admin |
| POST | `/v1/engine/request` | Route request to engine | User |
### Policy (Admin Only)
| Method | Path | Description |
|--------|-----------------------|------------------------------------|
| GET | `/v1/policy/rules` | List all policy rules |
| POST | `/v1/policy/rules` | Create a new policy rule |
| GET | `/v1/policy/rule?id=` | Get a policy rule by ID |
| PUT | `/v1/policy/rule?id=` | Update a policy rule by ID |
| DELETE | `/v1/policy/rule?id=` | Delete a policy rule by ID |
The mount endpoint accepts `{name, type, config}` where `config` is an
engine-type-specific configuration object. The request endpoint accepts
`{mount, operation, path, data}` and populates `CallerInfo` from the
@@ -482,21 +494,56 @@ authenticated user's token.
| Method | Path | Description |
|--------|-------------------------------------|-------------------------------|
| GET | `/v1/pki/{mount}/ca` | Root CA certificate (PEM) |
| GET | `/v1/pki/{mount}/ca/chain?issuer=` | Full chain: issuer + root (PEM) |
| GET | `/v1/pki/{mount}/ca/chain` | Full chain: issuer + root (PEM) |
| GET | `/v1/pki/{mount}/issuer/{name}` | Issuer certificate (PEM) |
These routes serve certificates with `Content-Type: application/x-pem-file`,
allowing systems to bootstrap TLS trust without authentication. The mount
must be of type `ca`; returns 404 otherwise.
### Policy (Admin Only)
### CA Certificate Management (Authenticated)
| Method | Path | Description |
|--------|-----------------------|---------------------|
| GET | `/v1/policy/rules` | List all rules |
| POST | `/v1/policy/rules` | Create a rule |
| GET | `/v1/policy/rule?id=` | Get rule by ID |
| DELETE | `/v1/policy/rule?id=` | Delete rule by ID |
| Method | Path | Description | Auth |
|--------|---------------------------------------|------------------------------------|---------|
| GET | `/v1/ca/{mount}/cert/{serial}` | Get certificate record by serial | User |
| POST | `/v1/ca/{mount}/cert/{serial}/revoke` | Revoke a certificate | Admin |
| DELETE | `/v1/ca/{mount}/cert/{serial}` | Delete a certificate record | Admin |
### Policy (Authenticated)
| Method | Path | Description | Auth |
|--------|-----------------------|---------------------|-------|
| GET | `/v1/policy/rules` | List all rules | User |
| POST | `/v1/policy/rules` | Create a rule | User |
| GET | `/v1/policy/rule?id=` | Get rule by ID | User |
| DELETE | `/v1/policy/rule?id=` | Delete rule by ID | User |
### ACME (RFC 8555)
ACME protocol endpoints are mounted per CA engine instance and require no
authentication (per the ACME spec). External Account Binding (EAB) is
supported.
| Method | Path | Description | Auth |
|--------|-----------------------------------|--------------------------------------|---------|
| GET | `/acme/{mount}/directory` | ACME directory object | None |
| HEAD/GET | `/acme/{mount}/new-nonce` | Obtain a fresh replay nonce | None |
| POST | `/acme/{mount}/new-account` | Register or retrieve an account | None |
| POST | `/acme/{mount}/new-order` | Create a new certificate order | None |
| POST | `/acme/{mount}/authz/{id}` | Fetch/respond to an authorization | None |
| POST | `/acme/{mount}/challenge/{type}/{id}` | Respond to a challenge | None |
| POST | `/acme/{mount}/finalize/{id}` | Finalize an order (submit CSR) | None |
| POST | `/acme/{mount}/cert/{id}` | Download issued certificate | None |
| POST | `/acme/{mount}/revoke-cert` | Revoke a certificate via ACME | None |
ACME management endpoints require MCIAS authentication:
| Method | Path | Description | Auth |
|--------|-------------------------------|--------------------------------------|---------|
| POST | `/v1/acme/{mount}/eab` | Create EAB credentials for a user | User |
| PUT | `/v1/acme/{mount}/config` | Set default issuer for ACME mount | Admin |
| GET | `/v1/acme/{mount}/accounts` | List ACME accounts | Admin |
| GET | `/v1/acme/{mount}/orders` | List ACME orders | Admin |
### Error Responses
@@ -517,9 +564,11 @@ HTTP status codes:
Metacrypt also exposes a gRPC API defined in `proto/metacrypt/`. Two API
versions exist:
#### v1 (current implementation)
#### v1 (legacy proto definitions)
The v1 API uses a generic `Execute` RPC for all engine operations:
The v1 API uses a generic `Execute` RPC for all engine operations. The v1
proto definitions are retained for reference; the active server implementation
uses v2.
```protobuf
rpc Execute(ExecuteRequest) returns (ExecuteResponse);
@@ -540,15 +589,16 @@ Timestamps are represented as RFC3339 strings within the `Struct` payload.
The `EngineService` also provides `Mount`, `Unmount`, and `ListMounts` RPCs
for engine lifecycle management.
#### v2 (defined, not yet implemented)
#### v2 (implemented)
The v2 API (`proto/metacrypt/v2/`) replaces the generic `Execute` RPC with
strongly-typed, per-operation RPCs and uses `google.protobuf.Timestamp` for
all time fields. Key changes:
all time fields. The gRPC server is fully implemented against v2. Key changes
from v1:
- **`CAService`**: 11 typed RPCs — `ImportRoot`, `GetRoot`, `CreateIssuer`,
- **`CAService`**: 14 typed RPCs — `ImportRoot`, `GetRoot`, `CreateIssuer`,
`DeleteIssuer`, `ListIssuers`, `GetIssuer`, `GetChain`, `IssueCert`,
`GetCert`, `ListCerts`, `RenewCert`.
`GetCert`, `ListCerts`, `RenewCert`, `SignCSR`, `RevokeCert`, `DeleteCert`.
- **`EngineService`**: Retains `Mount`, `Unmount`, `ListMounts`; drops the
generic `Execute` RPC. `MountRequest.config` is `map<string, string>`
instead of `google.protobuf.Struct`.
@@ -556,13 +606,13 @@ all time fields. Key changes:
`google.protobuf.Timestamp` instead of RFC3339 strings.
- **Message types**: `CertRecord` (full certificate data) and `CertSummary`
(lightweight, for list responses) replace the generic struct maps.
- **`ACMEService`** and **`AuthService`**: String timestamps replaced by
`google.protobuf.Timestamp`.
- **`ACMEService`**: `CreateEAB`, `SetConfig`, `ListAccounts`, `ListOrders`.
- **`AuthService`**: String timestamps replaced by `google.protobuf.Timestamp`.
The v2 proto definitions pass `buf lint` with no warnings. Server-side
implementation of v2 is planned as a future milestone.
The v2 proto definitions pass `buf lint` with no warnings. Generated Go code
lives in `gen/metacrypt/v2/`.
#### gRPC Interceptors (v1)
#### gRPC Interceptors (v2)
The gRPC server (`internal/grpcserver/`) uses three interceptor maps to gate
access:
@@ -573,8 +623,9 @@ access:
| `authRequiredMethods` | Validates MCIAS bearer token; populates caller info |
| `adminRequiredMethods`| Requires `IsAdmin == true` on the caller |
All three maps include the `Execute` RPC, ensuring engine operations are
always authenticated and gated on unseal state.
All CA write operations, engine lifecycle RPCs, policy mutations, and ACME
management RPCs are gated on unseal state, authentication, and (where
appropriate) admin privilege.
---
@@ -582,22 +633,33 @@ always authenticated and gated on unseal state.
Metacrypt includes an HTMX-powered web UI for basic operations:
| Route | Purpose |
|--------------------------|-------------------------------------------------------|
| `/` | Redirects based on service state |
| `/init` | Password setup form (first-time only) |
| `/unseal` | Password entry to unseal |
| `/login` | MCIAS login form (username, password, TOTP) |
| `/dashboard` | Engine mounts, service state, admin controls |
| `/pki` | PKI overview: list issuers, download CA/issuer PEMs |
| `/pki/issuer/{name}` | Issuer detail: certificates issued by that issuer |
| Route | Purpose |
|-------------------------------|-------------------------------------------------------|
| `/` | Redirects based on service state |
| `/init` | Password setup form (first-time only) |
| `/unseal` | Password entry to unseal |
| `/login` | MCIAS login form (username, password, TOTP) |
| `/dashboard` | Engine mounts, service state, admin controls |
| `/dashboard/mount-ca` | Mount a new CA engine (POST, admin) |
| `/pki` | PKI overview: list issuers, download CA/issuer PEMs |
| `/pki/import-root` | Import an existing root CA (POST) |
| `/pki/create-issuer` | Create a new intermediate issuer (POST) |
| `/pki/issue` | Issue a leaf certificate (POST) |
| `/pki/download/{token}` | Download issued cert bundle as .tar.gz |
| `/pki/issuer/{name}` | Issuer detail: certificates issued by that issuer |
| `/pki/cert/{serial}` | Certificate detail page |
| `/pki/cert/{serial}/download` | Download certificate files |
| `/pki/cert/{serial}/revoke` | Revoke a certificate (POST) |
| `/pki/cert/{serial}/delete` | Delete a certificate record (POST) |
| `/pki/{issuer}` | Issuer detail (alternate path) |
The dashboard shows mounted engines, the service state, and (for admins) a seal
button. Templates use Go's `html/template` with a shared layout. HTMX provides
form submission without full page reloads.
The PKI pages communicate with the backend via the internal gRPC client
(`internal/webserver/client.go`), which wraps the v1 gRPC `Execute` RPC.
(`internal/webserver/client.go`), which uses the v2 typed gRPC stubs
(`CAService`, `EngineService`, `SystemService`, etc.).
The issuer detail page supports filtering certificates by common name
(case-insensitive substring match) and sorting by common name (default) or
expiry date.
@@ -649,9 +711,15 @@ TOML configuration with environment variable overrides (`METACRYPT_*`).
```toml
[server]
listen_addr = ":8443" # required
grpc_addr = ":8444" # optional; gRPC server disabled if unset
tls_cert = "/path/cert.pem" # required
tls_key = "/path/key.pem" # required
[web]
listen_addr = "127.0.0.1:8080" # optional; web UI server address
vault_grpc = "127.0.0.1:9443" # gRPC address of the vault server
vault_ca_cert = "/path/ca.pem" # optional; CA cert to verify vault TLS
[database]
path = "/path/metacrypt.db" # required
@@ -764,9 +832,6 @@ closing connections before exit.
### Planned Capabilities
- **gRPC v2 server implementation** — The v2 typed proto definitions are
complete; the server-side handlers and generated Go code remain to be
implemented
- **Post-quantum readiness** — Hybrid key exchange (ML-KEM + ECDH); the
versioned ciphertext format and engine interface are designed for algorithm
agility