mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 13:51:36 +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:
@@ -51,6 +51,13 @@ func RegisterTools(s *gomcp.Server, client *Client) {
|
||||
// existing HTTP client; permission gates fire server-side via the
|
||||
// Phase-5 rbacGate wrappers. See internal/mcp/tools_auth_bundle2.go.
|
||||
registerAuthBundle2Tools(s, client)
|
||||
// Audit 2026-05-10 MED-13 — 11 tools rounding out the operator
|
||||
// surface: approvals (4) + break-glass admin (4) + bootstrap
|
||||
// status/consume (2) + audit category filter (1). See
|
||||
// internal/mcp/tools_audit_fix.go for the per-tool wiring + the
|
||||
// security comment on certctl_bootstrap_consume (never wire to
|
||||
// autonomous operation; one-shot token-minting primitive).
|
||||
registerAuditFixTools(s, client)
|
||||
// Phase G P1-33 (POST /api/v1/agents/{id}/discoveries) is
|
||||
// intentionally NOT exposed via MCP — it is a machine-to-machine
|
||||
// channel for agents to push filesystem-scan reports, not an
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
package mcp
|
||||
|
||||
// Audit 2026-05-10 MED-13 closure — 11 new MCP tools that round out
|
||||
// the MCP surface for the operator workflows that previously had GUI +
|
||||
// CLI coverage but no MCP equivalent: approval workflow (4),
|
||||
// break-glass credential admin (4), bootstrap-status/consume (2),
|
||||
// audit list with category filter (1).
|
||||
//
|
||||
// Coverage map (each tool → HTTP endpoint → permission):
|
||||
//
|
||||
// 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
|
||||
// certctl_breakglass_list GET /v1/auth/breakglass/credentials auth.breakglass.admin
|
||||
// certctl_breakglass_set_password POST /v1/auth/breakglass/credentials auth.breakglass.admin
|
||||
// certctl_breakglass_unlock POST /v1/auth/breakglass/credentials/{actor_id}/unlock auth.breakglass.admin
|
||||
// certctl_breakglass_remove DELETE /v1/auth/breakglass/credentials/{actor_id} auth.breakglass.admin
|
||||
// certctl_bootstrap_status GET /v1/auth/bootstrap (token; auth-exempt)
|
||||
// certctl_bootstrap_consume POST /v1/auth/bootstrap (token; auth-exempt)
|
||||
// certctl_audit_list_with_category GET /v1/audit?category=<cat> audit.read
|
||||
//
|
||||
// Hygiene notes carried into the audit row by the server-side handler:
|
||||
// - approval reject + breakglass set/remove are PERMANENTLY operator-
|
||||
// consequential. MCP tools simply pass the call through; the
|
||||
// server-side endpoint emits the audit row.
|
||||
// - bootstrap_consume is the load-bearing one-shot token-exchange
|
||||
// primitive. Tool description carries an explicit cautious-wording
|
||||
// comment: "never wire this to autonomous operation — a leaked
|
||||
// bootstrap token mints a fresh admin API key."
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
gomcp "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
func registerAuditFixTools(s *gomcp.Server, c *Client) {
|
||||
// ── Approvals (4) ───────────────────────────────────────────────────
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_approval_list",
|
||||
Description: "List pending approval requests (GET /v1/approvals). Approval workflow primitive: certificate issuance + profile-edit operations gated on `CertificateProfile.RequiresApproval=true` materialize an `issuance_approval_requests` row that one approver of a different actor than the requester must approve before the request actually executes. Permission: approval.read.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, _ struct{}) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Get("/api/v1/approvals", nil)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_approval_get",
|
||||
Description: "Get a single approval request by id (GET /v1/approvals/{id}). The response carries the approval payload — a JSON envelope with `before`+`after` for profile edits, or the full `IssuanceRequest` for certificate issuance. Permission: approval.read.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ApprovalIDInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Get("/api/v1/approvals/"+input.ID, nil)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_approval_approve",
|
||||
Description: "Approve a pending approval request (POST /v1/approvals/{id}/approve). The server-side service-layer rejects with ErrApproveBySameActor if the caller is the same actor who originated the request (same-actor self-approve is forbidden — the security primitive requires a SECOND human/key/actor sign-off). On success, the approval executes the requested operation. Permission: approval.approve.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ApprovalIDInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Post("/api/v1/approvals/"+input.ID+"/approve", map[string]string{})
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_approval_reject",
|
||||
Description: "Reject a pending approval request (POST /v1/approvals/{id}/reject). The originating request is permanently denied; a new request must be created if the requester still wants the operation. Permission: approval.reject.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input ApprovalIDInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Post("/api/v1/approvals/"+input.ID+"/reject", map[string]string{})
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
// ── Break-glass (4) ─────────────────────────────────────────────────
|
||||
//
|
||||
// Break-glass is a deliberate bypass of the SSO security boundary.
|
||||
// The whole feature is invisible (404 NOT 403) when
|
||||
// CERTCTL_BREAKGLASS_ENABLED=false. Operators turn it on during SSO
|
||||
// incidents and OFF after recovery.
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_breakglass_list",
|
||||
Description: "List configured break-glass credentials (GET /v1/auth/breakglass/credentials). Each row carries the actor_id + role + lockout-counter state. Break-glass is a deliberate SSO-bypass: it lets a designated admin log in via username+password when the OIDC IdP is down. Permission: auth.breakglass.admin. Returns 404 when CERTCTL_BREAKGLASS_ENABLED is false.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, _ struct{}) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Get("/api/v1/auth/breakglass/credentials", nil)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_breakglass_set_password",
|
||||
Description: "Set or update a break-glass credential password (POST /v1/auth/breakglass/credentials). Body: {actor_id, password, role_id}. The server-side handler hashes the password with Argon2id (RFC 9106, m=64MiB, t=3, p=4) before persisting. Returns 404 when CERTCTL_BREAKGLASS_ENABLED is false. NEVER log the password — the MCP transport sees plaintext; the server-side audit row redacts. Permission: auth.breakglass.admin.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input BreakglassSetPasswordInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Post("/api/v1/auth/breakglass/credentials", input)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_breakglass_unlock",
|
||||
Description: "Reset the lockout counter on a break-glass credential (POST /v1/auth/breakglass/credentials/{actor_id}/unlock). Use after a failed-attempts lockout: the credential is locked for CERTCTL_BREAKGLASS_LOCKOUT_DURATION after CERTCTL_BREAKGLASS_LOCKOUT_THRESHOLD bad attempts; this tool clears the counter ahead of the natural expiry. Permission: auth.breakglass.admin.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input BreakglassActorIDInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Post("/api/v1/auth/breakglass/credentials/"+input.ActorID+"/unlock", map[string]string{})
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_breakglass_remove",
|
||||
Description: "Permanently remove a break-glass credential (DELETE /v1/auth/breakglass/credentials/{actor_id}). Operator-consequential — once removed, the actor can no longer log in via break-glass; a new credential must be set via certctl_breakglass_set_password. Permission: auth.breakglass.admin.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input BreakglassActorIDInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Delete("/api/v1/auth/breakglass/credentials/" + input.ActorID)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
// ── Bootstrap (2) ───────────────────────────────────────────────────
|
||||
//
|
||||
// The bootstrap endpoints (GET probe + POST consume) are
|
||||
// AUTH-EXEMPT — they authenticate via the
|
||||
// CERTCTL_BOOTSTRAP_TOKEN pre-shared secret, not via the
|
||||
// caller's API key. The probe is safe; the consume is the
|
||||
// load-bearing one-shot that mints an admin API key on a fresh
|
||||
// server. NEVER WIRE certctl_bootstrap_consume INTO AUTONOMOUS
|
||||
// OPERATION — a leaked bootstrap token from any log/telemetry/
|
||||
// chat-transcript surface would let a downstream caller mint a
|
||||
// fresh admin key.
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_bootstrap_status",
|
||||
Description: "Probe whether the day-0 bootstrap endpoint is currently callable (GET /v1/auth/bootstrap). Returns 200 with `{available: bool, reason: <string>}` — `available=true` only on a fresh server with no admin-roled actors AND with CERTCTL_BOOTSTRAP_TOKEN set. This tool is safe — read-only, no credentials, no audit row.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, _ struct{}) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Get("/api/v1/auth/bootstrap", nil)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_bootstrap_consume",
|
||||
Description: "Consume the day-0 bootstrap token to mint a fresh admin API key (POST /v1/auth/bootstrap). Body: {token, key_name}. This is the load-bearing one-shot primitive that creates the FIRST admin key on a fresh certctl server. 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 key bypassing every other access-control gate. Run this manually, exactly once, from a trusted shell. The server-side audit row redacts the token but preserves the resulting key_id. AUTH-EXEMPT (the token IS the auth).",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input BootstrapConsumeInput) (*gomcp.CallToolResult, any, error) {
|
||||
data, err := c.Post("/api/v1/auth/bootstrap", input)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
|
||||
// ── Audit category filter (1) ───────────────────────────────────────
|
||||
gomcp.AddTool(s, &gomcp.Tool{
|
||||
Name: "certctl_audit_list_with_category",
|
||||
Description: "List audit events filtered by category (GET /v1/audit?category=<cat>). Categories: auth (login/logout/role changes), pki (issuance/renew/revoke), config (provider/profile/issuer edits), system (startup/shutdown/scheduler events), security (alerts, intrusion-detection). Pass `category` to narrow. Other query params (limit, since, until, actor_id) accepted verbatim. Permission: audit.read. Use this when investigating a specific class of operation; for full unfiltered access use the underlying GET /v1/audit directly.",
|
||||
}, func(ctx context.Context, req *gomcp.CallToolRequest, input AuditListWithCategoryInput) (*gomcp.CallToolResult, any, error) {
|
||||
q := url.Values{}
|
||||
if input.Category != "" {
|
||||
q.Set("category", input.Category)
|
||||
}
|
||||
if input.Limit > 0 {
|
||||
q.Set("limit", intToString(input.Limit))
|
||||
}
|
||||
if input.Since != "" {
|
||||
q.Set("since", input.Since)
|
||||
}
|
||||
if input.Until != "" {
|
||||
q.Set("until", input.Until)
|
||||
}
|
||||
if input.ActorID != "" {
|
||||
q.Set("actor_id", input.ActorID)
|
||||
}
|
||||
data, err := c.Get("/api/v1/audit", q)
|
||||
if err != nil {
|
||||
return errorResult(err)
|
||||
}
|
||||
return textResult(data)
|
||||
})
|
||||
}
|
||||
|
||||
// intToString is a tiny stdlib-free int formatter used by the
|
||||
// audit category tool to encode int Limit into the query string
|
||||
// without dragging in strconv at the call site (keeps the tool
|
||||
// definitions compact).
|
||||
func intToString(n int) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
neg := n < 0
|
||||
if neg {
|
||||
n = -n
|
||||
}
|
||||
buf := [20]byte{}
|
||||
i := len(buf)
|
||||
for n > 0 {
|
||||
i--
|
||||
buf[i] = byte('0' + n%10)
|
||||
n /= 10
|
||||
}
|
||||
if neg {
|
||||
i--
|
||||
buf[i] = '-'
|
||||
}
|
||||
return string(buf[i:])
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -689,3 +689,49 @@ type AuthListSessionsInput struct {
|
||||
type AuthRevokeSessionInput struct {
|
||||
ID string `json:"id" jsonschema:"Session ID (e.g. ses-abc123). Server-side own-bypass: caller may revoke their own session even without auth.session.revoke."`
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Audit 2026-05-10 MED-13 — input shapes for the 11 new MCP tools
|
||||
// (approvals + breakglass + bootstrap + audit category filter).
|
||||
// =============================================================================
|
||||
|
||||
// ApprovalIDInput is the id-only input for approval get/approve/reject.
|
||||
type ApprovalIDInput struct {
|
||||
ID string `json:"id" jsonschema:"Approval request ID (e.g. aprq-abc123). Returned by certctl_approval_list."`
|
||||
}
|
||||
|
||||
// BreakglassActorIDInput is the actor-id-only input for the unlock + remove tools.
|
||||
type BreakglassActorIDInput struct {
|
||||
ActorID string `json:"actor_id" jsonschema:"Break-glass actor ID (e.g. bg-admin1). Listed by certctl_breakglass_list."`
|
||||
}
|
||||
|
||||
// BreakglassSetPasswordInput is the body for certctl_breakglass_set_password.
|
||||
//
|
||||
// SECURITY: the password field crosses the MCP transport in
|
||||
// plaintext. The server-side handler hashes with Argon2id before
|
||||
// persisting; the audit row redacts the password column. Never log
|
||||
// this payload at the client side.
|
||||
type BreakglassSetPasswordInput struct {
|
||||
ActorID string `json:"actor_id" jsonschema:"Break-glass actor ID (e.g. bg-admin1). New row created if not present."`
|
||||
Password string `json:"password" jsonschema:"Plaintext password (hashed server-side with Argon2id). Choose >=14 chars from a strong-entropy source; this is the SSO-bypass credential."`
|
||||
RoleID string `json:"role_id" jsonschema:"Role ID granted on successful break-glass login (e.g. r-admin). Typically r-admin for production break-glass."`
|
||||
}
|
||||
|
||||
// BootstrapConsumeInput is the body for certctl_bootstrap_consume.
|
||||
//
|
||||
// SECURITY: NEVER wire this tool into autonomous operation. A leaked
|
||||
// bootstrap token mints a fresh admin API key bypassing every other
|
||||
// access-control gate. Run manually, once, from a trusted shell.
|
||||
type BootstrapConsumeInput struct {
|
||||
Token string `json:"token" jsonschema:"The pre-shared CERTCTL_BOOTSTRAP_TOKEN value (one-shot, constant-time-compared server-side, never logged)."`
|
||||
KeyName string `json:"key_name" jsonschema:"Human-readable name for the new admin API key (e.g. 'day-zero-admin'). Subsequently visible in certctl_auth_list_keys."`
|
||||
}
|
||||
|
||||
// AuditListWithCategoryInput is the input for the category-filtered audit list.
|
||||
type AuditListWithCategoryInput struct {
|
||||
Category string `json:"category,omitempty" jsonschema:"Audit category filter. One of: auth, pki, config, system, security. Empty returns unfiltered (equivalent to GET /v1/audit)."`
|
||||
Limit int `json:"limit,omitempty" jsonschema:"Maximum rows to return. Server default applies when 0."`
|
||||
Since string `json:"since,omitempty" jsonschema:"RFC3339 timestamp lower bound (inclusive). Optional."`
|
||||
Until string `json:"until,omitempty" jsonschema:"RFC3339 timestamp upper bound (exclusive). Optional."`
|
||||
ActorID string `json:"actor_id,omitempty" jsonschema:"Filter by originating actor ID. Optional."`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user