# 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.
