- Replace requireAdmin (role-based) guards on all REST endpoints
with RequirePolicy middleware backed by the existing policy engine;
built-in admin wildcard rule (-1) preserves existing admin behaviour
while operator rules can now grant targeted access to non-admin
accounts (e.g. a system account allowed to list accounts)
- Wire policy engine into Server: loaded from DB at startup,
reloaded after every policy-rule create/update/delete so changes
take effect immediately without a server restart
- Add service_account_delegates table (migration 000008) so a human
account can be delegated permission to issue tokens for a specific
system account without holding the admin role
- Add token-download nonce mechanism: a short-lived (5 min),
single-use random nonce is stored server-side after token issuance;
the browser downloads the token as a file via
GET /token/download/{nonce} (Content-Disposition: attachment)
instead of copying from a flash message
- Add /service-accounts UI page for non-admin delegates
- Add TestPolicyEnforcement and TestPolicyDenyRule integration tests
Security:
- Policy engine uses deny-wins, default-deny semantics; admin wildcard
is a compiled-in built-in and cannot be deleted via the API
- Token download nonces are 128-bit crypto/rand values, single-use,
and expire after 5 minutes; a background goroutine evicts stale entries
- alg header validation and Ed25519 signing unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
48 lines
1.4 KiB
HTML
48 lines
1.4 KiB
HTML
{{define "service_accounts"}}{{template "base" .}}{{end}}
|
|
{{define "title"}}Service Accounts — MCIAS{{end}}
|
|
{{define "content"}}
|
|
<div class="page-header">
|
|
<h1>Service Accounts</h1>
|
|
<p class="text-muted text-small">Service accounts for which you have been granted token-issue access.</p>
|
|
</div>
|
|
{{if .DownloadNonce}}
|
|
<div class="alert alert-success" role="alert" style="margin-bottom:1rem">
|
|
Token issued.
|
|
<a class="btn btn-sm btn-secondary" style="margin-left:.5rem"
|
|
href="/token/download/{{.DownloadNonce}}">Download token as file</a>
|
|
</div>
|
|
{{end}}
|
|
{{if .Accounts}}
|
|
<div class="card">
|
|
<table>
|
|
<thead>
|
|
<tr><th>Name</th><th>Status</th><th>Action</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .Accounts}}
|
|
<tr>
|
|
<td>{{.Username}}</td>
|
|
<td><span class="badge badge-{{string .Status}}">{{string .Status}}</span></td>
|
|
<td>
|
|
<button class="btn btn-sm btn-secondary"
|
|
hx-post="/accounts/{{.UUID}}/token"
|
|
hx-target="#issue-result-{{.UUID}}"
|
|
hx-swap="outerHTML">Issue Token</button>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="3">
|
|
<div id="issue-result-{{.UUID}}"></div>
|
|
</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{else}}
|
|
<div class="card">
|
|
<p class="text-muted text-small">You have not been granted access to any service accounts.</p>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|