Files
certctl/docs/quickstart.md
T
shankar0123 7d14635a72 feat: add EST server (RFC 7030) for device certificate enrollment (M23)
Implement Enrollment over Secure Transport protocol with 4 endpoints under
/.well-known/est/ — cacerts (CA chain distribution), simpleenroll (initial
enrollment), simplereenroll (certificate renewal), and csrattrs (CSR
attributes). PKCS#7 certs-only wire format with hand-rolled ASN.1, accepts
both PEM and base64-encoded DER CSRs, configurable issuer and profile
binding, full audit trail. 28 new tests (18 handler + 10 service).

Also includes:
- GetCACertPEM added to issuer connector interface (all 4 issuers updated)
- EST integration tests wired into e2e test suite (13 test cases)
- QA testing guide Part 26 (15 manual EST test cases)
- All docs updated: README, features, architecture, concepts, connectors,
  quickstart, demo-advanced (endpoint counts, MCP wording, agent IDs,
  issuer interface, resource lists, OpenSSL status)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 15:31:06 -04:00

15 KiB

Quick Start Guide

Certificate lifespans are dropping to 47 days by 2029. At that cadence, a team managing 100 certificates is processing 7+ renewals per week — every week, forever. Manual processes break. certctl automates the entire lifecycle: issuance, renewal, deployment, revocation, and audit — with zero human intervention.

This guide gets you running in 5 minutes and walks you through everything certctl does.

New to certificates? Read the Concepts Guide first — it explains TLS, CAs, and private keys in plain language.

Prerequisites

You need Docker and Docker Compose installed. That's it.

On macOS:

brew install --cask docker

On Linux, follow the official Docker install guide for your distribution.

Start Everything

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

The --build flag builds the server image including the React frontend. Without it, Docker may use a stale cached image.

For production deployments, copy deploy/.env.example to deploy/.env and customize the credentials:

cp deploy/.env.example deploy/.env
# Edit deploy/.env to set secure POSTGRES_PASSWORD and CERTCTL_API_KEY values
docker compose -f deploy/docker-compose.yml up -d --build

Wait about 30 seconds for PostgreSQL to initialize, then verify:

docker compose -f deploy/docker-compose.yml ps

You should see:

NAME                 STATUS
certctl-postgres     Up (healthy)
certctl-server       Up (healthy)
certctl-agent        Up
curl http://localhost:8443/health
{"status":"healthy"}

Open the Dashboard

Open http://localhost:8443 in your browser.

The dashboard comes pre-loaded with 15 demo certificates across multiple teams, environments, and statuses — expiring certs, expired certs, active certs, failed renewals. A realistic snapshot of what certificate management looks like in a real organization.

What you're looking at

The main dashboard shows total certificates, how many are expiring soon, how many have expired, the renewal success rate, and four charts: an expiration heatmap (90-day weekly buckets), renewal success rate trends (30-day line chart), certificate status distribution (donut chart), and issuance rate (30-day bar chart).

Explore the sidebar: Certificates, Agents, Policies, Jobs, Audit Trail, Notifications, Profiles, Teams, Owners, Agent Groups, Fleet Overview, Short-Lived Credentials, Discovery.

Scenarios to walk through

"We're about to have an outage" — Filter certificates by status → Expiring. You'll see auth-production (12 days), cdn-production (8 days), and mail-production (5 days). At 47-day lifespans, this is every other week. certctl catches these automatically and triggers renewal before they expire.

"A renewal failed" — Look at vpn-production — status: Failed. Click it to see the audit trail showing the ACME challenge failure after 3 retry attempts. The system sent a webhook notification to the ops channel. No one had to notice manually.

"Who owns this cert?" — Click any certificate. Owner, team, environment, tags. Clear accountability. Notifications route to the owner's email automatically.

"Can I revoke a compromised cert?" — Click any active certificate, then "Revoke." A modal with RFC 5280 reason codes (Key Compromise, Superseded, Cessation of Operation). After revocation, CRL and OCSP are served automatically — clients stop trusting the cert immediately.

"What about certificates already in production?" — Click "Discovered Certificates." Agents scan local filesystems for existing certs. The server probes TLS endpoints on configured CIDR ranges. Both feed into a triage workflow: claim unmanaged certs to bring them under automation, or dismiss them.

"Show me the agent fleet" — Click "Agents." Four agents online, one offline. Click "Fleet Overview" for OS/architecture grouping, version distribution, and per-platform listing. Agents generate ECDSA P-256 keys locally — private keys never leave your infrastructure.

"What about bulk operations?" — On the Certificates page, select multiple certificates with checkboxes. A bulk action bar appears: trigger renewal, revoke with reason codes, or reassign ownership — all with progress tracking. At 47-day lifespans with hundreds of certs, bulk operations aren't optional.

"Short-lived credentials?" — Click "Short-Lived" in the sidebar. Live countdown timers for certificates with TTL under 1 hour. Auto-refresh every 10 seconds. These are for service-to-service auth where rapid expiry replaces revocation.

Explore the API

Everything you see in the dashboard is backed by the REST API. All endpoints live under /api/v1/ and return JSON.

Core operations

# List all certificates
curl -s http://localhost:8443/api/v1/certificates | jq .

# Filter by status
curl -s "http://localhost:8443/api/v1/certificates?status=Expiring" | jq .

# Filter by environment
curl -s "http://localhost:8443/api/v1/certificates?environment=production" | jq .

# Get a specific certificate
curl -s http://localhost:8443/api/v1/certificates/mc-api-prod | jq .

# Get deployment targets for a certificate
curl -s http://localhost:8443/api/v1/certificates/mc-api-prod/deployments | jq .

# List agents
curl -s http://localhost:8443/api/v1/agents | jq .

# Check agent pending work
curl -s http://localhost:8443/api/v1/agents/ag-web-prod/work | jq .

# View audit trail
curl -s http://localhost:8443/api/v1/audit | jq .

# View policies and violations
curl -s http://localhost:8443/api/v1/policies | jq .
curl -s http://localhost:8443/api/v1/policies/pr-require-owner/violations | jq .

# Notifications
curl -s http://localhost:8443/api/v1/notifications | jq .

# Profiles and agent groups
curl -s http://localhost:8443/api/v1/profiles | jq .
curl -s http://localhost:8443/api/v1/agent-groups | jq .

Sorting, filtering, and pagination

# Sort by expiration date (ascending)
curl -s "http://localhost:8443/api/v1/certificates?sort=notAfter" | jq .

# Sort descending (prefix with -)
curl -s "http://localhost:8443/api/v1/certificates?sort=-createdAt" | jq .

# Time-range filters (RFC3339)
curl -s "http://localhost:8443/api/v1/certificates?expires_before=2026-05-01T00:00:00Z" | jq .
curl -s "http://localhost:8443/api/v1/certificates?created_after=2026-03-01T00:00:00Z" | jq .

# Sparse fields — request only what you need
curl -s "http://localhost:8443/api/v1/certificates?fields=id,common_name,status,expires_at" | jq .

# Cursor pagination — efficient for large inventories
curl -s "http://localhost:8443/api/v1/certificates?page_size=5" | jq '{next_cursor: .next_cursor, count: (.data | length)}'
curl -s "http://localhost:8443/api/v1/certificates?cursor=<next_cursor_value>&page_size=5" | jq .

Supported sort fields: notAfter, expiresAt, createdAt, updatedAt, commonName, name, status, environment.

Stats and metrics

# Dashboard summary
curl -s http://localhost:8443/api/v1/stats/summary | jq .

# Certificates by status
curl -s http://localhost:8443/api/v1/stats/certificates-by-status | jq .

# Expiration timeline (next 90 days)
curl -s "http://localhost:8443/api/v1/stats/expiration-timeline?days=90" | jq .

# Job trends (last 30 days)
curl -s "http://localhost:8443/api/v1/stats/job-trends?days=30" | jq .

# JSON metrics
curl -s http://localhost:8443/api/v1/metrics | jq .

# Prometheus format (for Prometheus, Grafana Agent, Datadog)
curl -s http://localhost:8443/api/v1/metrics/prometheus

Create Your First Certificate

Create a certificate record that certctl will track, renew, and deploy automatically.

curl -s -X POST http://localhost:8443/api/v1/certificates \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My First Certificate",
    "common_name": "myapp.example.com",
    "sans": ["myapp.example.com", "www.myapp.example.com"],
    "environment": "staging",
    "owner_id": "o-alice",
    "team_id": "t-platform",
    "issuer_id": "iss-local",
    "renewal_policy_id": "rp-default",
    "status": "Pending",
    "tags": {"purpose": "quickstart-demo"}
  }' | jq .

Save the certificate ID (or provide your own id in the request body, e.g. "id": "mc-my-first"):

CERT_ID="<paste the id from the response>"

Trigger renewal:

curl -s -X POST http://localhost:8443/api/v1/certificates/$CERT_ID/renew | jq .

Check the result:

curl -s http://localhost:8443/api/v1/certificates/$CERT_ID | jq .

Refresh the dashboard at http://localhost:8443 — your new certificate appears in the inventory.

Revoke a certificate

When a private key is compromised or a service is decommissioned:

curl -s -X POST http://localhost:8443/api/v1/certificates/$CERT_ID/revoke \
  -H "Content-Type: application/json" \
  -d '{"reason": "superseded"}' | jq .

Supported RFC 5280 reason codes: unspecified, keyCompromise, caCompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold, privilegeWithdrawn.

Confirm via CRL:

curl -s http://localhost:8443/api/v1/crl | jq .

Interactive approval workflow

For high-value certificates where you want human oversight:

# Approve a pending job
curl -s -X POST http://localhost:8443/api/v1/jobs/JOB_ID/approve \
  -H "Content-Type: application/json" \
  -d '{"reason": "Approved for production deployment"}' | jq .

# Reject a pending job
curl -s -X POST http://localhost:8443/api/v1/jobs/JOB_ID/reject \
  -H "Content-Type: application/json" \
  -d '{"reason": "Key type does not meet compliance requirements"}' | jq .

Certificate Discovery

Find certificates already running in your infrastructure — ones you didn't issue through certctl.

Filesystem discovery (agent-based)

# Configure agent to scan directories
export CERTCTL_DISCOVERY_DIRS="/etc/nginx/certs,/etc/ssl/certs,/var/lib/certs"
# Agent scans on startup + every 6 hours

Network discovery (agentless)

# Enable network scanning
export CERTCTL_NETWORK_SCAN_ENABLED=true

# Create a scan target
curl -s -X POST http://localhost:8443/api/v1/network-scan-targets \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Internal Network",
    "cidrs": ["10.0.1.0/24"],
    "ports": [443, 8443],
    "enabled": true,
    "scan_interval_hours": 6,
    "timeout_ms": 5000
  }' | jq .

# Trigger an immediate scan
curl -s -X POST http://localhost:8443/api/v1/network-scan-targets/nst-internal-network/scan | jq .

Triage discovered certificates

# List discovered certs
curl -s "http://localhost:8443/api/v1/discovered-certificates?agent_id=agent-nginx-prod" | jq .

# Summary counts
curl -s http://localhost:8443/api/v1/discovery-summary | jq .

# Claim a discovered cert (bring under management)
curl -s -X POST "http://localhost:8443/api/v1/discovered-certificates/DISCOVERY_ID/claim" \
  -H "Content-Type: application/json" \
  -d '{"managed_certificate_id": "mc-api-prod"}' | jq .

CLI Tool

cd cmd/cli && go build -o certctl-cli .

export CERTCTL_SERVER_URL="http://localhost:8443"
export CERTCTL_API_KEY="test-key-123"

./certctl-cli certs list               # List certificates
./certctl-cli certs get mc-api-prod    # Certificate details
./certctl-cli certs renew mc-api-prod  # Trigger renewal
./certctl-cli certs revoke mc-api-prod --reason keyCompromise
./certctl-cli agents list              # List agents
./certctl-cli jobs list                # List jobs
./certctl-cli import /path/to/certs.pem  # Bulk import
./certctl-cli status                   # Health + stats

MCP Server (AI Integration)

cd cmd/mcp-server && go build -o mcp-server .

export CERTCTL_SERVER_URL="http://localhost:8443"
export CERTCTL_API_KEY="test-key-123"

./mcp-server

Exposes 78 MCP tools covering the REST API via stdio transport. Ask Claude: "What certificates are expiring in the next 30 days?", "Revoke the payments cert due to key compromise", "Show me the audit trail."

Demo Data Reference

Resource Count Examples
Teams 5 Platform, Security, Payments, Frontend, Data
Owners 5 Alice, Bob, Carol, Dave, Eve
Issuers 4 Local Dev CA, Let's Encrypt Staging, step-ca Internal, DigiCert (disabled)
Agents 5 ag-web-prod, ag-web-staging, ag-lb-prod, ag-iis-prod, ag-data-prod
Targets 5 NGINX (prod/staging/data), F5 LB, IIS
Certificates 15 Various statuses: Active, Expiring, Expired, Failed, Wildcard
Policies 4 Required owner, allowed environments, max lifetime, min renewal window
Profiles 3 Default TLS, Short-Lived, High-Security
Agent Groups 5 Linux agents, ARM agents, Production subnet, etc.

Dashboard Demo Mode

The dashboard works without a backend for screenshots and presentations:

cd web && npm install && npm run dev
# Dashboard at http://localhost:5173

When the API is unreachable, the dashboard loads realistic mock data with a "Demo Mode" badge.

Presenting to Stakeholders

A suggested 5-minute flow:

  1. Dashboard — "Certificate inventory at a glance. Real-time charts show expiration trends and renewal health."
  2. Expiring certs — "These three would have caused outages. At 47-day lifespans, this happens every other week."
  3. Certificate detail — "Full lifecycle: who owns it, where it's deployed, deployment timeline, version history with rollback."
  4. Revocation — "One click revokes with an RFC 5280 reason code. CRL and OCSP served automatically."
  5. Failed renewal — "System tried 3 times, then alerted the team via Slack, Teams, PagerDuty, or OpsGenie."
  6. Agent fleet — "Agents handle key generation locally (ECDSA P-256). Private keys never leave your infrastructure."
  7. Discovery — "Agents scan filesystems, server probes TLS endpoints. We find what you're not managing yet."
  8. Bulk operations — "Select multiple certs, renew or revoke in bulk. At 47-day lifespans with hundreds of certs, this is essential."
  9. Audit trail — "Every action recorded. Export to CSV/JSON for compliance."
  10. CLI + MCP — "Terminal users get certctl-cli. AI assistants get MCP integration. Everything is API-first."

Tear Down

docker compose -f deploy/docker-compose.yml down -v

The -v flag removes the PostgreSQL data volume for a clean slate.

What's Next

  • Advanced Demo — Issue a real certificate via the Local CA end-to-end
  • Architecture — How the control plane, agents, and connectors work together
  • Connector Guide — Build custom connectors for your infrastructure
  • Concepts Guide — TLS certificates, CAs, and private keys explained from scratch