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:
141
ARCHITECTURE.md
141
ARCHITECTURE.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user