Files
metacrypt/web/templates/policy.html
Kyle Isom 64d921827e Add MEK rotation, per-engine DEKs, and v2 ciphertext format (audit #6, #22)
Implement a two-level key hierarchy: the MEK now wraps per-engine DEKs
stored in a new barrier_keys table, rather than encrypting all barrier
entries directly. A v2 ciphertext format (0x02) embeds the key ID so the
barrier can resolve which DEK to use on decryption. v1 ciphertext remains
supported for backward compatibility.

Key changes:
- crypto: EncryptV2/DecryptV2/ExtractKeyID for v2 ciphertext with key IDs
- barrier: key registry (CreateKey, RotateKey, ListKeys, MigrateToV2, ReWrapKeys)
- seal: RotateMEK re-wraps DEKs without re-encrypting data
- engine: Mount auto-creates per-engine DEK
- REST + gRPC: barrier/keys, barrier/rotate-mek, barrier/rotate-key, barrier/migrate
- proto: BarrierService (v1 + v2) with ListKeys, RotateMEK, RotateKey, Migrate
- db: migration v2 adds barrier_keys table

Also includes: security audit report, CSRF protection, engine design specs
(sshca, transit, user), path-bound AAD migration tool, policy engine
enhancements, and ARCHITECTURE.md updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 18:27:44 -07:00

111 lines
4.9 KiB
HTML

{{define "title"}} - Policy{{end}}
{{define "content"}}
<div class="page-header">
<h2>Policy Rules</h2>
<div class="page-meta">
<a href="/dashboard" class="btn btn-ghost btn-sm">← Dashboard</a>
</div>
</div>
{{if .Error}}<div class="error">{{.Error}}</div>{{end}}
<div class="card">
<div class="card-title">Active Rules</div>
{{if .Rules}}
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>ID</th>
<th>Priority</th>
<th>Effect</th>
<th>Usernames</th>
<th>Roles</th>
<th>Resources</th>
<th>Actions</th>
<th></th>
</tr>
</thead>
<tbody>
{{range .Rules}}
<tr>
<td><code>{{.ID}}</code></td>
<td>{{.Priority}}</td>
<td><span class="badge {{if eq .Effect "allow"}}badge-ok{{else}}badge-warn{{end}}">{{.Effect}}</span></td>
<td>{{if .Usernames}}{{range $i, $u := .Usernames}}{{if $i}}, {{end}}{{$u}}{{end}}{{else}}<em>any</em>{{end}}</td>
<td>{{if .Roles}}{{range $i, $r := .Roles}}{{if $i}}, {{end}}{{$r}}{{end}}{{else}}<em>any</em>{{end}}</td>
<td>{{if .Resources}}{{range $i, $r := .Resources}}{{if $i}}, {{end}}<code>{{$r}}</code>{{end}}{{else}}<em>any</em>{{end}}</td>
<td>{{if .Actions}}{{range $i, $a := .Actions}}{{if $i}}, {{end}}{{$a}}{{end}}{{else}}<em>any</em>{{end}}</td>
<td>
<form method="post" action="/policy/delete" style="display:inline">
{{csrfField}}
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="btn-danger btn-sm" onclick="return confirm('Delete rule {{.ID}}?')">Delete</button>
</form>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{else}}
<p>No policy rules defined. Admins always have full access. Non-admin users are denied by default unless a matching allow rule exists.</p>
{{end}}
</div>
<div class="card">
<div class="card-title">Create Rule</div>
<details>
<summary>Add a new policy rule</summary>
<form method="post" action="/policy">
{{csrfField}}
<div class="form-row">
<div class="form-group">
<label for="rule_id">Rule ID <span class="required">*</span></label>
<input type="text" id="rule_id" name="id" placeholder="allow-users-read" required>
</div>
<div class="form-group">
<label for="rule_priority">Priority</label>
<input type="number" id="rule_priority" name="priority" value="50" min="0" max="9999">
<small>Lower number = higher priority. Default: 50.</small>
</div>
<div class="form-group">
<label for="rule_effect">Effect <span class="required">*</span></label>
<select id="rule_effect" name="effect" required>
<option value="allow">allow</option>
<option value="deny">deny</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="rule_usernames">Usernames</label>
<input type="text" id="rule_usernames" name="usernames" placeholder="alice, bob">
<small>Comma-separated. Leave blank to match any user.</small>
</div>
<div class="form-group">
<label for="rule_roles">Roles</label>
<input type="text" id="rule_roles" name="roles" placeholder="user, guest">
<small>Comma-separated. Leave blank to match any role.</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="rule_resources">Resources</label>
<input type="text" id="rule_resources" name="resources" placeholder="engine/pki/*, engine/pki/list-certs">
<small>Comma-separated glob patterns. Leave blank to match any resource.</small>
</div>
<div class="form-group">
<label for="rule_actions">Actions</label>
<input type="text" id="rule_actions" name="actions" placeholder="read, write">
<small>Comma-separated: <code>read</code> or <code>write</code>. Leave blank to match any action.</small>
</div>
</div>
<div class="form-actions">
<button type="submit">Create Rule</button>
</div>
</form>
</details>
</div>
{{end}}