Files
homelab-optimized/ansible/automation/playbooks/certificate_renewal.yml
Gitea Mirror Bot b5e43a65a7
Some checks failed
Documentation / Build Docusaurus (push) Failing after 1m12s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-03-31 11:35:19 UTC
2026-03-31 11:35:19 +00:00

378 lines
15 KiB
YAML

---
# 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
ansible.builtin.command: "docker restart {{ item.service }}"
loop: "{{ current_certificates | selectattr('service', 'defined') | list }}"
when:
- restart_services | bool
- item.service is defined
register: service_restart_result
failed_when: false
changed_when: service_restart_result.rc == 0
- 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"