mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:21:30 +00:00
feat: M18a — MCP server exposing all 76 API endpoints as AI-native tools
Separate standalone binary (cmd/mcp-server/) using official MCP Go SDK (modelcontextprotocol/go-sdk v1.4.1) with stdio transport. Stateless HTTP proxy translates MCP tool calls to certctl REST API requests. 76 tools across 16 resource domains with typed input structs and jsonschema tags for automatic LLM-friendly schema generation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -208,6 +208,39 @@ Agent environment variables:
|
||||
|
||||
Docker Compose overrides these for the demo stack (see `deploy/docker-compose.yml`): port `8443`, auth type `none`, database pointing to the postgres container.
|
||||
|
||||
## MCP Server (AI Integration)
|
||||
|
||||
certctl ships a standalone MCP (Model Context Protocol) server that exposes all 76 API endpoints as tools for AI assistants — Claude, Cursor, Windsurf, OpenClaw, VS Code Copilot, and any MCP-compatible client.
|
||||
|
||||
```bash
|
||||
# Install
|
||||
go install github.com/shankar0123/certctl/cmd/mcp-server@latest
|
||||
|
||||
# Configure
|
||||
export CERTCTL_SERVER_URL=http://localhost:8443 # certctl API endpoint
|
||||
export CERTCTL_API_KEY=your-api-key # optional if auth disabled
|
||||
|
||||
# Run (stdio transport — add to your AI client config)
|
||||
mcp-server
|
||||
```
|
||||
|
||||
**Claude Desktop** (`claude_desktop_config.json`):
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"certctl": {
|
||||
"command": "mcp-server",
|
||||
"env": {
|
||||
"CERTCTL_SERVER_URL": "http://localhost:8443",
|
||||
"CERTCTL_API_KEY": "your-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
76 tools organized by resource: certificates (9), CRL/OCSP (3), issuers (6), targets (5), agents (8), jobs (5), policies (6), profiles (5), teams (5), owners (5), agent groups (6), audit (2), notifications (3), stats (5), metrics (1), health (4).
|
||||
|
||||
## API Overview
|
||||
|
||||
All endpoints are under `/api/v1/` and return JSON. List endpoints support pagination (`?page=1&per_page=50`). Full request/response schemas are available in the [OpenAPI 3.1 spec](api/openapi.yaml).
|
||||
@@ -417,7 +450,7 @@ All nine development milestones (M1–M9) are complete. The backend covers the f
|
||||
- **M15b: OCSP + Revocation GUI** ✅ — embedded OCSP responder (GET /api/v1/ocsp/{issuer_id}/{serial}), DER-encoded X.509 CRL (GET /api/v1/crl/{issuer_id}), short-lived cert exemption (TTL < 1h skip CRL/OCSP), revocation GUI with reason modal, ~31 new tests
|
||||
- **M13: GUI Operations** ✅ — bulk cert operations (multi-select → renew, revoke, reassign owner), deployment status timeline, inline policy/profile editor, target connector configuration wizard, audit trail export (CSV/JSON), short-lived credentials dashboard view
|
||||
- **M14: Observability** ✅ — dashboard charts (expiration heatmap, cert status distribution, job trends, issuance rate), agent fleet overview with OS/arch grouping, JSON metrics endpoint, stats API (5 endpoints), structured logging with request IDs, deployment rollback
|
||||
- **M18a: MCP Server** (V2.1) — AI-native integration, expose REST API as MCP tools for Claude, Cursor, OpenClaw, and any MCP-compatible client
|
||||
- **M18a: MCP Server** ✅ (V2.1) — AI-native integration, all 76 REST API endpoints exposed as MCP tools for Claude, Cursor, OpenClaw, and any MCP-compatible client
|
||||
- **M19: Immutable API Audit Log** — extend audit trail to log every API call (method, path, actor, status, latency), queryable via existing audit endpoint
|
||||
- **M16a: Notifier Connectors** — Slack, Microsoft Teams, PagerDuty, OpsGenie notification integrations (parallel with M19)
|
||||
- **M20: Enhanced Query API** — sparse field selection (`?fields=`), sort params, time-range filters, cursor pagination, `updatedAfter` for incremental agent sync, per-cert deployment history endpoint
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
gomcp "github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
|
||||
"github.com/shankar0123/certctl/internal/mcp"
|
||||
)
|
||||
|
||||
// Version is set at build time via -ldflags.
|
||||
var Version = "dev"
|
||||
|
||||
func main() {
|
||||
serverURL := os.Getenv("CERTCTL_SERVER_URL")
|
||||
if serverURL == "" {
|
||||
serverURL = "http://localhost:8443"
|
||||
}
|
||||
|
||||
apiKey := os.Getenv("CERTCTL_API_KEY")
|
||||
|
||||
client := mcp.NewClient(serverURL, apiKey)
|
||||
|
||||
server := gomcp.NewServer(&gomcp.Implementation{
|
||||
Name: "certctl",
|
||||
Version: Version,
|
||||
}, nil)
|
||||
|
||||
mcp.RegisterTools(server, client)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer stop()
|
||||
|
||||
fmt.Fprintf(os.Stderr, "certctl MCP server %s (backend: %s)\n", Version, serverURL)
|
||||
|
||||
if err := server.Run(ctx, &gomcp.StdioTransport{}); err != nil {
|
||||
log.Fatalf("MCP server error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ go 1.22.0
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1
|
||||
)
|
||||
|
||||
require golang.org/x/crypto v0.31.0
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package mcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client is a thin HTTP client that forwards requests to the certctl REST API.
|
||||
// It handles auth, base URL resolution, and JSON marshaling.
|
||||
type Client struct {
|
||||
baseURL string
|
||||
apiKey string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new certctl API client.
|
||||
func NewClient(baseURL, apiKey string) *Client {
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
apiKey: apiKey,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get performs an HTTP GET and returns the raw JSON response body.
|
||||
func (c *Client) Get(path string, query url.Values) (json.RawMessage, error) {
|
||||
return c.do("GET", path, query, nil)
|
||||
}
|
||||
|
||||
// Post performs an HTTP POST with a JSON body and returns the raw JSON response.
|
||||
func (c *Client) Post(path string, body interface{}) (json.RawMessage, error) {
|
||||
return c.do("POST", path, nil, body)
|
||||
}
|
||||
|
||||
// Put performs an HTTP PUT with a JSON body and returns the raw JSON response.
|
||||
func (c *Client) Put(path string, body interface{}) (json.RawMessage, error) {
|
||||
return c.do("PUT", path, nil, body)
|
||||
}
|
||||
|
||||
// Delete performs an HTTP DELETE and returns the raw JSON response (may be empty for 204).
|
||||
func (c *Client) Delete(path string) (json.RawMessage, error) {
|
||||
return c.do("DELETE", path, nil, nil)
|
||||
}
|
||||
|
||||
// GetRaw performs an HTTP GET and returns the raw response body bytes and content type.
|
||||
// Used for binary responses (DER CRL, OCSP).
|
||||
func (c *Client) GetRaw(path string) ([]byte, string, error) {
|
||||
u, err := url.JoinPath(c.baseURL, path)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
if c.apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, "", fmt.Errorf("API error (HTTP %d): %s", resp.StatusCode, string(data))
|
||||
}
|
||||
|
||||
return data, resp.Header.Get("Content-Type"), nil
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, query url.Values, body interface{}) (json.RawMessage, error) {
|
||||
u, err := url.JoinPath(c.baseURL, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
if query != nil && len(query) > 0 {
|
||||
u = u + "?" + query.Encode()
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshaling request body: %w", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u, bodyReader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
if c.apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading response: %w", err)
|
||||
}
|
||||
|
||||
// 204 No Content — return empty JSON object
|
||||
if resp.StatusCode == 204 {
|
||||
return json.RawMessage(`{"status":"deleted"}`), nil
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, fmt.Errorf("API error (HTTP %d): %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return json.RawMessage(respBody), nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,269 @@
|
||||
package mcp
|
||||
|
||||
// Input types for MCP tool arguments.
|
||||
// The jsonschema struct tags provide descriptions for LLM tool discovery.
|
||||
|
||||
// ── Pagination ──────────────────────────────────────────────────────
|
||||
|
||||
type ListParams struct {
|
||||
Page int `json:"page,omitempty" jsonschema:"Page number (default 1)"`
|
||||
PerPage int `json:"per_page,omitempty" jsonschema:"Results per page (default 50, max 500)"`
|
||||
}
|
||||
|
||||
// ── Certificates ────────────────────────────────────────────────────
|
||||
|
||||
type ListCertificatesInput struct {
|
||||
ListParams
|
||||
Status string `json:"status,omitempty" jsonschema:"Filter by status: Pending, Active, Expiring, Expired, RenewalInProgress, Failed, Revoked, Archived"`
|
||||
Environment string `json:"environment,omitempty" jsonschema:"Filter by environment"`
|
||||
OwnerID string `json:"owner_id,omitempty" jsonschema:"Filter by owner ID"`
|
||||
TeamID string `json:"team_id,omitempty" jsonschema:"Filter by team ID"`
|
||||
IssuerID string `json:"issuer_id,omitempty" jsonschema:"Filter by issuer ID"`
|
||||
}
|
||||
|
||||
type GetByIDInput struct {
|
||||
ID string `json:"id" jsonschema:"Resource ID (e.g. mc-api-prod, t-platform)"`
|
||||
}
|
||||
|
||||
type CreateCertificateInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Certificate ID (auto-generated if empty)"`
|
||||
Name string `json:"name" jsonschema:"Display name"`
|
||||
CommonName string `json:"common_name" jsonschema:"Certificate common name (e.g. api.example.com)"`
|
||||
SANs []string `json:"sans,omitempty" jsonschema:"Subject Alternative Names"`
|
||||
Environment string `json:"environment,omitempty" jsonschema:"Environment (e.g. production, staging)"`
|
||||
OwnerID string `json:"owner_id,omitempty" jsonschema:"Owner ID"`
|
||||
TeamID string `json:"team_id,omitempty" jsonschema:"Team ID"`
|
||||
IssuerID string `json:"issuer_id" jsonschema:"Issuer connector ID"`
|
||||
TargetIDs []string `json:"target_ids,omitempty" jsonschema:"Deployment target IDs"`
|
||||
RenewalPolicyID string `json:"renewal_policy_id,omitempty" jsonschema:"Renewal policy ID"`
|
||||
ProfileID string `json:"certificate_profile_id,omitempty" jsonschema:"Certificate profile ID"`
|
||||
Tags map[string]string `json:"tags,omitempty" jsonschema:"Key-value tags"`
|
||||
}
|
||||
|
||||
type UpdateCertificateInput struct {
|
||||
ID string `json:"id" jsonschema:"Certificate ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Display name"`
|
||||
Environment string `json:"environment,omitempty" jsonschema:"Environment"`
|
||||
OwnerID string `json:"owner_id,omitempty" jsonschema:"Owner ID"`
|
||||
TeamID string `json:"team_id,omitempty" jsonschema:"Team ID"`
|
||||
TargetIDs []string `json:"target_ids,omitempty" jsonschema:"Deployment target IDs"`
|
||||
RenewalPolicyID string `json:"renewal_policy_id,omitempty" jsonschema:"Renewal policy ID"`
|
||||
ProfileID string `json:"certificate_profile_id,omitempty" jsonschema:"Certificate profile ID"`
|
||||
Tags map[string]string `json:"tags,omitempty" jsonschema:"Key-value tags"`
|
||||
}
|
||||
|
||||
type TriggerDeploymentInput struct {
|
||||
ID string `json:"id" jsonschema:"Certificate ID"`
|
||||
TargetID string `json:"target_id,omitempty" jsonschema:"Optional specific target ID"`
|
||||
}
|
||||
|
||||
type RevokeCertificateInput struct {
|
||||
ID string `json:"id" jsonschema:"Certificate ID to revoke"`
|
||||
Reason string `json:"reason,omitempty" jsonschema:"RFC 5280 reason: unspecified, keyCompromise, caCompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold, privilegeWithdrawn"`
|
||||
}
|
||||
|
||||
type ListVersionsInput struct {
|
||||
ID string `json:"id" jsonschema:"Certificate ID"`
|
||||
ListParams
|
||||
}
|
||||
|
||||
// ── CRL & OCSP ──────────────────────────────────────────────────────
|
||||
|
||||
type GetDERCRLInput struct {
|
||||
IssuerID string `json:"issuer_id" jsonschema:"Issuer ID for DER-encoded CRL"`
|
||||
}
|
||||
|
||||
type OCSPInput struct {
|
||||
IssuerID string `json:"issuer_id" jsonschema:"Issuer ID"`
|
||||
Serial string `json:"serial" jsonschema:"Hex-encoded certificate serial number"`
|
||||
}
|
||||
|
||||
// ── Issuers ─────────────────────────────────────────────────────────
|
||||
|
||||
type CreateIssuerInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Issuer ID"`
|
||||
Name string `json:"name" jsonschema:"Issuer display name"`
|
||||
Type string `json:"type" jsonschema:"Issuer type: ACME, GenericCA, StepCA"`
|
||||
Config interface{} `json:"config,omitempty" jsonschema:"Issuer-specific configuration"`
|
||||
Enabled bool `json:"enabled,omitempty" jsonschema:"Whether the issuer is enabled"`
|
||||
}
|
||||
|
||||
type UpdateIssuerInput struct {
|
||||
ID string `json:"id" jsonschema:"Issuer ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Issuer display name"`
|
||||
Type string `json:"type,omitempty" jsonschema:"Issuer type"`
|
||||
Config interface{} `json:"config,omitempty" jsonschema:"Issuer-specific configuration"`
|
||||
Enabled *bool `json:"enabled,omitempty" jsonschema:"Whether the issuer is enabled"`
|
||||
}
|
||||
|
||||
// ── Targets ─────────────────────────────────────────────────────────
|
||||
|
||||
type CreateTargetInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Target ID"`
|
||||
Name string `json:"name" jsonschema:"Target display name"`
|
||||
Type string `json:"type" jsonschema:"Target type: NGINX, Apache, HAProxy, F5, IIS"`
|
||||
AgentID string `json:"agent_id,omitempty" jsonschema:"Agent ID that manages this target"`
|
||||
Config interface{} `json:"config,omitempty" jsonschema:"Target-specific configuration"`
|
||||
Enabled bool `json:"enabled,omitempty" jsonschema:"Whether the target is enabled"`
|
||||
}
|
||||
|
||||
type UpdateTargetInput struct {
|
||||
ID string `json:"id" jsonschema:"Target ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Target display name"`
|
||||
Type string `json:"type,omitempty" jsonschema:"Target type"`
|
||||
AgentID string `json:"agent_id,omitempty" jsonschema:"Agent ID"`
|
||||
Config interface{} `json:"config,omitempty" jsonschema:"Target-specific configuration"`
|
||||
Enabled *bool `json:"enabled,omitempty" jsonschema:"Whether the target is enabled"`
|
||||
}
|
||||
|
||||
// ── Agents ──────────────────────────────────────────────────────────
|
||||
|
||||
type RegisterAgentInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Agent ID"`
|
||||
Name string `json:"name" jsonschema:"Agent display name"`
|
||||
Hostname string `json:"hostname" jsonschema:"Agent hostname"`
|
||||
}
|
||||
|
||||
type AgentCSRInput struct {
|
||||
AgentID string `json:"agent_id" jsonschema:"Agent ID"`
|
||||
CSRPEM string `json:"csr_pem" jsonschema:"PEM-encoded certificate signing request"`
|
||||
CertificateID string `json:"certificate_id,omitempty" jsonschema:"Certificate ID for the CSR"`
|
||||
}
|
||||
|
||||
type AgentPickupInput struct {
|
||||
AgentID string `json:"agent_id" jsonschema:"Agent ID"`
|
||||
CertID string `json:"cert_id" jsonschema:"Certificate ID to pick up"`
|
||||
}
|
||||
|
||||
type AgentJobStatusInput struct {
|
||||
AgentID string `json:"agent_id" jsonschema:"Agent ID"`
|
||||
JobID string `json:"job_id" jsonschema:"Job ID"`
|
||||
Status string `json:"status" jsonschema:"Job status to report"`
|
||||
Error string `json:"error,omitempty" jsonschema:"Error message if job failed"`
|
||||
}
|
||||
|
||||
// ── Jobs ────────────────────────────────────────────────────────────
|
||||
|
||||
type ListJobsInput struct {
|
||||
ListParams
|
||||
Status string `json:"status,omitempty" jsonschema:"Filter by status: Pending, AwaitingCSR, AwaitingApproval, Running, Completed, Failed, Cancelled"`
|
||||
Type string `json:"type,omitempty" jsonschema:"Filter by type: Issuance, Renewal, Deployment, Validation"`
|
||||
}
|
||||
|
||||
type RejectJobInput struct {
|
||||
ID string `json:"id" jsonschema:"Job ID to reject"`
|
||||
Reason string `json:"reason,omitempty" jsonschema:"Reason for rejection"`
|
||||
}
|
||||
|
||||
// ── Policies ────────────────────────────────────────────────────────
|
||||
|
||||
type CreatePolicyInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Policy ID"`
|
||||
Name string `json:"name" jsonschema:"Policy display name"`
|
||||
Type string `json:"type" jsonschema:"Policy type: AllowedIssuers, AllowedDomains, RequiredMetadata, AllowedEnvironments, RenewalLeadTime"`
|
||||
Config interface{} `json:"config,omitempty" jsonschema:"Policy-specific configuration"`
|
||||
Enabled bool `json:"enabled,omitempty" jsonschema:"Whether the policy is enabled"`
|
||||
}
|
||||
|
||||
type UpdatePolicyInput struct {
|
||||
ID string `json:"id" jsonschema:"Policy ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Policy display name"`
|
||||
Type string `json:"type,omitempty" jsonschema:"Policy type"`
|
||||
Config interface{} `json:"config,omitempty" jsonschema:"Policy-specific configuration"`
|
||||
Enabled *bool `json:"enabled,omitempty" jsonschema:"Whether the policy is enabled"`
|
||||
}
|
||||
|
||||
type ListViolationsInput struct {
|
||||
ID string `json:"id" jsonschema:"Policy ID"`
|
||||
ListParams
|
||||
}
|
||||
|
||||
// ── Profiles ────────────────────────────────────────────────────────
|
||||
|
||||
type CreateProfileInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Profile ID"`
|
||||
Name string `json:"name" jsonschema:"Profile display name"`
|
||||
Description string `json:"description,omitempty" jsonschema:"Profile description"`
|
||||
AllowedKeyAlgorithms interface{} `json:"allowed_key_algorithms,omitempty" jsonschema:"Allowed key algorithms and minimum sizes"`
|
||||
MaxTTLSeconds int `json:"max_ttl_seconds,omitempty" jsonschema:"Maximum certificate TTL in seconds"`
|
||||
AllowedEKUs []string `json:"allowed_ekus,omitempty" jsonschema:"Allowed Extended Key Usages"`
|
||||
RequiredSANPatterns []string `json:"required_san_patterns,omitempty" jsonschema:"Required SAN patterns"`
|
||||
AllowShortLived bool `json:"allow_short_lived,omitempty" jsonschema:"Allow short-lived certificates (TTL < 1 hour)"`
|
||||
Enabled bool `json:"enabled,omitempty" jsonschema:"Whether the profile is enabled"`
|
||||
}
|
||||
|
||||
type UpdateProfileInput struct {
|
||||
ID string `json:"id" jsonschema:"Profile ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Profile display name"`
|
||||
Description string `json:"description,omitempty" jsonschema:"Profile description"`
|
||||
AllowedKeyAlgorithms interface{} `json:"allowed_key_algorithms,omitempty" jsonschema:"Allowed key algorithms and minimum sizes"`
|
||||
MaxTTLSeconds *int `json:"max_ttl_seconds,omitempty" jsonschema:"Maximum certificate TTL in seconds"`
|
||||
AllowedEKUs []string `json:"allowed_ekus,omitempty" jsonschema:"Allowed Extended Key Usages"`
|
||||
RequiredSANPatterns []string `json:"required_san_patterns,omitempty" jsonschema:"Required SAN patterns"`
|
||||
AllowShortLived *bool `json:"allow_short_lived,omitempty" jsonschema:"Allow short-lived certificates"`
|
||||
Enabled *bool `json:"enabled,omitempty" jsonschema:"Whether the profile is enabled"`
|
||||
}
|
||||
|
||||
// ── Teams ───────────────────────────────────────────────────────────
|
||||
|
||||
type CreateTeamInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Team ID"`
|
||||
Name string `json:"name" jsonschema:"Team name"`
|
||||
Description string `json:"description,omitempty" jsonschema:"Team description"`
|
||||
}
|
||||
|
||||
type UpdateTeamInput struct {
|
||||
ID string `json:"id" jsonschema:"Team ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Team name"`
|
||||
Description string `json:"description,omitempty" jsonschema:"Team description"`
|
||||
}
|
||||
|
||||
// ── Owners ──────────────────────────────────────────────────────────
|
||||
|
||||
type CreateOwnerInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Owner ID"`
|
||||
Name string `json:"name" jsonschema:"Owner display name"`
|
||||
Email string `json:"email,omitempty" jsonschema:"Owner email for notifications"`
|
||||
TeamID string `json:"team_id,omitempty" jsonschema:"Team ID the owner belongs to"`
|
||||
}
|
||||
|
||||
type UpdateOwnerInput struct {
|
||||
ID string `json:"id" jsonschema:"Owner ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Owner display name"`
|
||||
Email string `json:"email,omitempty" jsonschema:"Owner email"`
|
||||
TeamID string `json:"team_id,omitempty" jsonschema:"Team ID"`
|
||||
}
|
||||
|
||||
// ── Agent Groups ────────────────────────────────────────────────────
|
||||
|
||||
type CreateAgentGroupInput struct {
|
||||
ID string `json:"id,omitempty" jsonschema:"Agent group ID"`
|
||||
Name string `json:"name" jsonschema:"Group display name"`
|
||||
Description string `json:"description,omitempty" jsonschema:"Group description"`
|
||||
MatchOS string `json:"match_os,omitempty" jsonschema:"Match agents by OS (e.g. linux, darwin, windows)"`
|
||||
MatchArchitecture string `json:"match_architecture,omitempty" jsonschema:"Match agents by architecture (e.g. amd64, arm64)"`
|
||||
MatchIPCIDR string `json:"match_ip_cidr,omitempty" jsonschema:"Match agents by IP CIDR range"`
|
||||
MatchVersion string `json:"match_version,omitempty" jsonschema:"Match agents by version"`
|
||||
Enabled bool `json:"enabled,omitempty" jsonschema:"Whether the group is enabled"`
|
||||
}
|
||||
|
||||
type UpdateAgentGroupInput struct {
|
||||
ID string `json:"id" jsonschema:"Agent group ID to update"`
|
||||
Name string `json:"name,omitempty" jsonschema:"Group display name"`
|
||||
Description string `json:"description,omitempty" jsonschema:"Group description"`
|
||||
MatchOS string `json:"match_os,omitempty" jsonschema:"Match agents by OS"`
|
||||
MatchArchitecture string `json:"match_architecture,omitempty" jsonschema:"Match agents by architecture"`
|
||||
MatchIPCIDR string `json:"match_ip_cidr,omitempty" jsonschema:"Match agents by IP CIDR range"`
|
||||
MatchVersion string `json:"match_version,omitempty" jsonschema:"Match agents by version"`
|
||||
Enabled *bool `json:"enabled,omitempty" jsonschema:"Whether the group is enabled"`
|
||||
}
|
||||
|
||||
// ── Stats ───────────────────────────────────────────────────────────
|
||||
|
||||
type TimelineInput struct {
|
||||
Days int `json:"days,omitempty" jsonschema:"Number of days to look back (default 30, max 365)"`
|
||||
}
|
||||
|
||||
// ── Empty ───────────────────────────────────────────────────────────
|
||||
|
||||
type EmptyInput struct{}
|
||||
Reference in New Issue
Block a user