mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-12 15:08:53 +00:00
feat(mcp): 11 audit-fix MCP tools — approvals, break-glass, bootstrap, audit-category (MED-13)
Audit 2026-05-10 MED-13 closure.
WHAT.
11 new MCP tools rounding out the operator surface for workflows
that previously had GUI + CLI coverage but no MCP equivalent:
Approval workflow (4):
certctl_approval_list GET /v1/approvals approval.read
certctl_approval_get GET /v1/approvals/{id} approval.read
certctl_approval_approve POST /v1/approvals/{id}/approve approval.approve
certctl_approval_reject POST /v1/approvals/{id}/reject approval.reject
Break-glass credential admin (4):
certctl_breakglass_list GET /v1/auth/breakglass/credentials
certctl_breakglass_set_password POST /v1/auth/breakglass/credentials
certctl_breakglass_unlock POST /v1/auth/breakglass/credentials/{actor_id}/unlock
certctl_breakglass_remove DELETE /v1/auth/breakglass/credentials/{actor_id}
All gated auth.breakglass.admin; surface invisible (404 not 403)
when CERTCTL_BREAKGLASS_ENABLED=false.
Bootstrap (2):
certctl_bootstrap_status GET /v1/auth/bootstrap (auth-exempt; safe probe)
certctl_bootstrap_consume POST /v1/auth/bootstrap (auth-exempt; one-shot mint)
Audit category filter (1):
certctl_audit_list_with_category GET /v1/audit?category=<cat> audit.read
WHY.
certctl_bootstrap_consume is the load-bearing day-0 primitive: a
fresh server with no admin actors lets the holder of CERTCTL_BOOTSTRAP_TOKEN
mint a fresh admin API key. Exposing it via MCP without a security
gate would let a downstream caller mint admin from any chat
transcript / log surface that captured the bootstrap token. The
tool description carries an explicit cautious-wording comment:
CAUTION: NEVER WIRE THIS TO AUTONOMOUS OPERATION. A leaked
bootstrap token from any log, telemetry, or chat-transcript
surface lets a downstream caller mint a fresh admin API key
bypassing every other access-control gate. Run this manually,
exactly once, from a trusted shell.
Similarly certctl_breakglass_set_password's description flags
that the password crosses the MCP transport in plaintext; the
server-side handler hashes with Argon2id before persisting + the
audit row redacts, but client-side logging must NEVER capture the
payload.
HOW.
internal/mcp/tools_audit_fix.go (NEW):
registerAuditFixTools(s, c) — declares the 11 tools via
gomcp.AddTool. Each tool routes through the existing Client.Get/
Post/Delete helpers; the server-side rbacGate wrappers (or
auth-exempt allowlist, for bootstrap) handle authorization.
internal/mcp/types.go:
Adds 5 input structs:
ApprovalIDInput (get/approve/reject)
BreakglassActorIDInput (unlock/remove)
BreakglassSetPasswordInput (set_password — flagged plaintext)
BootstrapConsumeInput (token + key_name; cautious comment)
AuditListWithCategoryInput (category + optional limit/since/until/actor_id)
Each tagged with jsonschema descriptions for LLM tool discovery.
internal/mcp/tools.go:
RegisterTools now calls registerAuditFixTools after the existing
Bundle 2 Phase 9 registrar.
internal/mcp/tools_per_tool_test.go:
allHappyPathCases extended with 11 new entries. The existing
TestMCP_AllTools_HappyPath dispatches each tool via the in-memory
MCP transport against a 2xx mock backend and asserts the
wrapper-layer fence wraps the response; TestMCP_AllTools_ErrorPath
dispatches against a 5xx mock and asserts MCP_ERROR fence.
TestMCP_RegisterTools_DispatchableToolCount confirms every new
tool is dispatchable by name.
VERIFY.
- go vet ./internal/mcp/... PASS
- go test -short -count=1
-run 'TestMCP_AllTools_HappyPath|TestMCP_AllTools_ErrorPath|
TestMCP_RegisterTools_DispatchableToolCount'
./internal/mcp/... PASS
- go test -short -count=1 ./internal/mcp/... PASS (0.3s)
Refs: cowork/auth-bundles-audit-2026-05-10.md MED-13
cowork/auth-bundles-fixes-2026-05-10/HANDOFF.md item 4
This commit is contained in:
@@ -452,6 +452,19 @@ var allHappyPathCases = []toolCase{
|
||||
{"certctl_auth_remove_group_mapping", map[string]any{"id": "gm-1"}, http.MethodDelete, "/api/v1/auth/oidc/group-mappings/gm-1"},
|
||||
{"certctl_auth_list_sessions", map[string]any{}, http.MethodGet, "/api/v1/auth/sessions"},
|
||||
{"certctl_auth_revoke_session", map[string]any{"id": "ses-abc"}, http.MethodDelete, "/api/v1/auth/sessions/ses-abc"},
|
||||
|
||||
// Audit 2026-05-10 MED-13 — 11 tools (approvals + breakglass + bootstrap + audit-category).
|
||||
{"certctl_approval_list", map[string]any{}, http.MethodGet, "/api/v1/approvals"},
|
||||
{"certctl_approval_get", map[string]any{"id": "aprq-1"}, http.MethodGet, "/api/v1/approvals/aprq-1"},
|
||||
{"certctl_approval_approve", map[string]any{"id": "aprq-1"}, http.MethodPost, "/api/v1/approvals/aprq-1/approve"},
|
||||
{"certctl_approval_reject", map[string]any{"id": "aprq-1"}, http.MethodPost, "/api/v1/approvals/aprq-1/reject"},
|
||||
{"certctl_breakglass_list", map[string]any{}, http.MethodGet, "/api/v1/auth/breakglass/credentials"},
|
||||
{"certctl_breakglass_set_password", map[string]any{"actor_id": "bg-admin1", "password": "test-pass-strong-1", "role_id": "r-admin"}, http.MethodPost, "/api/v1/auth/breakglass/credentials"},
|
||||
{"certctl_breakglass_unlock", map[string]any{"actor_id": "bg-admin1"}, http.MethodPost, "/api/v1/auth/breakglass/credentials/bg-admin1/unlock"},
|
||||
{"certctl_breakglass_remove", map[string]any{"actor_id": "bg-admin1"}, http.MethodDelete, "/api/v1/auth/breakglass/credentials/bg-admin1"},
|
||||
{"certctl_bootstrap_status", map[string]any{}, http.MethodGet, "/api/v1/auth/bootstrap"},
|
||||
{"certctl_bootstrap_consume", map[string]any{"token": "test-token", "key_name": "day-zero-admin"}, http.MethodPost, "/api/v1/auth/bootstrap"},
|
||||
{"certctl_audit_list_with_category", map[string]any{"category": "auth"}, http.MethodGet, "/api/v1/audit"},
|
||||
}
|
||||
|
||||
// TestMCP_AllTools_HappyPath dispatches every tool against the mock API in
|
||||
|
||||
Reference in New Issue
Block a user