378 lines
15 KiB
YAML
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"
|