mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 12:41:30 +00:00
bcf2c3ae92
Pre-2.1.0 adoption polish delivering all four milestones: A) Demo Data Overhaul — seed_demo.sql rewritten with 35 certs across 5 issuers, 8 agents, 8 targets, 50+ jobs spanning 90 days, 55+ audit events, discovery scans, network scan targets, S/MIME cert. B) Examples Directory — 5 turnkey docker-compose configs: acme-nginx, acme-wildcard-dns01, private-ca-traefik, step-ca-haproxy, multi-issuer. C) Migration Guides — migrate-from-certbot.md, migrate-from-acmesh.md, certctl-for-cert-manager-users.md. D) Agent Install Script — install-agent.sh with cross-platform support (Linux systemd + macOS launchd), release.yml updated for 6-target cross-compilation. Triple-audited against codebase: 22 factual corrections applied across docs, examples, and config (env var names, CLI flags, ports, DNS hook interface, scheduler loop counts, license conversion date). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Private CA + Traefik Example
This example demonstrates certctl managing certificates for internal services without public CA dependency. Ideal for enterprise environments where:
- All services are internal (VPN, private networks)
- You need unified certificate lifecycle management across multiple internal apps
- You want automatic cert deployment to your reverse proxy
- You may have an existing enterprise root CA (ADCS, OpenCA, etc.)
What's Included
- certctl server with Local CA issuer (self-signed or sub-CA mode)
- certctl agent that deploys certificates to Traefik
- Traefik reverse proxy with file provider for dynamic cert discovery
- PostgreSQL database for certificate storage and audit trail
- Automatic certificate discovery for existing certs in Traefik
Architecture
┌──────────────────┐
│ certctl-server │ (Local CA issuer)
│ (control │
│ plane) │
└────────┬─────────┘
│
│ REST API (job polling)
│
┌────────▼──────────┐
│ certctl-agent │ (certificate deployer)
└────────┬──────────┘
│
│ Write cert/key files
│
┌────────▼──────────────────────┐
│ Traefik │
│ (watches cert directory) │
└────────────────────────────────┘
│
│ TLS handshakes
│
[Internal Services]
Quick Start (Self-Signed CA)
The simplest way to get running in 2 minutes:
# 1. Create directory structure
mkdir -p traefik-config ca-certs
# 2. Create a minimal Traefik dynamic config
cat > traefik-config/default.yaml << 'EOF'
# Traefik will auto-load certificates from /etc/traefik/certs
# Certctl deploys {cert-id}.crt and {cert-id}.key files here
http:
routers:
api:
rule: "Host(`api.internal.local`)"
service: api-service
tls: {}
services:
api-service:
loadBalancer:
servers:
- url: "http://localhost:3000"
EOF
# 3. Start the stack
docker compose up -d
# 4. Access the dashboards
# - certctl: http://localhost:8443 (API only, use the CLI or direct HTTP calls)
# - Traefik dashboard: http://localhost:8080
The self-signed CA will be automatically generated on first startup.
Using Sub-CA Mode (Enterprise Root CA)
If you have an existing enterprise CA (ADCS, OpenCA, etc.) and want issued certs to chain to your root:
# 1. Create directory structure
mkdir -p traefik-config ca-certs
# 2. Copy your enterprise CA cert and key
cp /path/to/your/enterprise-ca.crt ca-certs/ca-cert.pem
cp /path/to/your/enterprise-ca-key.pem ca-certs/ca-key.pem
# 3. Edit docker-compose.yml and uncomment the sub-CA env vars:
# CERTCTL_CA_CERT_PATH: /etc/certctl/ca-cert.pem
# CERTCTL_CA_KEY_PATH: /etc/certctl/ca-key.pem
# 4. Create the dynamic config (same as above)
mkdir -p traefik-config
cat > traefik-config/default.yaml << 'EOF'
http:
routers:
api:
rule: "Host(`api.internal.local`)"
service: api-service
tls: {}
services:
api-service:
loadBalancer:
servers:
- url: "http://localhost:3000"
EOF
# 5. Start the stack
docker compose up -d
Requirements for sub-CA mode:
- CA certificate must have
X509v3 Basic Constraints: CA:TRUE - CA certificate must have
X509v3 Key Usage: Certificate Sign - Key format: RSA, ECDSA, or PKCS#8
- Paths: must be absolute paths to mounted files
Creating a Certificate
Once the stack is running:
# 1. Create a certificate profile in certctl (defines allowed key types, TTL, etc.)
curl -X POST http://localhost:8443/api/v1/profiles \
-H "Content-Type: application/json" \
-d '{
"id": "prof-internal",
"name": "Internal Services",
"description": "For internal APIs and web apps",
"max_ttl_hours": 8760,
"key_types": ["rsa-2048", "ecdsa-p256"]
}'
# 2. Create a renewal policy (defines issuer, renewal thresholds, etc.)
curl -X POST http://localhost:8443/api/v1/policies \
-H "Content-Type: application/json" \
-d '{
"id": "pol-internal",
"name": "Internal Renewal Policy",
"issuer_id": "iss-local",
"profile_id": "prof-internal",
"renewal_threshold_days": 30,
"alert_thresholds_days": [30, 14, 7, 0]
}'
# 3. Create a certificate (triggers issuance immediately)
curl -X POST http://localhost:8443/api/v1/certificates \
-H "Content-Type: application/json" \
-d '{
"common_name": "api.internal.local",
"sans": ["app.internal.local", "www.internal.local"],
"policy_id": "pol-internal"
}'
# 4. Create a Traefik target (agent will deploy to this)
curl -X POST http://localhost:8443/api/v1/targets \
-H "Content-Type: application/json" \
-d '{
"id": "target-traefik-01",
"name": "Traefik Primary",
"type": "traefik",
"config": {
"cert_dir": "/etc/traefik/certs"
}
}'
# 5. Create a deployment job (agent picks this up and deploys)
curl -X POST http://localhost:8443/api/v1/certificates/{cert-id}/deploy \
-H "Content-Type: application/json" \
-d '{
"target_ids": ["target-traefik-01"]
}'
Once deployed, Traefik automatically loads the new certificate from the certs directory.
How It Works
Certificate Lifecycle
- Issue — certctl-server generates certificate from Local CA (self-signed or sub-CA)
- Store — certificate stored in PostgreSQL with full audit trail
- Deploy — certctl-agent writes
{cert-id}.crt+{cert-id}.keyto/etc/traefik/certs - Reload — Traefik file provider detects new files and hot-loads them (zero downtime)
- Monitor — certctl tracks deployment status and renewal timelines
Self-Signed CA
- Generated automatically on first startup if
CERTCTL_CA_CERT_PATHandCERTCTL_CA_KEY_PATHare not set - Certificate stored in server's in-memory state (not persisted)
- All issued certs chain to this self-signed root
- Use this for: demos, development, internal labs
Sub-CA Mode
- Requires you to provide an existing CA certificate and key
- Issued certificates chain to your enterprise root CA
- All issued certs are trustworthy to systems with your root CA in their trust store
- Use this for: production internal services, compliance requirements, enterprise PKI
File Organization
private-ca-traefik/
├── docker-compose.yml # Stack definition
├── traefik-config/ # Traefik dynamic config (you create)
│ └── default.yaml # Routing rules and TLS settings
├── ca-certs/ # CA certificate and key (for sub-CA mode)
│ ├── ca-cert.pem # Your enterprise CA certificate
│ └── ca-key.pem # Your enterprise CA private key
└── README.md # This file
Monitoring
certctl Dashboard
The server provides a REST API on port 8443. Example queries:
# List all certificates
curl http://localhost:8443/api/v1/certificates
# Check certificate status
curl http://localhost:8443/api/v1/certificates/{cert-id}
# View audit trail
curl http://localhost:8443/api/v1/audit
# Check renewal policy compliance
curl http://localhost:8443/api/v1/policies/{policy-id}
Traefik Dashboard
http://localhost:8080 shows:
- HTTP routers and services
- TLS certificates currently loaded
- Request/response metrics
Logs
# certctl server logs
docker compose logs certctl-server
# certctl agent logs
docker compose logs certctl-agent
# Traefik logs
docker compose logs traefik
Customizing Traefik Config
Edit traefik-config/default.yaml to add routers for your services:
http:
routers:
# Internal API
api:
rule: "Host(`api.internal.local`)"
service: api-service
tls: {}
# Web application
webapp:
rule: "Host(`app.internal.local`)"
service: webapp-service
tls: {}
services:
api-service:
loadBalancer:
servers:
- url: "http://api-backend:3000"
webapp-service:
loadBalancer:
servers:
- url: "http://webapp-backend:3001"
Changes are picked up automatically (file watcher enabled).
Production Considerations
- Use sub-CA mode — chain to your enterprise root for full trust
- Enable API key authentication — set
CERTCTL_AUTH_TYPE: api-keyandCERTCTL_API_KEY - Use agent-side key generation — set
CERTCTL_KEYGEN_MODE: agent(keys never leave agents) - Back up PostgreSQL — certificate data is authoritative; database loss means certificate loss
- Monitor renewal windows — set up alerts on policy thresholds
- Rotate CA keys regularly — plan for future CA refresh (sub-CA mode)
- Audit certificate usage — review
certctl_audit_eventsfor compliance
Troubleshooting
Certificates not deploying
# Check agent is healthy
docker compose logs certctl-agent | grep heartbeat
# Check deployment job status
curl http://localhost:8443/api/v1/jobs | jq '.[] | select(.type == "Deployment")'
# Check Traefik is watching the directory
docker compose exec traefik ls -la /etc/traefik/certs/
Traefik not reloading certs
# Verify file provider is enabled (check docker-compose.yml command)
# Verify certs volume is mounted at /etc/traefik/certs
# Check Traefik logs
docker compose logs traefik | grep "file"
CA cert not loading in sub-CA mode
# Verify file permissions
docker compose exec certctl-server ls -la /etc/certctl/
# Check server logs for CA loading errors
docker compose logs certctl-server | grep -i "ca\|cert"
# Verify CA certificate format
openssl x509 -in ca-certs/ca-cert.pem -text -noout | grep -A 3 "Basic Constraints"
Cleanup
# Stop all services
docker compose down
# Remove all data (certificates, database, etc.)
docker compose down -v
# Remove CA cert files (if using custom CA)
rm -rf ca-certs/
Next Steps
- Add more services — create additional routers and backends in
traefik-config/default.yaml - Set up renewal automation — configure renewal policies with thresholds
- Integrate with monitoring — expose certctl metrics to Prometheus
- Enable notifications — configure email/Slack alerts on certificate events
- Scale to multiple environments — deploy separate certctl stacks per environment (dev/staging/prod)