Inhaltsverzeichnis
Rinnovo schedulato
Complessità: Bassa
Durata: 30-60 minuti di configurazione
Caso d'uso: Server senza ACME/Kubernetes
Rinnovo automatico dei certificati tramite job schedulati per ambienti server classici.
Architettura
flowchart LR
subgraph TRIGGER["⏰ TRIGGER"]
C[Cron Job]
S[Systemd Timer]
T[Task Scheduler]
end
subgraph CHECK["🔍 VERIFICA"]
E{Scadenza < 30 giorni?}
end
subgraph RENEW["🔄 RINNOVO"]
R1[Creazione CSR]
R2[Richiesta firma]
R3[Distribuzione]
R4[Ricarica servizio]
end
subgraph NOTIFY["📧 NOTIFICA"]
N1[E-Mail]
N2[Slack/Teams]
N3[Ticket]
end
C --> E
S --> E
T --> E
E -->|Sì| R1 --> R2 --> R3 --> R4
E -->|No| N1
R4 --> N1
R4 --> N2
R4 --> N3
style E fill:#fff3e0
style R3 fill:#e8f5e9
Linux: Cron + Bash
Script di rinnovo
#!/bin/bash # /usr/local/bin/cert-renewal.sh # Rinnovo automatico certificati set -euo pipefail # Configurazione CERT_DIR="/etc/ssl/certs" KEY_DIR="/etc/ssl/private" CA_SERVER="https://ca.internal.example.com" RENEWAL_DAYS=30 LOG_FILE="/var/log/cert-renewal.log" MAIL_TO="pki-team@example.com" log() { echo "$(date -Iseconds) $1" >> "$LOG_FILE" } check_and_renew() { local cert_file="$1" local key_file="$2" local service="$3" # Verifica scadenza if openssl x509 -checkend $((RENEWAL_DAYS * 86400)) -noout -in "$cert_file" 2>/dev/null; then log "INFO: $cert_file ancora valido" return 0 fi log "WARN: $cert_file in scadenza, rinnovo..." # Estrazione subject dal vecchio certificato local subject=$(openssl x509 -in "$cert_file" -subject -noout | sed 's/subject=//') local san=$(openssl x509 -in "$cert_file" -text -noout | grep -A1 "Subject Alternative" | tail -1 | tr -d ' ') # Creazione nuovo CSR local csr_file=$(mktemp) openssl req -new -key "$key_file" -out "$csr_file" -subj "$subject" # Invio CSR alla CA (Esempio: REST API) local new_cert=$(curl -s -X POST "$CA_SERVER/api/sign" \ -H "Content-Type: application/x-pem-file" \ -H "Authorization: Bearer $(cat /etc/ssl/ca-token)" \ --data-binary @"$csr_file") if [ -z "$new_cert" ]; then log "ERROR: Rinnovo fallito per $cert_file" echo "Rinnovo certificato fallito: $cert_file" | mail -s "CERT RENEWAL FAILED" "$MAIL_TO" rm "$csr_file" return 1 fi # Backup e sostituzione cp "$cert_file" "${cert_file}.bak.$(date +%Y%m%d)" echo "$new_cert" > "$cert_file" # Ricarica servizio if [ -n "$service" ]; then systemctl reload "$service" || systemctl restart "$service" log "INFO: Servizio $service ricaricato" fi rm "$csr_file" log "INFO: $cert_file rinnovato con successo" # Notifica echo "Certificato rinnovato: $cert_file" | mail -s "CERT RENEWED" "$MAIL_TO" } # Logica principale log "=== Avvio verifica rinnovo ===" # Definizione certificati: cert_file:key_file:service CERTS=( "/etc/ssl/certs/webserver.pem:/etc/ssl/private/webserver.key:nginx" "/etc/ssl/certs/api.pem:/etc/ssl/private/api.key:api-service" "/etc/ssl/certs/mail.pem:/etc/ssl/private/mail.key:postfix" ) for cert_entry in "${CERTS[@]}"; do IFS=':' read -r cert_file key_file service <<< "$cert_entry" check_and_renew "$cert_file" "$key_file" "$service" || true done log "=== Verifica rinnovo terminata ==="
chmod +x /usr/local/bin/cert-renewal.sh
Cron-Job
# /etc/cron.d/cert-renewal # Giornalmente alle 03:00 0 3 * * * root /usr/local/bin/cert-renewal.sh
Linux: Systemd Timer
# /etc/systemd/system/cert-renewal.service [Unit] Description=Certificate Renewal Service After=network.target [Service] Type=oneshot ExecStart=/usr/local/bin/cert-renewal.sh StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
# /etc/systemd/system/cert-renewal.timer [Unit] Description=Daily Certificate Renewal Check [Timer] OnCalendar=*-*-* 03:00:00 RandomizedDelaySec=1h Persistent=true [Install] WantedBy=timers.target
# Attivazione timer systemctl daemon-reload systemctl enable --now cert-renewal.timer # Verifica stato systemctl list-timers cert-renewal.timer journalctl -u cert-renewal.service
Windows: Task Scheduler + PowerShell
# %SCRIPTS_PATH%\Cert-Renewal.ps1 param( [int]$RenewalDays = 30, [string]$LogPath = "C:\Logs\cert-renewal.log", [string]$SmtpServer = "smtp.example.com", [string]$MailTo = "pki-team@example.com" ) function Write-Log { param([string]$Message) $timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ss" "$timestamp $Message" | Out-File -Append -FilePath $LogPath } function Test-CertificateExpiry { param( [string]$CertPath, [int]$DaysThreshold ) $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath) $expiryDate = $cert.NotAfter $daysUntilExpiry = ($expiryDate - (Get-Date)).Days return @{ DaysRemaining = $daysUntilExpiry ExpiryDate = $expiryDate NeedsRenewal = $daysUntilExpiry -lt $DaysThreshold Subject = $cert.Subject } } function Invoke-CertificateRenewal { param( [string]$CertPath, [string]$KeyPath, [string]$ServiceName ) Write-Log "INFO: Avvio rinnovo per $CertPath" try { # Creazione CSR dal certificato esistente $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath) # Qui si chiamerebbe l'API della CA # Esempio: REST-Call alla CA interna $body = @{ subject = $cert.Subject san = $cert.Extensions | Where-Object { $_.Oid.Value -eq "2.5.29.17" } } | ConvertTo-Json $newCert = Invoke-RestMethod -Uri "https://ca.internal.example.com/api/renew" ` -Method POST ` -Body $body ` -Headers @{ Authorization = "Bearer $(Get-Content %SCRIPTS_PATH%\ca-token.txt)" } # Creazione backup $backupPath = "$CertPath.bak.$(Get-Date -Format 'yyyyMMdd')" Copy-Item $CertPath $backupPath # Salvataggio nuovo certificato $newCert | Out-File -FilePath $CertPath -Encoding ascii # Riavvio servizio if ($ServiceName) { Restart-Service $ServiceName -Force Write-Log "INFO: Servizio $ServiceName riavviato" } Write-Log "INFO: Rinnovo riuscito per $CertPath" # Notifica e-mail Send-MailMessage -To $MailTo -From "pki@example.com" ` -Subject "Certificato rinnovato: $($cert.Subject)" ` -Body "Il certificato è stato rinnovato con successo." ` -SmtpServer $SmtpServer return $true } catch { Write-Log "ERROR: Rinnovo fallito - $_" Send-MailMessage -To $MailTo -From "pki@example.com" ` -Subject "ERRORE: Rinnovo certificato" ` -Body "Errore durante il rinnovo di $CertPath : $_" ` -SmtpServer $SmtpServer return $false } } # Logica principale Write-Log "=== Avvio verifica rinnovo ===" $certificates = @( @{ Cert = "C:\certs\webserver.pem"; Key = "C:\certs\webserver.key"; Service = "W3SVC" }, @{ Cert = "C:\certs\api.pem"; Key = "C:\certs\api.key"; Service = "APIService" } ) foreach ($certConfig in $certificates) { $status = Test-CertificateExpiry -CertPath $certConfig.Cert -DaysThreshold $RenewalDays Write-Log "INFO: $($certConfig.Cert) - $($status.DaysRemaining) giorni rimanenti" if ($status.NeedsRenewal) { Invoke-CertificateRenewal ` -CertPath $certConfig.Cert ` -KeyPath $certConfig.Key ` -ServiceName $certConfig.Service } } Write-Log "=== Verifica rinnovo terminata ==="
Configurazione Task Scheduler:
# Creazione task $action = New-ScheduledTaskAction -Execute "PowerShell.exe" ` -Argument "-ExecutionPolicy Bypass -File %SCRIPTS_PATH%\Cert-Renewal.ps1" $trigger = New-ScheduledTaskTrigger -Daily -At "03:00" $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd Register-ScheduledTask -TaskName "Certificate Renewal" ` -Action $action ` -Trigger $trigger ` -Principal $principal ` -Settings $settings
Automazione Ansible
# cert-renewal.yml --- - name: Certificate Renewal hosts: all become: yes vars: renewal_days: 30 ca_server: "https://ca.internal.example.com" tasks: - name: Check certificate expiry community.crypto.x509_certificate_info: path: "{{ item.cert }}" register: cert_info loop: "{{ certificates }}" - name: Create CSR for expiring certificates community.crypto.openssl_csr: path: "/tmp/{{ item.item.name }}.csr" privatekey_path: "{{ item.item.key }}" common_name: "{{ item.subject.commonName }}" subject_alt_name: "{{ item.subject_alt_name | default(omit) }}" when: (item.not_after | to_datetime('%Y%m%d%H%M%SZ') - ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')).days < renewal_days loop: "{{ cert_info.results }}" - name: Request new certificate from CA uri: url: "{{ ca_server }}/api/sign" method: POST body_format: raw body: "{{ lookup('file', '/tmp/' + item.item.name + '.csr') }}" headers: Authorization: "Bearer {{ ca_token }}" return_content: yes register: new_cert when: (item.not_after | to_datetime('%Y%m%d%H%M%SZ') - ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')).days < renewal_days loop: "{{ cert_info.results }}" - name: Deploy new certificate copy: content: "{{ item.content }}" dest: "{{ item.item.item.cert }}" backup: yes notify: Reload services when: item.content is defined loop: "{{ new_cert.results }}" handlers: - name: Reload services service: name: "{{ item.item.item.service }}" state: reloaded loop: "{{ new_cert.results }}" when: item.changed
# Inventory (group_vars/all.yml) certificates: - name: webserver cert: /etc/ssl/certs/webserver.pem key: /etc/ssl/private/webserver.key service: nginx - name: api cert: /etc/ssl/certs/api.pem key: /etc/ssl/private/api.key service: api-service
Integrazione monitoraggio
# Prometheus Node Exporter Textfile # /var/lib/node_exporter/textfile_collector/cert_expiry.prom for cert in /etc/ssl/certs/*.pem; do expiry=$(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2) if [ -n "$expiry" ]; then expiry_epoch=$(date -d "$expiry" +%s) name=$(basename "$cert" .pem) echo "cert_expiry_seconds{cert=\"$name\"} $expiry_epoch" fi done
Checklist
| # | Punto di verifica | ✓ |
| — | ——————- | — |
| 1 | Script di rinnovo creato e testato | ☐ |
| 2 | Cron/Timer/Task configurato | ☐ |
| 3 | Autenticazione CA configurata | ☐ |
| 4 | Backup prima del rinnovo | ☐ |
| 5 | Ricarica servizio funzionante | ☐ |
| 6 | Notifica in caso di errore | ☐ |
| 7 | Logging attivato | ☐ |
Documentazione correlata
- Rinnovo manuale – Per casi speciali
- Integrazione ACME – Per certificati pubblici
- Monitoraggio scadenze – Sorveglianza
« ← Kubernetes Cert-Manager | → Scenari per operatori »
Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional
Zuletzt geändert: il 30/01/2026 alle 01:24