- 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>
207 lines
6.2 KiB
Markdown
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.
|