Files
metacrypt/POLICY.md
Kyle Isom fbd6d1af04 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>
2026-03-15 19:41:11 -07:00

207 lines
6.2 KiB
Markdown

# Metacrypt Policy Engine
Metacrypt includes a priority-based access control policy engine that governs
which authenticated users may perform which operations on which engine
resources. This document explains the policy model, rule structure, evaluation
algorithm, and how to manage rules via the API and web UI.
## Overview
Every request to `POST /v1/engine/request` is evaluated against the policy
engine before being dispatched to the underlying cryptographic engine. The
policy engine enforces a **default-deny** posture: unless a matching allow rule
exists, non-admin users are denied.
**Admin bypass**: Users with the `admin` role in MCIAS always pass policy
evaluation unconditionally. Policy rules only affect non-admin users.
## Rule Structure
A policy rule is a JSON object with the following fields:
| Field | Type | Required | Description |
|-------------|-----------------|----------|----------------------------------------------------------|
| `id` | string | Yes | Unique identifier for the rule |
| `priority` | integer | Yes | Evaluation order; lower number = higher priority |
| `effect` | `"allow"` or `"deny"` | Yes | Decision when this rule matches |
| `usernames` | []string | No | Match specific usernames (case-insensitive). Empty = any |
| `roles` | []string | No | Match any of these roles (case-insensitive). Empty = any |
| `resources` | []string | No | Glob patterns for the resource path. Empty = any |
| `actions` | []string | No | `"read"` or `"write"`. Empty = any |
### Resource Paths
Resources follow the pattern `engine/<mount>/<operation>`. For example:
- `engine/pki/list-certs` — list certificates on the `pki` mount
- `engine/pki/issue` — issue a certificate on the `pki` mount
- `engine/transit/*` — any operation on the `transit` mount
Glob patterns use standard filepath matching (`*` matches within a path
segment; `**` is not supported — use `engine/pki/*` to match all operations on
a mount).
### Actions
Operations are classified as either `read` or `write`:
| Action | Operations |
|---------|-------------------------------------------------------------------|
| `read` | `list-issuers`, `list-certs`, `get-cert`, `get-root`, `get-chain`, `get-issuer` |
| `write` | All other operations (`issue`, `renew`, `sign-csr`, `create-issuer`, `delete-issuer`, etc.) |
## Evaluation Algorithm
1. If the caller has the `admin` role → **allow** immediately (bypass all rules).
2. Load all rules from the barrier.
3. Sort rules by `priority` ascending (lower number = evaluated first).
4. Iterate rules in order; for each rule, check:
- If `usernames` is non-empty, the caller's username must match one entry.
- If `roles` is non-empty, the caller must have at least one matching role.
- If `resources` is non-empty, the resource must match at least one glob pattern.
- If `actions` is non-empty, the action must match one entry.
- All specified conditions must match (AND logic within a rule).
5. The **first matching rule** wins; return its `effect`.
6. If no rule matches → **deny** (default deny).
## Managing Rules via the REST API
All policy endpoints require an authenticated admin token
(`Authorization: Bearer <token>`).
### List Rules
```
GET /v1/policy/rules
```
Returns a JSON array of all policy rules.
### Create a Rule
```
POST /v1/policy/rules
Content-Type: application/json
{
"id": "allow-users-read-pki",
"priority": 10,
"effect": "allow",
"roles": ["user"],
"resources": ["engine/pki/*"],
"actions": ["read"]
}
```
Returns `201 Created` with the created rule on success.
### Get a Rule
```
GET /v1/policy/rule?id=allow-users-read-pki
```
### Update a Rule
```
PUT /v1/policy/rule?id=allow-users-read-pki
Content-Type: application/json
{ ... updated rule fields ... }
```
### Delete a Rule
```
DELETE /v1/policy/rule?id=allow-users-read-pki
```
## Managing Rules via the Web UI
Navigate to **Policy** in the top navigation bar (visible to admin users only).
The policy page shows:
- A table of all active rules with their ID, priority, effect, and match
conditions.
- A **Create Rule** form (expandable) for adding new rules.
- A **Delete** button on each row to remove a rule.
## Managing Rules via gRPC
The `PolicyService` gRPC service (defined in `proto/metacrypt/v2/policy.proto`)
provides equivalent operations:
| RPC | Description |
|------------------|--------------------------|
| `CreatePolicy` | Create a new rule |
| `ListPolicies` | List all rules |
| `GetPolicy` | Get a rule by ID |
| `DeletePolicy` | Delete a rule by ID |
## Common Patterns
### Allow users to read PKI resources
```json
{
"id": "allow-users-read-pki",
"priority": 10,
"effect": "allow",
"roles": ["user"],
"resources": ["engine/pki/*"],
"actions": ["read"]
}
```
### Allow a specific user to issue certificates
```json
{
"id": "allow-alice-issue",
"priority": 5,
"effect": "allow",
"usernames": ["alice"],
"resources": ["engine/pki/issue"],
"actions": ["write"]
}
```
### Deny guests all access to a mount
```json
{
"id": "deny-guests-transit",
"priority": 1,
"effect": "deny",
"roles": ["guest"],
"resources": ["engine/transit/*"]
}
```
### Allow users read-only access to all mounts
```json
{
"id": "allow-users-read-all",
"priority": 50,
"effect": "allow",
"roles": ["user"],
"actions": ["read"]
}
```
## Role Summary
| Role | Default access |
|---------|-----------------------------------------------------|
| `admin` | Full access to everything (policy bypass) |
| `user` | Denied by default; grant access via policy rules |
| `guest` | Denied by default; grant access via policy rules |
## Storage
Policy rules are stored in the encrypted barrier under the prefix
`policy/rules/<id>`. They are encrypted at rest with the master encryption key
and are only accessible when the service is unsealed.