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>
This commit is contained in:
2026-03-15 19:41:11 -07:00
parent 02ee538213
commit fbd6d1af04
17 changed files with 1055 additions and 58 deletions

108
web/templates/policy.html Normal file
View File

@@ -0,0 +1,108 @@
{{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">
<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">
<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}}