Files
certctl/README.md
T
shankar0123 690765b53e test: comprehensive test expansion — 330+ to 525+ tests, close M11b coverage gaps
Add 195+ new tests across service, handler, connector, and integration layers:
- Service tests: team (23), owner (21), agent_group (25), issuer (18), issuer_adapter (6)
- Handler tests: teams (26), owners (21)
- NGINX target connector tests (13): config validation, deployment, reload
- Integration tests: 19 M11b endpoint subtests (teams, owners, agent groups CRUD)
- CI pipeline: add ./internal/connector/target/... to test coverage path
- Docs: update test counts to 525+ across README, architecture, CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 23:43:32 -04:00

19 KiB
Raw Blame History

certctl — Self-Hosted Certificate Lifecycle Platform

TLS certificate lifespans are shrinking. The CA/Browser Forum passed Ballot SC-081v3 unanimously in April 2025, setting a phased reduction: 200 days by March 2026, 100 days by March 2027, and 47 days by March 2029. Manual certificate management is no longer viable at any scale.

certctl is a self-hosted platform for end-to-end certificate lifecycle automation — from issuance through renewal to deployment — with zero human intervention. Track every certificate in your organization, automatically renew them before they expire, and deploy them to your servers without touching a terminal. Private keys never leave your infrastructure.

License Go Report Card Status: v1.0.0

What It Does

certctl gives you a single pane of glass for every TLS certificate in your organization. The web dashboard shows your full certificate inventory — what's healthy, what's expiring, what's already expired, and who owns each one. The REST API (68 endpoints) lets you automate everything. Agents deployed on your infrastructure generate private keys locally and submit CSRs — private keys never leave your servers. The background scheduler watches expiration dates and triggers renewals automatically — when certificate lifespans drop to 47 days, certctl handles the constant rotation without human involvement.

flowchart LR
    subgraph "Control Plane"
        API["REST API + Dashboard\n:8443"]
        PG[("PostgreSQL")]
    end

    subgraph "Your Infrastructure"
        A1["Agent"] --> T1["NGINX"]
        A2["Agent"] --> T2["Apache / HAProxy"]
        A3["Agent"] --> T3["F5 · IIS"]
    end

    API --> PG
    A1 & A2 & A3 -->|"CSR + status\n(no private keys)"| API
    API -->|"Signed certs"| A1 & A2 & A3
    API -->|"Issue/Renew"| CA["Certificate Authorities\nLocal CA · ACME"]

Screenshots

Dashboard Certificates
Dashboard — certificate stats, expiry timeline, recent jobs Certificates — full inventory with status, environment, owner filters
Agents Jobs
Agents — fleet health, hostname, heartbeat tracking Jobs — issuance, renewal, deployment job queue
Notifications Policies
Notifications — threshold alerts grouped by certificate Policies — enforcement rules with enable/disable and delete
Issuers Targets
Issuers — CA connectors with test connectivity Targets — deployment targets (NGINX, Apache, HAProxy, F5, IIS)
Audit Trail
Audit Trail — immutable log of every action

Quick Start

git clone https://github.com/shankar0123/certctl.git
cd certctl
docker compose -f deploy/docker-compose.yml up -d --build

Wait ~30 seconds, then open http://localhost:8443 in your browser.

The dashboard comes pre-loaded with 15 demo certificates, 5 agents, policy rules, audit events, and notifications — a realistic snapshot of a certificate inventory so you can explore immediately.

Verify the API:

curl http://localhost:8443/health
# {"status":"healthy"}

curl -s http://localhost:8443/api/v1/certificates | jq '.total'
# 15

Manual Build

# Prerequisites: Go 1.22+, PostgreSQL 16+
go mod download
make build

# Set up database
export CERTCTL_DATABASE_URL="postgres://certctl:certctl@localhost:5432/certctl?sslmode=disable"
export CERTCTL_AUTH_TYPE=none
make migrate-up

# Start server
./bin/server

# Start agent (separate terminal)
export CERTCTL_SERVER_URL=http://localhost:8443
export CERTCTL_API_KEY=change-me-in-production
export CERTCTL_AGENT_NAME=local-agent
export CERTCTL_AGENT_ID=agent-local-01
./bin/agent --agent-id=agent-local-01

Documentation

Guide Description
Concepts TLS certificates explained from scratch — for beginners who know nothing about certs
Quick Start Get running in 5 minutes with accurate API examples
Demo Walkthrough 5-7 minute guided stakeholder presentation
Advanced Demo Issue a certificate end-to-end with technical deep-dives
Architecture System design, data flow diagrams, security model
Connectors Build custom issuer, target, and notifier connectors

Architecture

flowchart TB
    subgraph "Control Plane (certctl-server)"
        DASH["Web Dashboard\nReact SPA"]
        API["REST API\nGo 1.22 net/http"]
        SVC["Service Layer"]
        REPO["Repository Layer\ndatabase/sql + lib/pq"]
        SCHED["Scheduler\nRenewal · Jobs · Health · Notifications"]
    end

    subgraph "Data Store"
        PG[("PostgreSQL 16\n17 tables\nTEXT primary keys")]
    end

    subgraph "Agents"
        AG["certctl-agent\nKey generation · CSR · Deployment"]
    end

    DASH --> API
    API --> SVC --> REPO --> PG
    SCHED --> SVC
    AG -->|"Heartbeat + CSR"| API
    API -->|"Cert + Chain"| AG

Key Design Decisions

  • Private keys isolated from the control plane. Agents generate ECDSA P-256 keys locally and submit CSRs (public key only). The server signs the CSR and returns the certificate — private keys never touch the control plane. Server-side keygen is available via CERTCTL_KEYGEN_MODE=server for demo/development only.
  • TEXT primary keys, not UUIDs. IDs are human-readable prefixed strings (mc-api-prod, t-platform, o-alice) so you can identify resource types at a glance in logs and queries.
  • Handler → Service → Repository layering. Handlers define their own service interfaces for clean dependency inversion. No global service singletons.
  • Idempotent migrations. All schema uses IF NOT EXISTS and seed data uses ON CONFLICT (id) DO NOTHING, safe for repeated execution.

Database Schema

Table Purpose
managed_certificates Certificate records with metadata, status, expiry, tags
certificate_versions Historical versions with PEM chains and CSRs
renewal_policies Renewal window, auto-renew settings, retry config, alert thresholds
issuers CA configurations (Local CA, ACME, etc.)
deployment_targets Target systems (NGINX, F5, IIS) with agent assignments
agents Registered agents with heartbeat tracking, OS/arch/IP metadata
jobs Issuance, renewal, deployment, and validation jobs
teams Organizational groups for certificate ownership
owners Individual owners with email for notifications
policy_rules Enforcement rules (allowed issuers, environments, metadata)
policy_violations Flagged non-compliance with severity levels
audit_events Immutable action log (append-only, no update/delete)
notification_events Email and webhook notification records
certificate_target_mappings Many-to-many cert ↔ target relationships
certificate_profiles Named enrollment profiles with allowed key types, max TTL, crypto constraints
agent_groups Dynamic device grouping by OS, architecture, IP CIDR, version
agent_group_members Manual include/exclude membership for agent groups

Configuration

All server environment variables use the CERTCTL_ prefix:

Variable Default Description
CERTCTL_SERVER_HOST 127.0.0.1 Server bind address
CERTCTL_SERVER_PORT 8080 Server listen port
CERTCTL_DATABASE_URL postgres://localhost/certctl PostgreSQL connection string
CERTCTL_DATABASE_MAX_CONNS 25 Connection pool size
CERTCTL_LOG_LEVEL info Log level: debug, info, warn, error
CERTCTL_LOG_FORMAT json Log format: json or text
CERTCTL_AUTH_TYPE api-key Auth mode: api-key, jwt, or none
CERTCTL_AUTH_SECRET Required for api-key and jwt auth types
CERTCTL_KEYGEN_MODE agent Key generation mode: agent (production) or server (demo only)
CERTCTL_ACME_DIRECTORY_URL ACME directory URL (e.g., Let's Encrypt staging)
CERTCTL_ACME_EMAIL Contact email for ACME account registration
CERTCTL_ACME_CHALLENGE_TYPE ACME challenge type: http-01 (default) or dns-01
CERTCTL_CA_CERT_PATH Path to CA certificate for sub-CA mode
CERTCTL_CA_KEY_PATH Path to CA private key for sub-CA mode
CERTCTL_STEPCA_URL step-ca server URL
CERTCTL_STEPCA_PROVISIONER step-ca JWK provisioner name

Agent environment variables:

Variable Default Description
CERTCTL_SERVER_URL http://localhost:8080 Control plane URL
CERTCTL_API_KEY Agent API key
CERTCTL_AGENT_NAME certctl-agent Agent display name
CERTCTL_AGENT_ID Registered agent ID (required)
CERTCTL_KEY_DIR /var/lib/certctl/keys Directory for storing private keys (agent keygen mode)

Docker Compose overrides these for the demo stack (see deploy/docker-compose.yml): port 8443, auth type none, database pointing to the postgres container.

API Overview

All endpoints are under /api/v1/ and return JSON. List endpoints support pagination (?page=1&per_page=50).

Certificates

GET    /api/v1/certificates              List (filter: status, environment, owner_id, team_id)
POST   /api/v1/certificates              Create
GET    /api/v1/certificates/{id}          Get
PUT    /api/v1/certificates/{id}          Update
DELETE /api/v1/certificates/{id}          Archive (soft delete)
GET    /api/v1/certificates/{id}/versions Version history
POST   /api/v1/certificates/{id}/renew    Trigger renewal → 202 Accepted
POST   /api/v1/certificates/{id}/deploy   Trigger deployment → 202 Accepted

Agents

GET    /api/v1/agents                     List
POST   /api/v1/agents                     Register
GET    /api/v1/agents/{id}                Get
POST   /api/v1/agents/{id}/heartbeat      Record heartbeat
POST   /api/v1/agents/{id}/csr            Submit CSR for issuance
GET    /api/v1/agents/{id}/certificates/{certId}  Retrieve signed certificate
GET    /api/v1/agents/{id}/work            Poll for pending deployment jobs
POST   /api/v1/agents/{id}/jobs/{jobId}/status  Report job completion/failure

Infrastructure

GET    /api/v1/issuers                    List issuers
POST   /api/v1/issuers                    Create
GET    /api/v1/issuers/{id}               Get
PUT    /api/v1/issuers/{id}               Update
DELETE /api/v1/issuers/{id}               Delete
POST   /api/v1/issuers/{id}/test          Test connectivity

GET    /api/v1/targets                    List deployment targets
POST   /api/v1/targets                    Create
GET    /api/v1/targets/{id}               Get
PUT    /api/v1/targets/{id}               Update
DELETE /api/v1/targets/{id}               Delete

Organization

GET    /api/v1/teams                      List teams
POST   /api/v1/teams                      Create
GET    /api/v1/teams/{id}                 Get
PUT    /api/v1/teams/{id}                 Update
DELETE /api/v1/teams/{id}                 Delete
GET    /api/v1/owners                     List owners
POST   /api/v1/owners                     Create
GET    /api/v1/owners/{id}                Get
PUT    /api/v1/owners/{id}                Update
DELETE /api/v1/owners/{id}                Delete

Operations

GET    /api/v1/jobs                       List (filter: status, type)
GET    /api/v1/jobs/{id}                  Get
POST   /api/v1/jobs/{id}/cancel           Cancel

GET    /api/v1/policies                   List policy rules
POST   /api/v1/policies                   Create
PUT    /api/v1/policies/{id}              Update (enable/disable)
DELETE /api/v1/policies/{id}              Delete
GET    /api/v1/policies/{id}/violations   List violations for rule

GET    /api/v1/audit                      Query audit trail
GET    /api/v1/notifications              List notifications
POST   /api/v1/notifications/{id}/read    Mark as read

Auth

GET    /api/v1/auth/info                  Auth mode info (no auth required)
GET    /api/v1/auth/check                 Validate credentials

Health

GET    /health                            Server health check
GET    /ready                             Readiness check

Supported Integrations

Certificate Issuers

Issuer Status Type
Local CA (self-signed + sub-CA) Implemented GenericCA
ACME v2 (Let's Encrypt, Sectigo) Implemented (HTTP-01 + DNS-01) ACME
step-ca Implemented StepCA
OpenSSL / Custom CA Planned (V2)
Vault PKI Planned
DigiCert Planned

Note: ADCS integration is handled via the Local CA's sub-CA mode — certctl operates as a subordinate CA with its signing certificate issued by ADCS.

Deployment Targets

Target Status Type
NGINX Implemented NGINX
Apache httpd Implemented Apache
HAProxy Implemented HAProxy
F5 BIG-IP Interface only (V2) F5
Microsoft IIS Interface only (V2) IIS
Kubernetes Secrets Planned

Notifiers

Notifier Status Type
Email (SMTP) Implemented Email
Webhooks Implemented Webhook
Slack Planned

Development

# Install dev tools (golangci-lint, migrate CLI, air)
make install-tools

# Run tests
make test

# Run with coverage
make test-coverage

# Lint
make lint

# Format
make fmt

Docker Compose

make docker-up          # Start stack (server + postgres + agent)
make docker-down        # Stop stack
make docker-logs-server # Server logs
make docker-logs-agent  # Agent logs
make docker-clean       # Stop + remove volumes

Security

Private Key Management

  • Agent keygen mode (default): Agents generate ECDSA P-256 keys locally and store them with 0600 permissions in CERTCTL_KEY_DIR (default /var/lib/certctl/keys). Only the CSR (public key) is sent to the control plane. Private keys never leave agent infrastructure.
  • Server keygen mode (demo only): Set CERTCTL_KEYGEN_MODE=server for development/demo with Local CA. The control plane generates RSA-2048 keys server-side. A log warning is emitted at startup.

Authentication

  • Agent-to-server: API key (registered at agent creation)
  • API key and JWT auth types supported; none for demo/development
  • Auth type and secret configured via CERTCTL_AUTH_TYPE and CERTCTL_AUTH_SECRET

Audit Trail

  • Immutable append-only log in PostgreSQL (audit_events table)
  • Every action attributed to an actor with timestamp and resource reference
  • No update or delete operations on audit records

Roadmap

V1 (v1.0.0 released)

All nine development milestones (M1M9) are complete. The backend covers the full certificate lifecycle: Local CA and ACME v2 issuers, NGINX/Apache/HAProxy/F5/IIS target connectors, threshold-based expiration alerting, agent-side ECDSA P-256 key generation, API auth with rate limiting, and a React dashboard with 16 pages wired to the real API. The CI pipeline runs build, vet, test with coverage gates (service layer 30%+, handler layer 50%+), frontend type checking, Vitest test suite, and Vite production build on every push. 525+ tests total: 431 Go test functions (192 service, 212 handler, 4 integration with 35+ subtests, 23 connector) plus 53 frontend Vitest tests covering API client functions and utility helpers. Docker images are published to GitHub Container Registry on every version tag via the release workflow.

V2: Operational Maturity

  • M10: Agent Metadata + Targets — agents report OS, architecture, IP, hostname, version via heartbeat; Apache httpd and HAProxy target connectors
  • M11: Crypto Policy + Profiles + Ownership — certificate profiles (named enrollment profiles with allowed key types, max TTL, crypto constraints), certificate ownership tracking (owners + teams + notification routing), dynamic agent groups (OS/arch/IP CIDR/version matching), interactive renewal approval (AwaitingApproval state)
  • M12: Sub-CA + DNS-01 + step-ca — Local CA sub-CA mode (enterprise root chain with RSA/ECDSA/PKCS#8), ACME DNS-01 challenges (script-based DNS hooks for any provider, wildcard cert support), step-ca issuer connector (native /sign API with JWK provisioner auth)
  • M13: GUI Operations — bulk cert operations (renew, revoke, reassign), deployment timeline, inline policy editor, target config wizard, audit export, short-lived credentials dashboard
  • M14: Enterprise Connectors — SSE/WebSocket real-time updates, F5 BIG-IP, IIS, ADCS, OpenSSL/Custom CA implementations
  • M15: Revocation Infrastructure — revocation API with reason codes, embedded OCSP responder, CRL endpoint, bulk revocation by profile/owner/agent, revocation webhooks
  • M16: Team Adoption — OIDC/SSO, RBAC (profile-gated), CLI tool, Slack/Teams/PagerDuty/OpsGenie notifiers, bulk cert import
  • M17: Observability — expiration calendar, health scores, compliance scoring, Prometheus metrics (issuance/revocation rates, OCSP latency), deployment rollback
  • M18: Integrations — MCP server (OpenClaw/Claude/Cursor), CT Log monitoring, DigiCert issuer, filesystem cert discovery

V3: Discovery, Visibility & Cloud

Discovery engine (passive/active scanning, cert chain validation, unknown cert detection, triage workflows), Kubernetes cert-manager external issuer, cloud targets (AWS ALB/IAM Roles Anywhere, Azure Key Vault/Managed Identity, Palo Alto, FortiGate, Citrix ADC, Kubernetes Secrets), extended issuers (Entrust, GlobalSign, Google CAS, EJBCA, Vault PKI), ServiceNow integration, Ansible module

V4+: Platform & Scale

Kubernetes CRD, Terraform provider, multi-region, HA control plane, HSM support, LDAP auth, API key scoping, multi-tenancy, SPIFFE/SPIRE federation, OPA policy backend, compliance reporting (NIST, SOC 2, PCI-DSS)

License

Certctl is licensed under the Business Source License 1.1. The source code is publicly available and free to use, modify, and self-host. The one restriction: you may not offer certctl as a managed/hosted certificate management service to third parties.