mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 16:31:33 +00:00
3f619bcaac
Add three new issuer connectors completing commercial and open-source CA coverage. Entrust uses mTLS client certificate auth with sync/async issuance. GlobalSign Atlas uses mTLS + API key/secret dual auth with serial-based tracking. EJBCA supports dual auth (mTLS or OAuth2) for self-hosted Keyfactor CAs. Each connector implements the full issuer.Connector interface (9 methods), includes httptest-based unit tests (~14 each), and follows established patterns (injectable HTTP clients, RFC 5280 revocation reason mapping, CRL/OCSP delegated to CA). Also includes: issuer factory cases, env var seeding, config structs, domain types, seed data (3 rows, all disabled), OpenAPI enum updates, frontend issuer catalog entries with config fields, and full docs (connectors.md, architecture.md, features.md, README). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3727 lines
110 KiB
YAML
3727 lines
110 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: certctl API
|
|
description: |
|
|
Certificate lifecycle management platform API. Manages certificates, issuers,
|
|
deployment targets, agents, jobs, policies, profiles, teams, owners, agent groups,
|
|
audit events, notifications, and observability metrics.
|
|
|
|
All endpoints under `/api/v1/` require authentication by default (configurable via
|
|
`CERTCTL_AUTH_TYPE`). Use `Bearer {api_key}` in the Authorization header.
|
|
|
|
Paginated list endpoints accept `page` (default 1) and `per_page` (default 50, max 500)
|
|
query parameters and return a standard envelope with `data`, `total`, `page`, and `per_page`.
|
|
version: 2.0.0
|
|
license:
|
|
name: BSL 1.1
|
|
url: https://github.com/shankar0123/certctl/blob/master/LICENSE
|
|
|
|
servers:
|
|
- url: http://localhost:8080
|
|
description: Local development
|
|
- url: http://localhost:8443
|
|
description: Docker Compose demo
|
|
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
tags:
|
|
- name: Certificates
|
|
description: Certificate lifecycle — CRUD, versions, renewal, deployment, revocation
|
|
- name: CRL & OCSP
|
|
description: Certificate revocation list and OCSP responder
|
|
- name: Issuers
|
|
description: CA issuer connector management (Local CA, ACME, step-ca)
|
|
- name: Targets
|
|
description: Deployment target management (NGINX, Apache, HAProxy, F5, IIS)
|
|
- name: Agents
|
|
description: Agent registration, heartbeat, CSR submission, work polling
|
|
- name: Jobs
|
|
description: Job queue — issuance, renewal, deployment, validation
|
|
- name: Policies
|
|
description: Policy rules and violation tracking
|
|
- name: Profiles
|
|
description: Certificate enrollment profiles with crypto constraints
|
|
- name: Teams
|
|
description: Team management for ownership grouping
|
|
- name: Owners
|
|
description: Certificate owner management with email routing
|
|
- name: Agent Groups
|
|
description: Dynamic agent grouping by OS, architecture, IP CIDR, version
|
|
- name: Audit
|
|
description: Immutable audit trail
|
|
- name: Notifications
|
|
description: Notification events (expiration, renewal, deployment, revocation)
|
|
- name: Stats
|
|
description: Dashboard statistics and aggregations
|
|
- name: Metrics
|
|
description: System metrics (gauges, counters, uptime)
|
|
- name: Health
|
|
description: Health and readiness probes, auth info
|
|
- name: Discovery
|
|
description: Certificate discovery — filesystem scanning by agents and network TLS probing
|
|
- name: Network Scan
|
|
description: Network scan target management for active TLS certificate discovery
|
|
- name: Health Monitoring
|
|
description: Continuous TLS endpoint health checks with status tracking and probe history
|
|
- name: Digest
|
|
description: Scheduled certificate digest email notifications
|
|
|
|
paths:
|
|
# ─── Health & Auth ───────────────────────────────────────────────────
|
|
/health:
|
|
get:
|
|
tags: [Health]
|
|
summary: Health check
|
|
security: []
|
|
operationId: getHealth
|
|
responses:
|
|
"200":
|
|
description: Server is healthy
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: healthy
|
|
|
|
/ready:
|
|
get:
|
|
tags: [Health]
|
|
summary: Readiness check
|
|
security: []
|
|
operationId: getReady
|
|
responses:
|
|
"200":
|
|
description: Server is ready
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: ready
|
|
|
|
/api/v1/auth/info:
|
|
get:
|
|
tags: [Health]
|
|
summary: Auth configuration info
|
|
description: Returns auth mode. Served without auth so GUI can detect auth requirements before login.
|
|
security: []
|
|
operationId: getAuthInfo
|
|
responses:
|
|
"200":
|
|
description: Auth configuration
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
auth_type:
|
|
type: string
|
|
enum: [api-key, jwt, none]
|
|
required:
|
|
type: boolean
|
|
|
|
/api/v1/auth/check:
|
|
get:
|
|
tags: [Health]
|
|
summary: Validate credentials
|
|
description: Returns 200 if auth credentials are valid, 401 otherwise.
|
|
operationId: checkAuth
|
|
responses:
|
|
"200":
|
|
description: Authenticated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: authenticated
|
|
"401":
|
|
description: Unauthorized
|
|
|
|
# ─── Certificates ────────────────────────────────────────────────────
|
|
/api/v1/certificates:
|
|
get:
|
|
tags: [Certificates]
|
|
summary: List certificates
|
|
operationId: listCertificates
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/CertificateStatus"
|
|
- name: environment
|
|
in: query
|
|
schema:
|
|
type: string
|
|
- name: owner_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
- name: team_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
- name: issuer_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Paginated list of certificates
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/ManagedCertificate"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Certificates]
|
|
summary: Create certificate
|
|
operationId: createCertificate
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ManagedCertificate"
|
|
responses:
|
|
"201":
|
|
description: Certificate created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ManagedCertificate"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/certificates/{id}:
|
|
get:
|
|
tags: [Certificates]
|
|
summary: Get certificate
|
|
operationId: getCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Certificate details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ManagedCertificate"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Certificates]
|
|
summary: Update certificate
|
|
operationId: updateCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ManagedCertificate"
|
|
responses:
|
|
"200":
|
|
description: Certificate updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ManagedCertificate"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Certificates]
|
|
summary: Archive certificate
|
|
operationId: archiveCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Certificate archived
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/certificates/{id}/versions:
|
|
get:
|
|
tags: [Certificates]
|
|
summary: List certificate versions
|
|
operationId: listCertificateVersions
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of certificate versions
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/CertificateVersion"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/certificates/{id}/renew:
|
|
post:
|
|
tags: [Certificates]
|
|
summary: Trigger certificate renewal
|
|
operationId: triggerRenewal
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"202":
|
|
description: Renewal triggered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/certificates/{id}/deploy:
|
|
post:
|
|
tags: [Certificates]
|
|
summary: Trigger certificate deployment
|
|
operationId: triggerDeployment
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
target_id:
|
|
type: string
|
|
description: Optional specific target ID
|
|
responses:
|
|
"202":
|
|
description: Deployment triggered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/certificates/{id}/revoke:
|
|
post:
|
|
tags: [Certificates]
|
|
summary: Revoke certificate
|
|
description: |
|
|
Revokes a certificate with an optional RFC 5280 reason code. Records revocation in
|
|
cert inventory, audit log, and certificate_revocations table. Best-effort issuer notification.
|
|
operationId: revokeCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
reason:
|
|
$ref: "#/components/schemas/RevocationReason"
|
|
responses:
|
|
"200":
|
|
description: Certificate revoked
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Certificate Export ──────────────────────────────────────────────
|
|
/api/v1/certificates/{id}/export/pem:
|
|
get:
|
|
tags: [Certificates]
|
|
summary: Export certificate as PEM
|
|
description: |
|
|
Returns the certificate and its chain in PEM format. By default returns JSON
|
|
with cert_pem, chain_pem, and full_pem fields. Add ?download=true to get the
|
|
full PEM chain as a file download with Content-Disposition headers.
|
|
operationId: exportCertificatePEM
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
- name: download
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: ["true"]
|
|
description: Set to "true" to get a file download instead of JSON.
|
|
responses:
|
|
"200":
|
|
description: PEM export
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
cert_pem:
|
|
type: string
|
|
description: Leaf certificate PEM
|
|
chain_pem:
|
|
type: string
|
|
description: Intermediate/root chain PEM
|
|
full_pem:
|
|
type: string
|
|
description: Full PEM chain (cert + intermediates)
|
|
application/x-pem-file:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
description: Full PEM file (when download=true)
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/certificates/{id}/export/pkcs12:
|
|
post:
|
|
tags: [Certificates]
|
|
summary: Export certificate as PKCS#12
|
|
description: |
|
|
Returns a PKCS#12 (.p12) bundle containing the certificate and chain.
|
|
Private keys are NOT included — they live on agents and never touch the control plane.
|
|
The bundle is encrypted with the provided password (or empty password if omitted).
|
|
operationId: exportCertificatePKCS12
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
password:
|
|
type: string
|
|
description: Password to encrypt the PKCS#12 bundle (can be empty)
|
|
responses:
|
|
"200":
|
|
description: PKCS#12 binary
|
|
content:
|
|
application/x-pkcs12:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── CRL & OCSP ─────────────────────────────────────────────────────
|
|
/api/v1/crl:
|
|
get:
|
|
tags: [CRL & OCSP]
|
|
summary: Get JSON CRL
|
|
description: Returns all revoked certificates in JSON format.
|
|
operationId: getCRL
|
|
responses:
|
|
"200":
|
|
description: JSON CRL
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
version:
|
|
type: integer
|
|
example: 1
|
|
entries:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
serial_number:
|
|
type: string
|
|
revocation_date:
|
|
type: string
|
|
format: date-time
|
|
revocation_reason:
|
|
type: string
|
|
total:
|
|
type: integer
|
|
generated_at:
|
|
type: string
|
|
format: date-time
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/crl/{issuer_id}:
|
|
get:
|
|
tags: [CRL & OCSP]
|
|
summary: Get DER-encoded X.509 CRL
|
|
description: Returns a proper DER-encoded CRL signed by the issuing CA. 24-hour validity.
|
|
operationId: getDERCRL
|
|
parameters:
|
|
- name: issuer_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: DER-encoded CRL
|
|
content:
|
|
application/pkix-crl:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
"501":
|
|
description: Issuer does not support CRL generation
|
|
|
|
/api/v1/ocsp/{issuer_id}/{serial}:
|
|
get:
|
|
tags: [CRL & OCSP]
|
|
summary: OCSP responder
|
|
description: Returns signed OCSP response (good/revoked/unknown) for the given serial number.
|
|
operationId: handleOCSP
|
|
parameters:
|
|
- name: issuer_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
- name: serial
|
|
in: path
|
|
required: true
|
|
description: Hex-encoded certificate serial number
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: OCSP response
|
|
content:
|
|
application/ocsp-response:
|
|
schema:
|
|
type: string
|
|
format: binary
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
"501":
|
|
description: Issuer does not support OCSP
|
|
|
|
# ─── Issuers ─────────────────────────────────────────────────────────
|
|
/api/v1/issuers:
|
|
get:
|
|
tags: [Issuers]
|
|
summary: List issuers
|
|
operationId: listIssuers
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of issuers
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Issuer"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Issuers]
|
|
summary: Create issuer
|
|
operationId: createIssuer
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Issuer"
|
|
responses:
|
|
"201":
|
|
description: Issuer created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Issuer"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/issuers/{id}:
|
|
get:
|
|
tags: [Issuers]
|
|
summary: Get issuer
|
|
operationId: getIssuer
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Issuer details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Issuer"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Issuers]
|
|
summary: Update issuer
|
|
operationId: updateIssuer
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Issuer"
|
|
responses:
|
|
"200":
|
|
description: Issuer updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Issuer"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Issuers]
|
|
summary: Delete issuer
|
|
operationId: deleteIssuer
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Issuer deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/issuers/{id}/test:
|
|
post:
|
|
tags: [Issuers]
|
|
summary: Test issuer connection
|
|
operationId: testIssuerConnection
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Connection successful
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Targets ─────────────────────────────────────────────────────────
|
|
/api/v1/targets:
|
|
get:
|
|
tags: [Targets]
|
|
summary: List targets
|
|
operationId: listTargets
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of targets
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Targets]
|
|
summary: Create target
|
|
operationId: createTarget
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
responses:
|
|
"201":
|
|
description: Target created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/targets/{id}:
|
|
get:
|
|
tags: [Targets]
|
|
summary: Get target
|
|
operationId: getTarget
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Target details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Targets]
|
|
summary: Update target
|
|
operationId: updateTarget
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
responses:
|
|
"200":
|
|
description: Target updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Targets]
|
|
summary: Delete target
|
|
operationId: deleteTarget
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Target deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Agents ──────────────────────────────────────────────────────────
|
|
/api/v1/agents:
|
|
get:
|
|
tags: [Agents]
|
|
summary: List agents
|
|
operationId: listAgents
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of agents
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Agent"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Agents]
|
|
summary: Register agent
|
|
operationId: registerAgent
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Agent"
|
|
responses:
|
|
"201":
|
|
description: Agent registered
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Agent"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"409":
|
|
$ref: "#/components/responses/Conflict"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agents/{id}:
|
|
get:
|
|
tags: [Agents]
|
|
summary: Get agent
|
|
operationId: getAgent
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Agent details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Agent"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agents/{id}/heartbeat:
|
|
post:
|
|
tags: [Agents]
|
|
summary: Agent heartbeat
|
|
description: Reports agent liveness and metadata (OS, architecture, IP, version).
|
|
operationId: agentHeartbeat
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
version:
|
|
type: string
|
|
hostname:
|
|
type: string
|
|
os:
|
|
type: string
|
|
architecture:
|
|
type: string
|
|
ip_address:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Heartbeat recorded
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agents/{id}/csr:
|
|
post:
|
|
tags: [Agents]
|
|
summary: Submit CSR
|
|
description: Agent submits a PEM-encoded CSR for signing. Used in agent keygen mode.
|
|
operationId: agentSubmitCSR
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [csr_pem]
|
|
properties:
|
|
csr_pem:
|
|
type: string
|
|
description: PEM-encoded certificate signing request
|
|
certificate_id:
|
|
type: string
|
|
responses:
|
|
"202":
|
|
description: CSR accepted
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agents/{id}/certificates/{cert_id}:
|
|
get:
|
|
tags: [Agents]
|
|
summary: Pick up signed certificate
|
|
description: Agent retrieves the signed certificate PEM after CSR signing completes.
|
|
operationId: agentPickupCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
- name: cert_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Certificate PEM
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
certificate_pem:
|
|
type: string
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agents/{id}/work:
|
|
get:
|
|
tags: [Agents]
|
|
summary: Get pending work
|
|
description: Returns pending deployment and AwaitingCSR jobs for the agent.
|
|
operationId: agentGetWork
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Work items
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
jobs:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/WorkItem"
|
|
count:
|
|
type: integer
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agents/{id}/jobs/{job_id}/status:
|
|
post:
|
|
tags: [Agents]
|
|
summary: Report job status
|
|
description: Agent reports completion or failure of an assigned job.
|
|
operationId: agentReportJobStatus
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
- name: job_id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [status]
|
|
properties:
|
|
status:
|
|
type: string
|
|
error:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Status updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Jobs ────────────────────────────────────────────────────────────
|
|
/api/v1/jobs:
|
|
get:
|
|
tags: [Jobs]
|
|
summary: List jobs
|
|
operationId: listJobs
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/JobStatus"
|
|
- name: type
|
|
in: query
|
|
schema:
|
|
$ref: "#/components/schemas/JobType"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of jobs
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Job"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/jobs/{id}:
|
|
get:
|
|
tags: [Jobs]
|
|
summary: Get job
|
|
operationId: getJob
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Job details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Job"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/jobs/{id}/cancel:
|
|
post:
|
|
tags: [Jobs]
|
|
summary: Cancel job
|
|
operationId: cancelJob
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Job cancelled
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/jobs/{id}/approve:
|
|
post:
|
|
tags: [Jobs]
|
|
summary: Approve job
|
|
description: Approves a job in AwaitingApproval state.
|
|
operationId: approveJob
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Job approved
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/jobs/{id}/reject:
|
|
post:
|
|
tags: [Jobs]
|
|
summary: Reject job
|
|
description: Rejects a job in AwaitingApproval state with an optional reason.
|
|
operationId: rejectJob
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
reason:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Job rejected
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Policies ────────────────────────────────────────────────────────
|
|
/api/v1/policies:
|
|
get:
|
|
tags: [Policies]
|
|
summary: List policies
|
|
operationId: listPolicies
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of policies
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/PolicyRule"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Policies]
|
|
summary: Create policy
|
|
operationId: createPolicy
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PolicyRule"
|
|
responses:
|
|
"201":
|
|
description: Policy created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PolicyRule"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/policies/{id}:
|
|
get:
|
|
tags: [Policies]
|
|
summary: Get policy
|
|
operationId: getPolicy
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Policy details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PolicyRule"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Policies]
|
|
summary: Update policy
|
|
operationId: updatePolicy
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PolicyRule"
|
|
responses:
|
|
"200":
|
|
description: Policy updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PolicyRule"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Policies]
|
|
summary: Delete policy
|
|
operationId: deletePolicy
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Policy deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/policies/{id}/violations:
|
|
get:
|
|
tags: [Policies]
|
|
summary: List policy violations
|
|
operationId: listPolicyViolations
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of violations
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/PolicyViolation"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Profiles ────────────────────────────────────────────────────────
|
|
/api/v1/profiles:
|
|
get:
|
|
tags: [Profiles]
|
|
summary: List profiles
|
|
operationId: listProfiles
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of profiles
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/CertificateProfile"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Profiles]
|
|
summary: Create profile
|
|
operationId: createProfile
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CertificateProfile"
|
|
responses:
|
|
"201":
|
|
description: Profile created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CertificateProfile"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/profiles/{id}:
|
|
get:
|
|
tags: [Profiles]
|
|
summary: Get profile
|
|
operationId: getProfile
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Profile details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CertificateProfile"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Profiles]
|
|
summary: Update profile
|
|
operationId: updateProfile
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CertificateProfile"
|
|
responses:
|
|
"200":
|
|
description: Profile updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CertificateProfile"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Profiles]
|
|
summary: Delete profile
|
|
operationId: deleteProfile
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Profile deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Teams ───────────────────────────────────────────────────────────
|
|
/api/v1/teams:
|
|
get:
|
|
tags: [Teams]
|
|
summary: List teams
|
|
operationId: listTeams
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of teams
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Team"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Teams]
|
|
summary: Create team
|
|
operationId: createTeam
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Team"
|
|
responses:
|
|
"201":
|
|
description: Team created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Team"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/teams/{id}:
|
|
get:
|
|
tags: [Teams]
|
|
summary: Get team
|
|
operationId: getTeam
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Team details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Team"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Teams]
|
|
summary: Update team
|
|
operationId: updateTeam
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Team"
|
|
responses:
|
|
"200":
|
|
description: Team updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Team"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Teams]
|
|
summary: Delete team
|
|
operationId: deleteTeam
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Team deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Owners ──────────────────────────────────────────────────────────
|
|
/api/v1/owners:
|
|
get:
|
|
tags: [Owners]
|
|
summary: List owners
|
|
operationId: listOwners
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of owners
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Owner"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Owners]
|
|
summary: Create owner
|
|
operationId: createOwner
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Owner"
|
|
responses:
|
|
"201":
|
|
description: Owner created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Owner"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/owners/{id}:
|
|
get:
|
|
tags: [Owners]
|
|
summary: Get owner
|
|
operationId: getOwner
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Owner details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Owner"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Owners]
|
|
summary: Update owner
|
|
operationId: updateOwner
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Owner"
|
|
responses:
|
|
"200":
|
|
description: Owner updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/Owner"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Owners]
|
|
summary: Delete owner
|
|
operationId: deleteOwner
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Owner deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Agent Groups ───────────────────────────────────────────────────
|
|
/api/v1/agent-groups:
|
|
get:
|
|
tags: [Agent Groups]
|
|
summary: List agent groups
|
|
operationId: listAgentGroups
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of agent groups
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/AgentGroup"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Agent Groups]
|
|
summary: Create agent group
|
|
operationId: createAgentGroup
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AgentGroup"
|
|
responses:
|
|
"201":
|
|
description: Agent group created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AgentGroup"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agent-groups/{id}:
|
|
get:
|
|
tags: [Agent Groups]
|
|
summary: Get agent group
|
|
operationId: getAgentGroup
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Agent group details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AgentGroup"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Agent Groups]
|
|
summary: Update agent group
|
|
operationId: updateAgentGroup
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AgentGroup"
|
|
responses:
|
|
"200":
|
|
description: Agent group updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AgentGroup"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Agent Groups]
|
|
summary: Delete agent group
|
|
operationId: deleteAgentGroup
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Agent group deleted
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/agent-groups/{id}/members:
|
|
get:
|
|
tags: [Agent Groups]
|
|
summary: List agent group members
|
|
description: Returns agents matching the group's dynamic criteria plus manually included members.
|
|
operationId: listAgentGroupMembers
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: List of member agents
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/Agent"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Audit ───────────────────────────────────────────────────────────
|
|
/api/v1/audit:
|
|
get:
|
|
tags: [Audit]
|
|
summary: List audit events
|
|
operationId: listAuditEvents
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of audit events
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/AuditEvent"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/audit/{id}:
|
|
get:
|
|
tags: [Audit]
|
|
summary: Get audit event
|
|
operationId: getAuditEvent
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Audit event details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AuditEvent"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Notifications ──────────────────────────────────────────────────
|
|
/api/v1/notifications:
|
|
get:
|
|
tags: [Notifications]
|
|
summary: List notifications
|
|
operationId: listNotifications
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: Paginated list of notifications
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/NotificationEvent"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/notifications/{id}:
|
|
get:
|
|
tags: [Notifications]
|
|
summary: Get notification
|
|
operationId: getNotification
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Notification details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NotificationEvent"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/notifications/{id}/read:
|
|
post:
|
|
tags: [Notifications]
|
|
summary: Mark notification as read
|
|
operationId: markNotificationAsRead
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Marked as read
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Stats ───────────────────────────────────────────────────────────
|
|
/api/v1/stats/summary:
|
|
get:
|
|
tags: [Stats]
|
|
summary: Dashboard summary
|
|
operationId: getDashboardSummary
|
|
responses:
|
|
"200":
|
|
description: High-level system metrics
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DashboardSummary"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/stats/certificates-by-status:
|
|
get:
|
|
tags: [Stats]
|
|
summary: Certificate status breakdown
|
|
operationId: getCertificatesByStatus
|
|
responses:
|
|
"200":
|
|
description: Certificate counts by status
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
status_counts:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
count:
|
|
type: integer
|
|
format: int64
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/stats/expiration-timeline:
|
|
get:
|
|
tags: [Stats]
|
|
summary: Expiration timeline
|
|
operationId: getExpirationTimeline
|
|
parameters:
|
|
- name: days
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 30
|
|
minimum: 1
|
|
maximum: 365
|
|
responses:
|
|
"200":
|
|
description: Certificates expiring per day
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
buckets:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
format: date
|
|
count:
|
|
type: integer
|
|
format: int64
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/stats/job-trends:
|
|
get:
|
|
tags: [Stats]
|
|
summary: Job success/failure trends
|
|
operationId: getJobTrends
|
|
parameters:
|
|
- name: days
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 30
|
|
minimum: 1
|
|
maximum: 365
|
|
responses:
|
|
"200":
|
|
description: Job trends per day
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
trends:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
format: date
|
|
completed:
|
|
type: integer
|
|
format: int64
|
|
failed:
|
|
type: integer
|
|
format: int64
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/stats/issuance-rate:
|
|
get:
|
|
tags: [Stats]
|
|
summary: Certificate issuance rate
|
|
operationId: getIssuanceRate
|
|
parameters:
|
|
- name: days
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 30
|
|
minimum: 1
|
|
maximum: 365
|
|
responses:
|
|
"200":
|
|
description: Issuance count per day
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
rate:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
date:
|
|
type: string
|
|
format: date
|
|
count:
|
|
type: integer
|
|
format: int64
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Metrics ─────────────────────────────────────────────────────────
|
|
/api/v1/metrics:
|
|
get:
|
|
tags: [Metrics]
|
|
summary: System metrics
|
|
description: JSON metrics snapshot with gauges, counters, and uptime. See also /api/v1/metrics/prometheus for Prometheus exposition format.
|
|
operationId: getMetrics
|
|
responses:
|
|
"200":
|
|
description: Metrics snapshot
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/MetricsResponse"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Prometheus Metrics (M22) ──────────────────────────────────────
|
|
/api/v1/metrics/prometheus:
|
|
get:
|
|
tags: [Metrics]
|
|
summary: Prometheus metrics
|
|
description: |
|
|
Prometheus exposition format metrics. Compatible with Prometheus, Grafana Agent,
|
|
Datadog Agent, Victoria Metrics, and any OpenMetrics scraper.
|
|
Returns 11 metrics with certctl_ prefix (8 gauges, 2 counters, 1 info).
|
|
operationId: getPrometheusMetrics
|
|
responses:
|
|
"200":
|
|
description: Prometheus text format
|
|
content:
|
|
text/plain:
|
|
schema:
|
|
type: string
|
|
description: "Prometheus exposition format (text/plain; version=0.0.4)"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Certificate Deployments (M20) ─────────────────────────────────
|
|
/api/v1/certificates/{id}/deployments:
|
|
get:
|
|
tags: [Certificates]
|
|
summary: List certificate deployments
|
|
description: Returns deployment targets associated with this certificate.
|
|
operationId: getCertificateDeployments
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Deployment targets for this certificate
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DeploymentTarget"
|
|
total:
|
|
type: integer
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Discovery (M18b) ─────────────────────────────────────────────
|
|
/api/v1/agents/{id}/discoveries:
|
|
post:
|
|
tags: [Discovery]
|
|
summary: Submit discovery report
|
|
description: |
|
|
Agent submits a batch of discovered certificates from filesystem scanning.
|
|
Server deduplicates by (fingerprint, agent_id, source_path) and records scan metadata.
|
|
operationId: submitDiscoveryReport
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DiscoveryReport"
|
|
responses:
|
|
"202":
|
|
description: Report accepted and processed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DiscoveryScan"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/discovered-certificates:
|
|
get:
|
|
tags: [Discovery]
|
|
summary: List discovered certificates
|
|
description: Returns discovered certificates with optional filters by agent and triage status.
|
|
operationId: listDiscoveredCertificates
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- name: agent_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by discovering agent
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [Unmanaged, Managed, Dismissed]
|
|
description: Filter by triage status
|
|
responses:
|
|
"200":
|
|
description: Paginated list of discovered certificates
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DiscoveredCertificate"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/discovered-certificates/{id}:
|
|
get:
|
|
tags: [Discovery]
|
|
summary: Get discovered certificate
|
|
description: Returns a single discovered certificate by ID.
|
|
operationId: getDiscoveredCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Discovered certificate details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DiscoveredCertificate"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/discovered-certificates/{id}/claim:
|
|
post:
|
|
tags: [Discovery]
|
|
summary: Claim discovered certificate
|
|
description: Links a discovered certificate to an existing managed certificate. Changes status to Managed.
|
|
operationId: claimDiscoveredCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [managed_certificate_id]
|
|
properties:
|
|
managed_certificate_id:
|
|
type: string
|
|
description: ID of the managed certificate to link to
|
|
responses:
|
|
"200":
|
|
description: Certificate claimed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusMessageResponse"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/discovered-certificates/{id}/dismiss:
|
|
post:
|
|
tags: [Discovery]
|
|
summary: Dismiss discovered certificate
|
|
description: Marks a discovered certificate as dismissed (excluded from triage queue).
|
|
operationId: dismissDiscoveredCertificate
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Certificate dismissed
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusMessageResponse"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/discovery-scans:
|
|
get:
|
|
tags: [Discovery]
|
|
summary: List discovery scans
|
|
description: Returns history of discovery scan executions with optional agent filter.
|
|
operationId: listDiscoveryScans
|
|
parameters:
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
- name: agent_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by agent ID
|
|
responses:
|
|
"200":
|
|
description: Paginated list of discovery scans
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/DiscoveryScan"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/discovery-summary:
|
|
get:
|
|
tags: [Discovery]
|
|
summary: Discovery status summary
|
|
description: Returns aggregate counts of discovered certificates by triage status.
|
|
operationId: getDiscoverySummary
|
|
responses:
|
|
"200":
|
|
description: Status counts
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
Unmanaged:
|
|
type: integer
|
|
Managed:
|
|
type: integer
|
|
Dismissed:
|
|
type: integer
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Network Scan Targets (M21) ───────────────────────────────────
|
|
/api/v1/network-scan-targets:
|
|
get:
|
|
tags: [Network Scan]
|
|
summary: List network scan targets
|
|
description: Returns all configured network scan targets with CIDR ranges and ports.
|
|
operationId: listNetworkScanTargets
|
|
responses:
|
|
"200":
|
|
description: List of network scan targets
|
|
content:
|
|
application/json:
|
|
schema:
|
|
allOf:
|
|
- $ref: "#/components/schemas/PaginationEnvelope"
|
|
- type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/NetworkScanTarget"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
post:
|
|
tags: [Network Scan]
|
|
summary: Create network scan target
|
|
description: |
|
|
Creates a new network scan target. CIDR ranges are validated and capped at /20
|
|
(4096 IPs max per CIDR) to prevent accidental huge scans.
|
|
operationId: createNetworkScanTarget
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NetworkScanTargetCreate"
|
|
responses:
|
|
"201":
|
|
description: Target created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NetworkScanTarget"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/network-scan-targets/{id}:
|
|
get:
|
|
tags: [Network Scan]
|
|
summary: Get network scan target
|
|
description: Returns a single network scan target by ID.
|
|
operationId: getNetworkScanTarget
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Network scan target details
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NetworkScanTarget"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
put:
|
|
tags: [Network Scan]
|
|
summary: Update network scan target
|
|
description: Updates an existing network scan target.
|
|
operationId: updateNetworkScanTarget
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NetworkScanTargetCreate"
|
|
responses:
|
|
"200":
|
|
description: Target updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/NetworkScanTarget"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
delete:
|
|
tags: [Network Scan]
|
|
summary: Delete network scan target
|
|
description: Deletes a network scan target.
|
|
operationId: deleteNetworkScanTarget
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Target deleted
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/network-scan-targets/{id}/scan:
|
|
post:
|
|
tags: [Network Scan]
|
|
summary: Trigger network scan
|
|
description: |
|
|
Triggers an immediate scan of the specified target. Scans all configured CIDRs and ports
|
|
concurrently (50 goroutines). Results feed into the discovery pipeline for deduplication.
|
|
operationId: triggerNetworkScan
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"202":
|
|
description: Scan completed with certificates found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DiscoveryScan"
|
|
"200":
|
|
description: Scan completed, no certificates found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusMessageResponse"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Health Monitoring ─────────────────────────────────────────────
|
|
/api/v1/health-checks:
|
|
get:
|
|
tags: [Health Monitoring]
|
|
summary: List endpoint health checks
|
|
description: |
|
|
Lists all TLS endpoint health checks with optional filtering by status, certificate, or network scan target.
|
|
Includes current status, last probe results, and probe history summary.
|
|
operationId: listHealthChecks
|
|
parameters:
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [Healthy, Degraded, Down, CertMismatch]
|
|
description: Filter by health status
|
|
- name: certificate_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by certificate ID
|
|
- name: network_scan_target_id
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Filter by network scan target ID
|
|
- name: enabled
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
description: Filter by enabled/disabled state
|
|
- $ref: "#/components/parameters/page"
|
|
- $ref: "#/components/parameters/per_page"
|
|
responses:
|
|
"200":
|
|
description: List of health checks
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/EndpointHealthCheck"
|
|
total:
|
|
type: integer
|
|
page:
|
|
type: integer
|
|
per_page:
|
|
type: integer
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
post:
|
|
tags: [Health Monitoring]
|
|
summary: Create health check
|
|
description: Creates a new manual health check for an endpoint.
|
|
operationId: createHealthCheck
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [endpoint, check_interval_seconds]
|
|
properties:
|
|
endpoint:
|
|
type: string
|
|
description: "host:port to monitor"
|
|
example: "api.example.com:443"
|
|
expected_fingerprint:
|
|
type: string
|
|
description: Expected certificate SHA-256 fingerprint (optional)
|
|
check_interval_seconds:
|
|
type: integer
|
|
minimum: 30
|
|
description: Probe frequency in seconds (default 300)
|
|
timeout_ms:
|
|
type: integer
|
|
description: TLS connection timeout in milliseconds
|
|
responses:
|
|
"201":
|
|
description: Health check created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/EndpointHealthCheck"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/health-checks/summary:
|
|
get:
|
|
tags: [Health Monitoring]
|
|
summary: Health check summary
|
|
description: Returns aggregate status counts for all health checks.
|
|
operationId: getHealthCheckSummary
|
|
responses:
|
|
"200":
|
|
description: Health check summary
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
healthy:
|
|
type: integer
|
|
degraded:
|
|
type: integer
|
|
down:
|
|
type: integer
|
|
cert_mismatch:
|
|
type: integer
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/health-checks/{id}:
|
|
get:
|
|
tags: [Health Monitoring]
|
|
summary: Get health check
|
|
operationId: getHealthCheck
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"200":
|
|
description: Health check detail
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/EndpointHealthCheck"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
put:
|
|
tags: [Health Monitoring]
|
|
summary: Update health check
|
|
description: Update thresholds, interval, or expected fingerprint.
|
|
operationId: updateHealthCheck
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
expected_fingerprint:
|
|
type: string
|
|
check_interval_seconds:
|
|
type: integer
|
|
timeout_ms:
|
|
type: integer
|
|
enabled:
|
|
type: boolean
|
|
responses:
|
|
"200":
|
|
description: Health check updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/EndpointHealthCheck"
|
|
"400":
|
|
$ref: "#/components/responses/BadRequest"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
delete:
|
|
tags: [Health Monitoring]
|
|
summary: Delete health check
|
|
operationId: deleteHealthCheck
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
responses:
|
|
"204":
|
|
description: Health check deleted
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/health-checks/{id}/history:
|
|
get:
|
|
tags: [Health Monitoring]
|
|
summary: Get probe history
|
|
description: Returns historical probe records with status, response times, and errors.
|
|
operationId: getHealthCheckHistory
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 100
|
|
minimum: 1
|
|
maximum: 1000
|
|
description: Max number of records to return
|
|
responses:
|
|
"200":
|
|
description: Probe history
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/HealthHistoryEntry"
|
|
total:
|
|
type: integer
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/health-checks/{id}/acknowledge:
|
|
post:
|
|
tags: [Health Monitoring]
|
|
summary: Acknowledge incident
|
|
description: Mark a health check incident as acknowledged by the operator.
|
|
operationId: acknowledgeHealthCheckIncident
|
|
parameters:
|
|
- $ref: "#/components/parameters/resourceId"
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
acknowledged_by:
|
|
type: string
|
|
description: Operator name or ID
|
|
responses:
|
|
"200":
|
|
description: Incident acknowledged
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/EndpointHealthCheck"
|
|
"404":
|
|
$ref: "#/components/responses/NotFound"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ─── Digest ────────────────────────────────────────────────────────
|
|
/api/v1/digest/preview:
|
|
get:
|
|
tags: [Digest]
|
|
summary: Preview digest email
|
|
description: |
|
|
Returns an HTML preview of the scheduled certificate digest email.
|
|
This includes a summary of certificate status, pending jobs, and expiring certificates.
|
|
operationId: previewDigest
|
|
responses:
|
|
"200":
|
|
description: HTML digest email preview
|
|
content:
|
|
text/html:
|
|
schema:
|
|
type: string
|
|
example: "<html>...</html>"
|
|
"503":
|
|
description: Digest service not configured
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusMessageResponse"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
/api/v1/digest/send:
|
|
post:
|
|
tags: [Digest]
|
|
summary: Send digest email
|
|
description: |
|
|
Triggers immediate sending of the certificate digest email to configured recipients.
|
|
If no explicit recipients are configured, sends to certificate owners.
|
|
operationId: sendDigest
|
|
responses:
|
|
"200":
|
|
description: Digest sent successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusMessageResponse"
|
|
"503":
|
|
description: Digest service not configured
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/StatusMessageResponse"
|
|
"500":
|
|
$ref: "#/components/responses/InternalError"
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
description: API key passed as Bearer token. Configure via CERTCTL_AUTH_SECRET.
|
|
|
|
parameters:
|
|
resourceId:
|
|
name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: string
|
|
description: Human-readable resource ID (e.g., mc-api-prod, t-platform)
|
|
page:
|
|
name: page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 1
|
|
minimum: 1
|
|
per_page:
|
|
name: per_page
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 50
|
|
minimum: 1
|
|
maximum: 500
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Validation error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
NotFound:
|
|
description: Resource not found
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
Conflict:
|
|
description: Resource conflict
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
InternalError:
|
|
description: Internal server error
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
schemas:
|
|
# ─── Common ──────────────────────────────────────────────────────
|
|
ErrorResponse:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: string
|
|
request_id:
|
|
type: string
|
|
|
|
StatusResponse:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
|
|
PaginationEnvelope:
|
|
type: object
|
|
properties:
|
|
total:
|
|
type: integer
|
|
format: int64
|
|
page:
|
|
type: integer
|
|
per_page:
|
|
type: integer
|
|
|
|
# ─── Certificates ────────────────────────────────────────────────
|
|
CertificateStatus:
|
|
type: string
|
|
enum:
|
|
- Pending
|
|
- Active
|
|
- Expiring
|
|
- Expired
|
|
- RenewalInProgress
|
|
- Failed
|
|
- Revoked
|
|
- Archived
|
|
|
|
ManagedCertificate:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
common_name:
|
|
type: string
|
|
sans:
|
|
type: array
|
|
items:
|
|
type: string
|
|
environment:
|
|
type: string
|
|
owner_id:
|
|
type: string
|
|
team_id:
|
|
type: string
|
|
issuer_id:
|
|
type: string
|
|
target_ids:
|
|
type: array
|
|
items:
|
|
type: string
|
|
renewal_policy_id:
|
|
type: string
|
|
certificate_profile_id:
|
|
type: string
|
|
status:
|
|
$ref: "#/components/schemas/CertificateStatus"
|
|
expires_at:
|
|
type: string
|
|
format: date-time
|
|
tags:
|
|
type: object
|
|
additionalProperties:
|
|
type: string
|
|
last_renewal_at:
|
|
type: string
|
|
format: date-time
|
|
last_deployment_at:
|
|
type: string
|
|
format: date-time
|
|
revoked_at:
|
|
type: string
|
|
format: date-time
|
|
revocation_reason:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
required:
|
|
- name
|
|
- common_name
|
|
- renewal_policy_id
|
|
- issuer_id
|
|
- owner_id
|
|
- team_id
|
|
|
|
CertificateVersion:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
certificate_id:
|
|
type: string
|
|
serial_number:
|
|
type: string
|
|
not_before:
|
|
type: string
|
|
format: date-time
|
|
not_after:
|
|
type: string
|
|
format: date-time
|
|
fingerprint_sha256:
|
|
type: string
|
|
pem_chain:
|
|
type: string
|
|
csr_pem:
|
|
type: string
|
|
key_algorithm:
|
|
type: string
|
|
key_size:
|
|
type: integer
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
RevocationReason:
|
|
type: string
|
|
enum:
|
|
- unspecified
|
|
- keyCompromise
|
|
- caCompromise
|
|
- affiliationChanged
|
|
- superseded
|
|
- cessationOfOperation
|
|
- certificateHold
|
|
- privilegeWithdrawn
|
|
|
|
# ─── Issuers ─────────────────────────────────────────────────────
|
|
IssuerType:
|
|
type: string
|
|
enum: [ACME, GenericCA, StepCA, VaultPKI, DigiCert, Sectigo, GoogleCAS, AWSACMPCA, Entrust, GlobalSign, EJBCA]
|
|
|
|
Issuer:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
type:
|
|
$ref: "#/components/schemas/IssuerType"
|
|
config:
|
|
type: object
|
|
description: Issuer-specific configuration (varies by type)
|
|
enabled:
|
|
type: boolean
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Targets ─────────────────────────────────────────────────────
|
|
TargetType:
|
|
type: string
|
|
enum: [NGINX, Apache, HAProxy, Traefik, Caddy, Envoy, Postfix, Dovecot, IIS, F5, SSH, WinCertStore, JavaKeystore, KubernetesSecrets]
|
|
|
|
DeploymentTarget:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
type:
|
|
$ref: "#/components/schemas/TargetType"
|
|
agent_id:
|
|
type: string
|
|
config:
|
|
type: object
|
|
description: Target-specific configuration (varies by type)
|
|
enabled:
|
|
type: boolean
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Agents ──────────────────────────────────────────────────────
|
|
AgentStatus:
|
|
type: string
|
|
enum: [Online, Offline, Degraded]
|
|
|
|
Agent:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
hostname:
|
|
type: string
|
|
status:
|
|
$ref: "#/components/schemas/AgentStatus"
|
|
last_heartbeat_at:
|
|
type: string
|
|
format: date-time
|
|
registered_at:
|
|
type: string
|
|
format: date-time
|
|
api_key_hash:
|
|
type: string
|
|
os:
|
|
type: string
|
|
architecture:
|
|
type: string
|
|
ip_address:
|
|
type: string
|
|
version:
|
|
type: string
|
|
|
|
WorkItem:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
type:
|
|
$ref: "#/components/schemas/JobType"
|
|
certificate_id:
|
|
type: string
|
|
common_name:
|
|
type: string
|
|
sans:
|
|
type: array
|
|
items:
|
|
type: string
|
|
target_id:
|
|
type: string
|
|
target_type:
|
|
type: string
|
|
target_config:
|
|
type: object
|
|
status:
|
|
$ref: "#/components/schemas/JobStatus"
|
|
|
|
# ─── Jobs ────────────────────────────────────────────────────────
|
|
JobType:
|
|
type: string
|
|
enum: [Issuance, Renewal, Deployment, Validation]
|
|
|
|
JobStatus:
|
|
type: string
|
|
enum:
|
|
- Pending
|
|
- AwaitingCSR
|
|
- AwaitingApproval
|
|
- Running
|
|
- Completed
|
|
- Failed
|
|
- Cancelled
|
|
|
|
Job:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
type:
|
|
$ref: "#/components/schemas/JobType"
|
|
certificate_id:
|
|
type: string
|
|
target_id:
|
|
type: string
|
|
status:
|
|
$ref: "#/components/schemas/JobStatus"
|
|
attempts:
|
|
type: integer
|
|
max_attempts:
|
|
type: integer
|
|
last_error:
|
|
type: string
|
|
scheduled_at:
|
|
type: string
|
|
format: date-time
|
|
started_at:
|
|
type: string
|
|
format: date-time
|
|
completed_at:
|
|
type: string
|
|
format: date-time
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Policies ────────────────────────────────────────────────────
|
|
PolicyType:
|
|
type: string
|
|
enum:
|
|
- AllowedIssuers
|
|
- AllowedDomains
|
|
- RequiredMetadata
|
|
- AllowedEnvironments
|
|
- RenewalLeadTime
|
|
|
|
PolicySeverity:
|
|
type: string
|
|
enum: [Warning, Error, Critical]
|
|
|
|
PolicyRule:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
type:
|
|
$ref: "#/components/schemas/PolicyType"
|
|
config:
|
|
type: object
|
|
description: Policy-specific configuration (varies by type)
|
|
enabled:
|
|
type: boolean
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
PolicyViolation:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
certificate_id:
|
|
type: string
|
|
rule_id:
|
|
type: string
|
|
message:
|
|
type: string
|
|
severity:
|
|
$ref: "#/components/schemas/PolicySeverity"
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Profiles ────────────────────────────────────────────────────
|
|
CertificateProfile:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
description:
|
|
type: string
|
|
allowed_key_algorithms:
|
|
type: array
|
|
items:
|
|
$ref: "#/components/schemas/KeyAlgorithmRule"
|
|
max_ttl_seconds:
|
|
type: integer
|
|
allowed_ekus:
|
|
type: array
|
|
description: Extended Key Usages to include in issued certificates
|
|
items:
|
|
type: string
|
|
enum:
|
|
- serverAuth
|
|
- clientAuth
|
|
- codeSigning
|
|
- emailProtection
|
|
- timeStamping
|
|
required_san_patterns:
|
|
type: array
|
|
items:
|
|
type: string
|
|
spiffe_uri_pattern:
|
|
type: string
|
|
allow_short_lived:
|
|
type: boolean
|
|
enabled:
|
|
type: boolean
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
KeyAlgorithmRule:
|
|
type: object
|
|
properties:
|
|
algorithm:
|
|
type: string
|
|
enum: [RSA, ECDSA, Ed25519]
|
|
min_size:
|
|
type: integer
|
|
|
|
# ─── Teams ───────────────────────────────────────────────────────
|
|
Team:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
description:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Owners ──────────────────────────────────────────────────────
|
|
Owner:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
email:
|
|
type: string
|
|
team_id:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Agent Groups ────────────────────────────────────────────────
|
|
AgentGroup:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
description:
|
|
type: string
|
|
match_os:
|
|
type: string
|
|
match_architecture:
|
|
type: string
|
|
match_ip_cidr:
|
|
type: string
|
|
match_version:
|
|
type: string
|
|
enabled:
|
|
type: boolean
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Audit ───────────────────────────────────────────────────────
|
|
ActorType:
|
|
type: string
|
|
enum: [User, System, Agent]
|
|
|
|
AuditEvent:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
actor:
|
|
type: string
|
|
actor_type:
|
|
$ref: "#/components/schemas/ActorType"
|
|
action:
|
|
type: string
|
|
resource_type:
|
|
type: string
|
|
resource_id:
|
|
type: string
|
|
details:
|
|
type: object
|
|
timestamp:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Notifications ───────────────────────────────────────────────
|
|
NotificationType:
|
|
type: string
|
|
enum:
|
|
- ExpirationWarning
|
|
- RenewalSuccess
|
|
- RenewalFailure
|
|
- DeploymentSuccess
|
|
- DeploymentFailure
|
|
- PolicyViolation
|
|
- Revocation
|
|
|
|
NotificationChannel:
|
|
type: string
|
|
enum: [Email, Webhook, Slack]
|
|
|
|
NotificationEvent:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
type:
|
|
$ref: "#/components/schemas/NotificationType"
|
|
certificate_id:
|
|
type: string
|
|
channel:
|
|
$ref: "#/components/schemas/NotificationChannel"
|
|
recipient:
|
|
type: string
|
|
message:
|
|
type: string
|
|
sent_at:
|
|
type: string
|
|
format: date-time
|
|
status:
|
|
type: string
|
|
error:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Stats & Metrics ─────────────────────────────────────────────
|
|
DashboardSummary:
|
|
type: object
|
|
properties:
|
|
total_certificates:
|
|
type: integer
|
|
format: int64
|
|
expiring_certificates:
|
|
type: integer
|
|
format: int64
|
|
expired_certificates:
|
|
type: integer
|
|
format: int64
|
|
revoked_certificates:
|
|
type: integer
|
|
format: int64
|
|
active_agents:
|
|
type: integer
|
|
format: int64
|
|
offline_agents:
|
|
type: integer
|
|
format: int64
|
|
total_agents:
|
|
type: integer
|
|
format: int64
|
|
pending_jobs:
|
|
type: integer
|
|
format: int64
|
|
failed_jobs:
|
|
type: integer
|
|
format: int64
|
|
complete_jobs:
|
|
type: integer
|
|
format: int64
|
|
completed_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
MetricsResponse:
|
|
type: object
|
|
properties:
|
|
gauge:
|
|
type: object
|
|
properties:
|
|
certificate_total:
|
|
type: integer
|
|
format: int64
|
|
certificate_active:
|
|
type: integer
|
|
format: int64
|
|
certificate_expiring_soon:
|
|
type: integer
|
|
format: int64
|
|
certificate_expired:
|
|
type: integer
|
|
format: int64
|
|
certificate_revoked:
|
|
type: integer
|
|
format: int64
|
|
agent_total:
|
|
type: integer
|
|
format: int64
|
|
agent_online:
|
|
type: integer
|
|
format: int64
|
|
job_pending:
|
|
type: integer
|
|
format: int64
|
|
counter:
|
|
type: object
|
|
properties:
|
|
job_completed_total:
|
|
type: integer
|
|
format: int64
|
|
job_failed_total:
|
|
type: integer
|
|
format: int64
|
|
uptime:
|
|
type: object
|
|
properties:
|
|
uptime_seconds:
|
|
type: integer
|
|
format: int64
|
|
server_started:
|
|
type: string
|
|
format: date-time
|
|
measured_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
# ─── Discovery (M18b) ────────────────────────────────────────────
|
|
DiscoveredCertificate:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
fingerprint_sha256:
|
|
type: string
|
|
common_name:
|
|
type: string
|
|
sans:
|
|
type: array
|
|
items:
|
|
type: string
|
|
serial_number:
|
|
type: string
|
|
issuer_dn:
|
|
type: string
|
|
subject_dn:
|
|
type: string
|
|
not_before:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
not_after:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
key_algorithm:
|
|
type: string
|
|
key_size:
|
|
type: integer
|
|
is_ca:
|
|
type: boolean
|
|
source_path:
|
|
type: string
|
|
source_format:
|
|
type: string
|
|
agent_id:
|
|
type: string
|
|
discovery_scan_id:
|
|
type: string
|
|
nullable: true
|
|
managed_certificate_id:
|
|
type: string
|
|
nullable: true
|
|
status:
|
|
type: string
|
|
enum: [Unmanaged, Managed, Dismissed]
|
|
first_seen_at:
|
|
type: string
|
|
format: date-time
|
|
last_seen_at:
|
|
type: string
|
|
format: date-time
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
DiscoveryScan:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
agent_id:
|
|
type: string
|
|
directories:
|
|
type: array
|
|
items:
|
|
type: string
|
|
certificates_found:
|
|
type: integer
|
|
certificates_new:
|
|
type: integer
|
|
errors_count:
|
|
type: integer
|
|
scan_duration_ms:
|
|
type: integer
|
|
started_at:
|
|
type: string
|
|
format: date-time
|
|
completed_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
|
|
DiscoveryReport:
|
|
type: object
|
|
required: [agent_id, directories, certificates]
|
|
properties:
|
|
agent_id:
|
|
type: string
|
|
directories:
|
|
type: array
|
|
items:
|
|
type: string
|
|
certificates:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
fingerprint_sha256:
|
|
type: string
|
|
common_name:
|
|
type: string
|
|
sans:
|
|
type: array
|
|
items:
|
|
type: string
|
|
serial_number:
|
|
type: string
|
|
issuer_dn:
|
|
type: string
|
|
subject_dn:
|
|
type: string
|
|
not_before:
|
|
type: string
|
|
not_after:
|
|
type: string
|
|
key_algorithm:
|
|
type: string
|
|
key_size:
|
|
type: integer
|
|
is_ca:
|
|
type: boolean
|
|
pem_data:
|
|
type: string
|
|
source_path:
|
|
type: string
|
|
source_format:
|
|
type: string
|
|
errors:
|
|
type: array
|
|
items:
|
|
type: string
|
|
scan_duration_ms:
|
|
type: integer
|
|
|
|
StatusMessageResponse:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
message:
|
|
type: string
|
|
|
|
# ─── Network Scan (M21) ──────────────────────────────────────────
|
|
NetworkScanTarget:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
cidrs:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: CIDR ranges to scan (max /20 per CIDR)
|
|
ports:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
description: TCP ports to probe for TLS
|
|
enabled:
|
|
type: boolean
|
|
scan_interval_hours:
|
|
type: integer
|
|
description: Hours between scheduled scans
|
|
timeout_ms:
|
|
type: integer
|
|
description: Per-connection timeout in milliseconds
|
|
last_scan_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
last_scan_duration_ms:
|
|
type: integer
|
|
nullable: true
|
|
last_scan_certs_found:
|
|
type: integer
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
NetworkScanTargetCreate:
|
|
type: object
|
|
required: [name, cidrs]
|
|
properties:
|
|
name:
|
|
type: string
|
|
cidrs:
|
|
type: array
|
|
items:
|
|
type: string
|
|
description: CIDR ranges (max /20 per CIDR, max 4096 IPs)
|
|
ports:
|
|
type: array
|
|
items:
|
|
type: integer
|
|
description: TCP ports to probe (default [443])
|
|
enabled:
|
|
type: boolean
|
|
default: true
|
|
scan_interval_hours:
|
|
type: integer
|
|
default: 6
|
|
timeout_ms:
|
|
type: integer
|
|
default: 5000
|
|
|
|
EndpointHealthCheck:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
description: Health check ID
|
|
endpoint:
|
|
type: string
|
|
description: "Target endpoint (host:port)"
|
|
example: "api.example.com:443"
|
|
certificate_id:
|
|
type: string
|
|
nullable: true
|
|
description: Associated managed certificate ID (if from deployment)
|
|
network_scan_target_id:
|
|
type: string
|
|
nullable: true
|
|
description: Associated network scan target ID (if auto-created)
|
|
expected_fingerprint:
|
|
type: string
|
|
nullable: true
|
|
description: Expected certificate SHA-256 fingerprint
|
|
status:
|
|
type: string
|
|
enum: [Healthy, Degraded, Down, CertMismatch]
|
|
description: Current health status
|
|
enabled:
|
|
type: boolean
|
|
check_interval_seconds:
|
|
type: integer
|
|
description: Frequency of TLS probes (seconds)
|
|
timeout_ms:
|
|
type: integer
|
|
description: TLS connection timeout (milliseconds)
|
|
consecutive_failures:
|
|
type: integer
|
|
description: Number of consecutive probe failures
|
|
last_checked_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of last probe
|
|
last_success_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of last successful probe
|
|
last_failure_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of last failed probe
|
|
last_transition_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
description: Timestamp of last status transition
|
|
failure_reason:
|
|
type: string
|
|
nullable: true
|
|
description: Reason for last failure
|
|
acknowledged:
|
|
type: boolean
|
|
description: Whether the current status has been acknowledged
|
|
acknowledged_by:
|
|
type: string
|
|
nullable: true
|
|
description: Operator name who acknowledged (if applicable)
|
|
acknowledged_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
HealthHistoryEntry:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
health_check_id:
|
|
type: string
|
|
status:
|
|
type: string
|
|
enum: [Healthy, Degraded, Down, CertMismatch]
|
|
response_time_ms:
|
|
type: integer
|
|
nullable: true
|
|
description: Time to connect and complete TLS handshake (milliseconds)
|
|
observed_fingerprint:
|
|
type: string
|
|
nullable: true
|
|
description: SHA-256 fingerprint of certificate observed on endpoint
|
|
tls_version:
|
|
type: string
|
|
nullable: true
|
|
description: TLS version (e.g., TLSv1.3)
|
|
cipher_suite:
|
|
type: string
|
|
nullable: true
|
|
description: Cipher suite used in TLS handshake
|
|
cert_subject:
|
|
type: string
|
|
nullable: true
|
|
description: Subject DN of observed certificate
|
|
cert_issuer:
|
|
type: string
|
|
nullable: true
|
|
description: Issuer DN of observed certificate
|
|
cert_not_before:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
cert_not_after:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
failure_reason:
|
|
type: string
|
|
nullable: true
|
|
description: Error message if probe failed
|
|
checked_at:
|
|
type: string
|
|
format: date-time
|
|
description: Timestamp of this probe
|