mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 12:21:31 +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>
109 lines
3.6 KiB
Bash
109 lines
3.6 KiB
Bash
#!/bin/bash
|
|
|
|
#
|
|
# Cloudflare DNS-01 Challenge Script (PRESENT)
|
|
#
|
|
# This script creates a DNS TXT record for ACME DNS-01 challenge validation.
|
|
# Called by certctl during the renewal process to prove domain ownership.
|
|
#
|
|
# certctl sets these environment variables before invoking this script:
|
|
# CERTCTL_DNS_DOMAIN - Base domain (e.g., "example.com")
|
|
# CERTCTL_DNS_FQDN - Full challenge FQDN (e.g., "_acme-challenge.example.com")
|
|
# CERTCTL_DNS_VALUE - Challenge value/token to place in the TXT record
|
|
#
|
|
# You must set these environment variables before running:
|
|
# CLOUDFLARE_API_TOKEN - Cloudflare API token with DNS:write permission
|
|
# CLOUDFLARE_ZONE_ID - Cloudflare zone ID for your domain
|
|
# (Find at: https://dash.cloudflare.com > Select Domain > Zone ID in sidebar)
|
|
#
|
|
# Error Handling:
|
|
# This script exits 0 on success, non-zero on failure.
|
|
# certctl will retry the renewal if this script fails.
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
# Get values from certctl environment variables
|
|
DOMAIN="${CERTCTL_DNS_DOMAIN:-}"
|
|
RECORD_NAME="${CERTCTL_DNS_FQDN:-}"
|
|
VALIDATION_TOKEN="${CERTCTL_DNS_VALUE:-}"
|
|
|
|
# Validate inputs
|
|
if [[ -z "$DOMAIN" || -z "$RECORD_NAME" || -z "$VALIDATION_TOKEN" ]]; then
|
|
echo "Error: Required certctl environment variables not set (CERTCTL_DNS_DOMAIN, CERTCTL_DNS_FQDN, CERTCTL_DNS_VALUE)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Validate environment
|
|
if [[ -z "${CLOUDFLARE_API_TOKEN:-}" ]]; then
|
|
echo "Error: CLOUDFLARE_API_TOKEN environment variable not set" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -z "${CLOUDFLARE_ZONE_ID:-}" ]]; then
|
|
echo "Error: CLOUDFLARE_ZONE_ID environment variable not set" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Validate RECORD_NAME (set by certctl above)
|
|
RECORD_TYPE="TXT"
|
|
RECORD_TTL=120 # Short TTL for challenge records (1-2 min)
|
|
|
|
# Cloudflare API endpoint
|
|
CF_API="https://api.cloudflare.com/client/v4"
|
|
CF_ZONE="$CLOUDFLARE_ZONE_ID"
|
|
CF_TOKEN="$CLOUDFLARE_API_TOKEN"
|
|
|
|
echo "[certctl DNS-01] Creating DNS record: $RECORD_NAME = $VALIDATION_TOKEN"
|
|
|
|
# Step 1: Check if record already exists (GET /zones/{zone_id}/dns_records)
|
|
# This is optional but helps with idempotency
|
|
EXISTING=$(curl -s -X GET \
|
|
"$CF_API/zones/$CF_ZONE/dns_records?name=$RECORD_NAME&type=$RECORD_TYPE" \
|
|
-H "Authorization: Bearer $CF_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
| jq -r '.result | if length > 0 then .[0].id else "" end')
|
|
|
|
if [[ -n "$EXISTING" ]]; then
|
|
echo "[certctl DNS-01] Record already exists (ID: $EXISTING). Updating..."
|
|
# Update existing record
|
|
RESPONSE=$(curl -s -X PUT \
|
|
"$CF_API/zones/$CF_ZONE/dns_records/$EXISTING" \
|
|
-H "Authorization: Bearer $CF_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"type\": \"$RECORD_TYPE\",
|
|
\"name\": \"$RECORD_NAME\",
|
|
\"content\": \"$VALIDATION_TOKEN\",
|
|
\"ttl\": $RECORD_TTL
|
|
}")
|
|
else
|
|
echo "[certctl DNS-01] Creating new DNS record..."
|
|
# Create new record
|
|
RESPONSE=$(curl -s -X POST \
|
|
"$CF_API/zones/$CF_ZONE/dns_records" \
|
|
-H "Authorization: Bearer $CF_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{
|
|
\"type\": \"$RECORD_TYPE\",
|
|
\"name\": \"$RECORD_NAME\",
|
|
\"content\": \"$VALIDATION_TOKEN\",
|
|
\"ttl\": $RECORD_TTL
|
|
}")
|
|
fi
|
|
|
|
# Check response success
|
|
SUCCESS=$(echo "$RESPONSE" | jq -r '.success')
|
|
if [[ "$SUCCESS" != "true" ]]; then
|
|
ERROR=$(echo "$RESPONSE" | jq -r '.errors[0].message // "Unknown error"')
|
|
echo "Error: Cloudflare API failed: $ERROR" >&2
|
|
exit 1
|
|
fi
|
|
|
|
RECORD_ID=$(echo "$RESPONSE" | jq -r '.result.id')
|
|
echo "[certctl DNS-01] Successfully created/updated DNS record (ID: $RECORD_ID)"
|
|
echo "[certctl DNS-01] Waiting for DNS propagation..."
|
|
sleep 10
|
|
|
|
exit 0
|