# 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//`. 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 `). ### 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/`. They are encrypted at rest with the master encryption key and are only accessible when the service is unsealed.