Files
homelab-optimized/ansible/automation/playbooks/certificate_renewal.yml
Gitea Mirror Bot 8e49624d78
Some checks failed
Documentation / Build Docusaurus (push) Failing after 21m3s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-03-18 10:31:50 UTC
2026-03-18 10:31:50 +00:00

414 lines
16 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
# SSL Certificate Management and Renewal Playbook
# Manage Let's Encrypt certificates and other SSL certificates
# Usage: ansible-playbook playbooks/certificate_renewal.yml
# Usage: ansible-playbook playbooks/certificate_renewal.yml -e "force_renewal=true"
# Usage: ansible-playbook playbooks/certificate_renewal.yml -e "check_only=true"
- name: SSL Certificate Management and Renewal
hosts: "{{ host_target | default('all') }}"
gather_facts: yes
vars:
force_renewal: "{{ force_renewal | default(false) }}"
check_only: "{{ check_only | default(false) }}"
renewal_threshold_days: "{{ renewal_threshold_days | default(30) }}"
backup_certificates: "{{ backup_certificates | default(true) }}"
restart_services: "{{ restart_services | default(true) }}"
# Certificate locations and services
certificate_configs:
atlantis:
- name: "nginx-proxy-manager"
cert_path: "/volume1/docker/nginx-proxy-manager/data/letsencrypt"
domains: ["*.vish.gg", "vish.gg"]
service: "nginx-proxy-manager"
renewal_method: "npm" # Nginx Proxy Manager handles this
- name: "synology-dsm"
cert_path: "/usr/syno/etc/certificate"
domains: ["atlantis.vish.local"]
service: "nginx"
renewal_method: "synology"
calypso:
- name: "nginx-proxy-manager"
cert_path: "/volume1/docker/nginx-proxy-manager/data/letsencrypt"
domains: ["*.calypso.local"]
service: "nginx-proxy-manager"
renewal_method: "npm"
homelab_vm:
- name: "nginx"
cert_path: "/etc/letsencrypt"
domains: ["homelab.vish.gg"]
service: "nginx"
renewal_method: "certbot"
- name: "traefik"
cert_path: "/opt/docker/traefik/certs"
domains: ["*.homelab.vish.gg"]
service: "traefik"
renewal_method: "traefik"
tasks:
- name: Create certificate report directory
file:
path: "/tmp/certificate_reports/{{ ansible_date_time.date }}"
state: directory
mode: '0755'
delegate_to: localhost
- name: Get current certificate configurations for this host
set_fact:
current_certificates: "{{ certificate_configs.get(inventory_hostname, []) }}"
- name: Display certificate management plan
debug:
msg: |
🔒 CERTIFICATE MANAGEMENT PLAN
==============================
🖥️ Host: {{ inventory_hostname }}
📅 Date: {{ ansible_date_time.date }}
🔍 Check Only: {{ check_only }}
🔄 Force Renewal: {{ force_renewal }}
📅 Renewal Threshold: {{ renewal_threshold_days }} days
💾 Backup Certificates: {{ backup_certificates }}
📋 Certificates to manage: {{ current_certificates | length }}
{% for cert in current_certificates %}
- {{ cert.name }}: {{ cert.domains | join(', ') }}
{% endfor %}
- name: Check certificate expiration dates
shell: |
cert_info_file="/tmp/certificate_reports/{{ ansible_date_time.date }}/{{ inventory_hostname }}_cert_info.txt"
echo "🔒 CERTIFICATE STATUS REPORT - {{ inventory_hostname }}" > "$cert_info_file"
echo "=================================================" >> "$cert_info_file"
echo "Date: {{ ansible_date_time.iso8601 }}" >> "$cert_info_file"
echo "Renewal Threshold: {{ renewal_threshold_days }} days" >> "$cert_info_file"
echo "" >> "$cert_info_file"
{% for cert in current_certificates %}
echo "=== {{ cert.name }} ===" >> "$cert_info_file"
echo "Domains: {{ cert.domains | join(', ') }}" >> "$cert_info_file"
echo "Method: {{ cert.renewal_method }}" >> "$cert_info_file"
# Check certificate expiration for each domain
{% for domain in cert.domains %}
echo "Checking {{ domain }}..." >> "$cert_info_file"
# Try different methods to check certificate
if command -v openssl &> /dev/null; then
# Method 1: Check via SSL connection (if accessible)
cert_info=$(echo | timeout 10 openssl s_client -servername {{ domain }} -connect {{ domain }}:443 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
if [ $? -eq 0 ]; then
echo " SSL Connection: ✅" >> "$cert_info_file"
echo " $cert_info" >> "$cert_info_file"
# Calculate days until expiration
not_after=$(echo "$cert_info" | grep notAfter | cut -d= -f2)
if [ -n "$not_after" ]; then
exp_date=$(date -d "$not_after" +%s 2>/dev/null || echo "0")
current_date=$(date +%s)
days_left=$(( (exp_date - current_date) / 86400 ))
echo " Days until expiration: $days_left" >> "$cert_info_file"
if [ $days_left -lt {{ renewal_threshold_days }} ]; then
echo " Status: ⚠️ RENEWAL NEEDED" >> "$cert_info_file"
else
echo " Status: ✅ Valid" >> "$cert_info_file"
fi
fi
else
echo " SSL Connection: ❌ Failed" >> "$cert_info_file"
fi
# Method 2: Check local certificate files
{% if cert.cert_path %}
if [ -d "{{ cert.cert_path }}" ]; then
echo " Local cert path: {{ cert.cert_path }}" >> "$cert_info_file"
# Find certificate files
cert_files=$(find {{ cert.cert_path }} -name "*.crt" -o -name "*.pem" -o -name "fullchain.pem" 2>/dev/null | head -5)
if [ -n "$cert_files" ]; then
echo " Certificate files found:" >> "$cert_info_file"
for cert_file in $cert_files; do
echo " $cert_file" >> "$cert_info_file"
if openssl x509 -in "$cert_file" -noout -dates 2>/dev/null; then
local_cert_info=$(openssl x509 -in "$cert_file" -noout -dates 2>/dev/null)
echo " $local_cert_info" >> "$cert_info_file"
fi
done
else
echo " No certificate files found in {{ cert.cert_path }}" >> "$cert_info_file"
fi
else
echo " Certificate path {{ cert.cert_path }} not found" >> "$cert_info_file"
fi
{% endif %}
else
echo " OpenSSL not available" >> "$cert_info_file"
fi
echo "" >> "$cert_info_file"
{% endfor %}
echo "" >> "$cert_info_file"
{% endfor %}
cat "$cert_info_file"
register: certificate_status
changed_when: false
- name: Backup existing certificates
shell: |
backup_dir="/tmp/certificate_backups/{{ ansible_date_time.epoch }}"
mkdir -p "$backup_dir"
echo "Creating certificate backup..."
{% for cert in current_certificates %}
{% if cert.cert_path %}
if [ -d "{{ cert.cert_path }}" ]; then
echo "Backing up {{ cert.name }}..."
tar -czf "$backup_dir/{{ cert.name }}_backup.tar.gz" -C "$(dirname {{ cert.cert_path }})" "$(basename {{ cert.cert_path }})" 2>/dev/null || echo "Backup failed for {{ cert.name }}"
fi
{% endif %}
{% endfor %}
echo "✅ Certificate backup created at $backup_dir"
ls -la "$backup_dir"
register: certificate_backup
when:
- backup_certificates | bool
- not check_only | bool
- name: Renew certificates via Certbot
shell: |
echo "🔄 Renewing certificates via Certbot..."
{% if force_renewal %}
certbot renew --force-renewal --quiet
{% else %}
certbot renew --quiet
{% endif %}
if [ $? -eq 0 ]; then
echo "✅ Certbot renewal successful"
else
echo "❌ Certbot renewal failed"
exit 1
fi
register: certbot_renewal
when:
- not check_only | bool
- current_certificates | selectattr('renewal_method', 'equalto', 'certbot') | list | length > 0
ignore_errors: yes
- name: Check Nginx Proxy Manager certificates
shell: |
echo "🔍 Checking Nginx Proxy Manager certificates..."
{% for cert in current_certificates %}
{% if cert.renewal_method == 'npm' %}
if [ -d "{{ cert.cert_path }}" ]; then
echo "NPM certificate path exists: {{ cert.cert_path }}"
# NPM manages certificates automatically, just check status
find {{ cert.cert_path }} -name "*.pem" -mtime -1 | head -5 | while read cert_file; do
echo "Recent certificate: $cert_file"
done
else
echo "NPM certificate path not found: {{ cert.cert_path }}"
fi
{% endif %}
{% endfor %}
register: npm_certificate_check
when: current_certificates | selectattr('renewal_method', 'equalto', 'npm') | list | length > 0
changed_when: false
- name: Restart services after certificate renewal
shell: |
echo "🔄 Restarting services after certificate renewal..."
services_restarted=()
{% for cert in current_certificates %}
{% if cert.service %}
echo "Restarting {{ cert.service }}..."
# Different restart methods based on service type
case "{{ cert.service }}" in
"nginx-proxy-manager")
if docker ps --filter "name=nginx-proxy-manager" --format "{{.Names}}" | grep -q nginx-proxy-manager; then
docker restart nginx-proxy-manager
services_restarted+=("nginx-proxy-manager")
fi
;;
"nginx")
if systemctl is-active --quiet nginx; then
systemctl reload nginx
services_restarted+=("nginx")
fi
;;
"traefik")
if docker ps --filter "name=traefik" --format "{{.Names}}" | grep -q traefik; then
docker restart traefik
services_restarted+=("traefik")
fi
;;
*)
echo "Unknown service: {{ cert.service }}"
;;
esac
{% endif %}
{% endfor %}
if [ ${#services_restarted[@]} -gt 0 ]; then
echo "✅ Services restarted: ${services_restarted[*]}"
else
echo " No services needed restarting"
fi
register: service_restart_result
when:
- restart_services | bool
- not check_only | bool
- (certbot_renewal.changed | default(false)) or (force_renewal | bool)
- name: Verify certificate renewal
shell: |
echo "🔍 Verifying certificate renewal..."
verification_results=()
{% for cert in current_certificates %}
{% for domain in cert.domains %}
echo "Verifying {{ domain }}..."
if command -v openssl &> /dev/null; then
# Check certificate via SSL connection
cert_info=$(echo | timeout 10 openssl s_client -servername {{ domain }} -connect {{ domain }}:443 2>/dev/null | openssl x509 -noout -dates 2>/dev/null)
if [ $? -eq 0 ]; then
not_after=$(echo "$cert_info" | grep notAfter | cut -d= -f2)
if [ -n "$not_after" ]; then
exp_date=$(date -d "$not_after" +%s 2>/dev/null || echo "0")
current_date=$(date +%s)
days_left=$(( (exp_date - current_date) / 86400 ))
if [ $days_left -gt {{ renewal_threshold_days }} ]; then
echo "✅ {{ domain }}: $days_left days remaining"
verification_results+=("{{ domain }}:OK:$days_left")
else
echo "⚠️ {{ domain }}: Only $days_left days remaining"
verification_results+=("{{ domain }}:WARNING:$days_left")
fi
else
echo "❌ {{ domain }}: Cannot parse expiration date"
verification_results+=("{{ domain }}:ERROR:unknown")
fi
else
echo "❌ {{ domain }}: SSL connection failed"
verification_results+=("{{ domain }}:ERROR:connection_failed")
fi
else
echo "⚠️ Cannot verify {{ domain }}: OpenSSL not available"
verification_results+=("{{ domain }}:SKIP:no_openssl")
fi
{% endfor %}
{% endfor %}
echo ""
echo "📊 VERIFICATION SUMMARY:"
for result in "${verification_results[@]}"; do
echo "$result"
done
register: certificate_verification
changed_when: false
- name: Generate certificate management report
copy:
content: |
🔒 CERTIFICATE MANAGEMENT REPORT - {{ inventory_hostname }}
======================================================
📅 Management Date: {{ ansible_date_time.iso8601 }}
🖥️ Host: {{ inventory_hostname }}
🔍 Check Only: {{ check_only }}
🔄 Force Renewal: {{ force_renewal }}
📅 Renewal Threshold: {{ renewal_threshold_days }} days
💾 Backup Created: {{ backup_certificates }}
📋 CERTIFICATES MANAGED: {{ current_certificates | length }}
{% for cert in current_certificates %}
- {{ cert.name }}: {{ cert.domains | join(', ') }} ({{ cert.renewal_method }})
{% endfor %}
📊 CERTIFICATE STATUS:
{{ certificate_status.stdout }}
{% if not check_only %}
🔄 RENEWAL ACTIONS:
{% if certbot_renewal is defined %}
Certbot Renewal: {{ 'Success' if certbot_renewal.rc == 0 else 'Failed' }}
{% endif %}
{% if service_restart_result is defined %}
Service Restarts:
{{ service_restart_result.stdout }}
{% endif %}
{% if backup_certificates %}
💾 BACKUP INFO:
{{ certificate_backup.stdout }}
{% endif %}
{% endif %}
🔍 VERIFICATION RESULTS:
{{ certificate_verification.stdout }}
💡 RECOMMENDATIONS:
- Schedule regular certificate checks via cron
- Monitor certificate expiration alerts
- Test certificate renewal in staging environment
- Keep certificate backups in secure location
{% if current_certificates | selectattr('renewal_method', 'equalto', 'npm') | list | length > 0 %}
- Nginx Proxy Manager handles automatic renewal
{% endif %}
✅ CERTIFICATE MANAGEMENT COMPLETE
dest: "/tmp/certificate_reports/{{ ansible_date_time.date }}/{{ inventory_hostname }}_cert_report.txt"
delegate_to: localhost
- name: Display certificate management summary
debug:
msg: |
✅ CERTIFICATE MANAGEMENT COMPLETE - {{ inventory_hostname }}
====================================================
📅 Date: {{ ansible_date_time.date }}
🔍 Mode: {{ 'Check Only' if check_only else 'Full Management' }}
📋 Certificates: {{ current_certificates | length }}
{{ certificate_verification.stdout }}
📄 Full report: /tmp/certificate_reports/{{ ansible_date_time.date }}/{{ inventory_hostname }}_cert_report.txt
🔍 Next Steps:
{% if check_only %}
- Run without check_only to perform renewals
{% endif %}
- Schedule regular certificate monitoring
- Set up expiration alerts
- Test certificate functionality
====================================================
- name: Send certificate alerts (if configured)
debug:
msg: |
📧 CERTIFICATE ALERT
Host: {{ inventory_hostname }}
Certificates expiring soon detected!
Check the full report for details.
when:
- send_alerts | default(false) | bool
- "'WARNING' in certificate_verification.stdout"