Files
certctl/docs/quickstart.md
T
2026-03-14 08:22:17 -04:00

10 KiB

Certctl Quick Start Guide

Get a working certctl deployment from zero to managing certificates in 10 minutes.

Prerequisites

  • Docker and Docker Compose (recommended), or:
    • Go 1.22+
    • PostgreSQL 14+
    • psql CLI tool

Option 1: Docker Compose (Fastest)

1. Clone & Setup

git clone https://github.com/shankar0123/certctl.git
cd certctl

# Copy environment template
cp .env.example .env

# Optional: edit .env for custom settings
# nano .env

2. Start the Stack

make docker-up

# Wait for services to be healthy (~30 seconds)
docker-compose -f deploy/docker-compose.yml ps

You should see:

NAME                 STATUS
certctl-postgres     Up (healthy)
certctl-server       Up (healthy)
certctl-agent        Up

3. Verify Health

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

# Container logs
make docker-logs-server

Option 2: Manual Build & Run

1. Clone & Dependencies

git clone https://github.com/shankar0123/certctl.git
cd certctl

go mod download

2. Setup PostgreSQL

# Create database and user
psql -U postgres -h localhost << EOF
CREATE USER certctl WITH PASSWORD 'certctl';
CREATE DATABASE certctl OWNER certctl;
GRANT ALL PRIVILEGES ON DATABASE certctl TO certctl;
EOF

# Verify connection
psql -h localhost -U certctl -d certctl -c "SELECT 1"

3. Run Migrations

# Install migrate tool
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest

# Set database URL
export DB_URL="postgres://certctl:certctl@localhost:5432/certctl?sslmode=disable"

# Run migrations
make migrate-up

4. Start Server

# Terminal 1: Server
make run

# Expected output:
# 2024-03-14T10:30:00Z server starting version=1.0.0 server_port=8443

5. Start Agent (Optional)

# Terminal 2: Agent
export SERVER_URL=http://localhost:8443
export API_KEY=default-api-key
./bin/agent

# Expected output: Agent connecting to http://localhost:8443

Walk-Through: Create Your First Certificate

Step 1: Verify API Access

curl -X GET http://localhost:8443/health

Response:

{"status":"healthy"}

Step 2: Create a Team (Optional)

Teams organize ownership and auditing. For this quick start, we'll use a default team.

TEAM_ID="default"

Step 3: Register an ACME Issuer

Create a certificate issuer configuration (Let's Encrypt staging for this demo):

curl -X POST http://localhost:8443/api/v1/issuers \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "default",
    "name": "lets-encrypt-staging",
    "type": "acme",
    "config": {
      "directory_url": "https://acme-staging-v02.api.letsencrypt.org/directory",
      "email": "admin@example.com"
    }
  }'

Response (save the issuer_id):

{
  "id": "issuer-abc123",
  "name": "lets-encrypt-staging",
  "type": "acme",
  "created_at": "2024-03-14T10:30:00Z"
}

Store the issuer ID:

ISSUER_ID="issuer-abc123"

Step 4: Register an Agent

Agents handle certificate requests and deployment. Register one:

curl -X POST http://localhost:8443/api/v1/agents \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "default",
    "name": "quickstart-agent",
    "description": "Local development agent"
  }'

Response (save the api_key and id):

{
  "id": "agent-xyz789",
  "name": "quickstart-agent",
  "api_key": "ey...",
  "registered_at": "2024-03-14T10:30:00Z",
  "status": "registered"
}

Store the agent details:

AGENT_ID="agent-xyz789"
AGENT_API_KEY="ey..."

Step 5: Create a Deployment Target

Targets are where certificates will be deployed (NGINX, F5, etc.). For this demo, we'll skip actual deployment:

curl -X POST http://localhost:8443/api/v1/targets \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "default",
    "agent_id": "'$AGENT_ID'",
    "name": "example-nginx",
    "type": "nginx",
    "config": {
      "host": "nginx.example.com",
      "ssh_user": "deploy",
      "ssh_key": "/path/to/key",
      "cert_path": "/etc/nginx/ssl"
    }
  }'

Response:

{
  "id": "target-def456",
  "name": "example-nginx",
  "agent_id": "agent-xyz789",
  "type": "nginx",
  "status": "pending_validation"
}

Store the target ID:

TARGET_ID="target-def456"

Step 6: Create a Managed Certificate

Now the main event—request a certificate to be issued and managed:

curl -X POST http://localhost:8443/api/v1/certificates \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "default",
    "domain": "api.example.com",
    "issuer_id": "'$ISSUER_ID'",
    "target_ids": ["'$TARGET_ID'"],
    "renewal_days_before": 30,
    "auto_deploy": true
  }'

Response:

{
  "id": "cert-ghi012",
  "domain": "api.example.com",
  "issuer_id": "issuer-abc123",
  "status": "pending",
  "created_at": "2024-03-14T10:30:00Z",
  "expires_at": null,
  "renewal_at": null
}

Store the certificate ID:

CERT_ID="cert-ghi012"

Step 7: Check Certificate Status

Poll the certificate status as issuance progresses:

curl -X GET http://localhost:8443/api/v1/certificates/$CERT_ID \
  -H "Content-Type: application/json"

Response (will change over time):

{
  "id": "cert-ghi012",
  "domain": "api.example.com",
  "status": "issued",
  "expires_at": "2024-06-12T10:30:00Z",
  "issued_by": "issuer-abc123",
  "deployed_to": [
    {
      "target_id": "target-def456",
      "status": "success",
      "deployed_at": "2024-03-14T10:30:30Z"
    }
  ]
}

Step 8: View Audit Trail

See all actions related to your certificate:

curl -X GET "http://localhost:8443/api/v1/audit/logs?resource_id=$CERT_ID" \
  -H "Content-Type: application/json"

Response:

{
  "logs": [
    {
      "id": "audit-001",
      "timestamp": "2024-03-14T10:30:00Z",
      "actor": {
        "type": "api",
        "id": "client-001"
      },
      "action": "certificate_created",
      "resource": {
        "type": "certificate",
        "id": "cert-ghi012"
      },
      "status": "success"
    },
    {
      "id": "audit-002",
      "timestamp": "2024-03-14T10:30:10Z",
      "actor": {
        "type": "agent",
        "id": "agent-xyz789"
      },
      "action": "certificate_issued",
      "resource": {
        "type": "certificate",
        "id": "cert-ghi012"
      },
      "status": "success",
      "details": {
        "issuer": "lets-encrypt-staging",
        "expiry": "2024-06-12"
      }
    },
    {
      "id": "audit-003",
      "timestamp": "2024-03-14T10:30:25Z",
      "actor": {
        "type": "system",
        "id": "scheduler"
      },
      "action": "certificate_deployed",
      "resource": {
        "type": "certificate",
        "id": "cert-ghi012"
      },
      "status": "success",
      "details": {
        "deployed_to": "example-nginx"
      }
    }
  ]
}

Step 9: Trigger Manual Renewal (Optional)

To manually trigger certificate renewal:

curl -X POST http://localhost:8443/api/v1/certificates/$CERT_ID/renew \
  -H "Content-Type: application/json"

The scheduler will automatically check for renewals every hour. Certificates within 30 days of expiry are renewed automatically.


Development Mode

For development with hot reload and database browser:

# Install tools
make install-tools

# Start dev stack (includes PgAdmin at localhost:5050)
make docker-up-dev

# View logs
make docker-logs-server
make docker-logs-agent

# Admin credentials for PgAdmin:
# Email: admin@example.com (default, see .env)
# Password: admin (default, see .env)

# Access PgAdmin: http://localhost:5050
# Add server: postgres, port 5432, user certctl, password certctl

Testing the Flow End-to-End

Here's a complete script to test the full flow:

#!/bin/bash
set -e

API="http://localhost:8443"
TEAM="default"

echo "1. Creating ACME issuer..."
ISSUER=$(curl -s -X POST $API/api/v1/issuers \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "'$TEAM'",
    "name": "letsencrypt-staging",
    "type": "acme",
    "config": {
      "directory_url": "https://acme-staging-v02.api.letsencrypt.org/directory",
      "email": "test@example.com"
    }
  }' | jq -r '.id')

echo "   Issuer: $ISSUER"

echo "2. Registering agent..."
AGENT=$(curl -s -X POST $API/api/v1/agents \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "'$TEAM'",
    "name": "test-agent"
  }' | jq -r '.id')

echo "   Agent: $AGENT"

echo "3. Creating certificate..."
CERT=$(curl -s -X POST $API/api/v1/certificates \
  -H "Content-Type: application/json" \
  -d '{
    "team_id": "'$TEAM'",
    "domain": "test-'$(date +%s)'.example.com",
    "issuer_id": "'$ISSUER'",
    "renewal_days_before": 30
  }' | jq -r '.id')

echo "   Certificate: $CERT"

echo "4. Checking status..."
curl -s -X GET $API/api/v1/certificates/$CERT | jq '.status'

echo "5. Viewing audit trail..."
curl -s -X GET "$API/api/v1/audit/logs?resource_id=$CERT" | jq '.logs | length'

echo "Done!"

Save as test.sh, make executable, and run:

chmod +x test.sh
./test.sh

Common Issues

Server Won't Start

# Check database connection
psql -h localhost -U certctl -d certctl -c "SELECT 1"

# View logs
make docker-logs-server

# Check environment
env | grep DB_

Agent Can't Connect

# Verify server is running
curl http://localhost:8443/health

# Check agent logs
docker logs certctl-agent

# Verify API key is correct
echo $AGENT_API_KEY

Certificate Stays "Pending"

# Check if agent is registered
curl http://localhost:8443/api/v1/agents

# Check agent logs for errors
make docker-logs-agent

# View certificate details
curl http://localhost:8443/api/v1/certificates/$CERT_ID

# Check audit trail
curl "http://localhost:8443/api/v1/audit/logs?resource_id=$CERT_ID"

Next Steps

  1. Read docs/architecture.md to understand the design
  2. Explore the API for more operations
  3. Build a custom connector for your infrastructure
  4. Deploy to production using docs/k8s-deployment.md (coming soon)

For more help, see README.md or open an issue on GitHub.