Problema
Muchas organizaciones usan Let’s Encrypt para automatizar la emisión de certificados TLS. Cuando el dominio está detrás de un firewall o no se puede servir HTTP, la validación DNS‑01 es la única opción viable. La API del proveedor DNS necesita credenciales (clave API, token, usuario/contraseña) y, en entornos sin una estrategia centralizada, esas credenciales terminan copiadas a cada servidor que ejecuta el cliente ACME. El patrón típico es:
- Un script de solicitud de certificado en cada host.
- Variables de entorno o archivos de configuración con la clave del DNS.
- Credenciales estáticas que permanecen en el disco indefinidamente.
Este enfoque genera varios riesgos: exposición accidental, rotación imposible, falta de auditoría y, cuando se necesita un proceso de aprobación interno, no hay control sobre quién puede solicitar certificados. El problema se vuelve crítico al intentar desplegar certificados en dispositivos de borde (IPMI, impresoras, appliances) que no pueden almacenar secretos de forma segura.
Causa
- Ausencia de un almacén de secretos centralizado – sin Vault, AWS Secrets Manager o similar, cada nodo gestiona sus propias credenciales.
- Clientes ACME sin integración nativa a secret stores – la mayoría de los plugins DNS esperan variables de entorno o archivos locales.
- Falta de delegación DNS – en vez de crear zonas delegadas para cada servicio, se usa la zona principal con credenciales de administrador.
- Política de rotación inexistente – las claves se crean una vez y nunca se actualizan, lo que dificulta cumplir con requisitos de seguridad.
- Flujos de aprobación manuales – cuando un equipo solicita un certificado, no hay paso de revisión antes de que el cliente use la API DNS.
Solución
Implementar una arquitectura de “Secret‑as‑a‑Service” combinada con un cliente ACME que pueda leer esas credenciales en tiempo de ejecución. Los componentes clave son:
1. Almacén de secretos centralizado
Utiliza HashiCorp Vault, Azure Key Vault o AWS Secrets Manager. Guarda cada credencial DNS como un secreto versionado y habilita políticas de acceso basadas en roles (RBAC). Configura la rotación automática (por ejemplo, TTL de 30 días) y registra cada acceso.
2. Wrapper de ejecución para el cliente ACME
En lugar de ejecutar certbot o acme.sh directamente, crea un pequeño script que:
- Consulta el secreto en Vault usando un token de AppRole o IAM.
- Exporta las variables esperadas por el plugin DNS (por ejemplo,
CLOUDFLARE_API_TOKEN). - Lanza el cliente ACME con los parámetros habituales.
- Elimina las variables del entorno al terminar.
Este wrapper mantiene las credenciales fuera del disco y garantiza que solo la duración del proceso tengan presencia en memoria.
3. Uso de delegación DNS y tokens de zona limitada
En proveedores que soportan “API tokens de zona” (Cloudflare, Route 53, Gandi), genera tokens con permisos solo para crear/eliminar registros _acme-challenge.<subdominio>. Así, incluso si el token se filtra, el atacante no podrá modificar la zona completa.
4. Integración de flujo de aprobación
Antes de que el wrapper solicite el secreto, ejecuta una verificación contra un sistema de tickets (Jira, ServiceNow) o un webhook que requiera la aprobación de un responsable. La lógica puede ser tan simple como:
if ! curl -s -X POST -H "Authorization: Bearer $APPROVAL_TOKEN" \
https://approval.example.com/check?cert=$DOMAIN; then
echo "Aprobación denegada"
exit 1
fi
Esto separa la decisión de emisión del proceso técnico.
5. Despliegue a dispositivos de borde
Una vez el certificado está disponible, usa un agente ligero (Ansible, Salt, o un script SSH) que copie el archivo a la IPMI o a la impresora mediante su API. El agente no necesita credenciales DNS; solo necesita acceso al host que ejecutó el wrapper y a la API del dispositivo.
Cuándo aplicar esta solución
Se recomienda cuando:
- Se gestionan más de 5 dominios con validación DNS‑01.
- Existen dispositivos que no pueden almacenar credenciales de forma segura.
- La organización tiene requisitos de auditoría o rotación de secretos.
- Se necesita un proceso de aprobación interno antes de emitir certificados.
No es necesario si:
- Sólo se usan unos pocos dominios internos y el riesgo de exposición es bajo.
- El proveedor DNS permite crear tokens de solo‑lectura y la política de seguridad es mínima.
- No se requiere integración con sistemas de tickets.
Código
#!/usr/bin/env bash
set -euo pipefail
# Parámetros
DOMAIN="${1:?Falta dominio}"
VAULT_PATH="secret/dns/cloudflare/${DOMAIN}"
APPROLE_ID="my-approle-id"
SECRET_ID="my-secret-id"
# 1. Obtener token de Vault (AppRole)
VAULT_TOKEN=$(curl -s --request POST \
--data "{\"role_id\":\"${APPROLE_ID}\",\"secret_id\":\"${SECRET_ID}\"}" \
http://vault.local:8200/v1/auth/approle/login | jq -r .auth.client_token)
# 2. Aprobar solicitud (ejemplo simple)
if ! curl -s -X POST -H "Authorization: Bearer ${VAULT_TOKEN}" \
"https://approval.example.com/check?domain=${DOMAIN}" | grep -q approved; then
echo "Solicitud no aprobada"
exit 1
fi
# 3. Leer credencial DNS desde Vault
CLOUDFLARE_API_TOKEN=$(curl -s -H "X-Vault-Token: ${VAULT_TOKEN}" \
"http://vault.local:8200/v1/${VAULT_PATH}" | jq -r .data.token)
export CLOUDFLARE_API_TOKEN
# 4. Ejecutar certbot con plugin DNS Cloudflare
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials <(printf "dns_cloudflare_api_token = %s\n" "$CLOUDFLARE_API_TOKEN") \
-d "${DOMAIN}" -d "*.${DOMAIN}" \
--non-interactive --agree-tos --email [email protected]
# 5. Limpiar variable sensible
unset CLOUDFLARE_API_TOKEN
Verificación
- Comprobar que el secreto se recuperó – revisa los logs del wrapper; debe mostrar “Token obtenido” sin imprimir el valor.
- Validar que el certificado está en el directorio –
ls -l /etc/letsencrypt/live/${DOMAIN}debe contenerfullchain.pemyprivkey.pem. - Probar la cadena TLS – con
openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN}verifica que la cadena incluye el nuevo certificado. - Auditar acceso en Vault – en la UI de Vault revisa la política de auditoría para confirmar que solo el wrapper accedió al secreto.
Notas adicionales
- Rotación automática – configura en Vault un
lease_durationy unrenewscript que vuelva a generar tokens de zona limitada antes de que expiren. - Fallback a HTTP‑01 – si el DNS del proveedor no permite tokens de zona, considera usar un proxy interno que sirva el desafío HTTP‑01 desde una zona DMZ.
- Manejo de errores – el wrapper debe capturar fallos de renovación y notificar vía Slack o correo; de lo contrario, los dispositivos de borde quedarán sin certificado al expirar.
- Escalado – para cientos de dominios, agrupa los procesos en un job de CI/CD (GitLab CI, GitHub Actions) que invoque el wrapper bajo un agente dedicado. Esto evita que cada servidor tenga que ejecutar el cliente ACME.