fix(vm201): roa-qr cert auto-renew + add to SSL monitor
Cauza ERR_CERT_DATE_INVALID pe roa-qr.romfast.ro: renewal-ul win-acme avea Installation plugin "None" in loc de IIS -> certul se reinnoia in store dar binding-ul SNI ramanea pe certul vechi (expirat 31 mai). - monitor-ssl-certificates.sh: adaugat roa-qr.romfast.ro (Site ID 5); normalizat CRLF->LF (CRLF dadea exit 127 la exec pe Linux) - docs: box incident 2026-06-25 cu cauza-radacina + diagnostic per renewal Fix aplicat pe VM 201: plugin install None->IIS in renewal.json + force renew (cert nou valid pana 23 sep 2026, binding auto-actualizat). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -206,6 +206,30 @@ iisreset
|
|||||||
Win-acme poate reînnoi certificatele, dar IIS uneori nu aplică noile certificate pe binding-uri.
|
Win-acme poate reînnoi certificatele, dar IIS uneori nu aplică noile certificate pe binding-uri.
|
||||||
Acest lucru cauzează servirea certificatelor expirate chiar dacă cele noi sunt în Certificate Store.
|
Acest lucru cauzează servirea certificatelor expirate chiar dacă cele noi sunt în Certificate Store.
|
||||||
|
|
||||||
|
> **Incident 2026-06-25 — `roa-qr.romfast.ro` servea cert expirat (31 mai).**
|
||||||
|
> Cauza-rădăcină: renewal-ul `roa-qr` fusese creat cu **Installation plugin = `None`**
|
||||||
|
> (`aecc502c-5f75-43d2-b578-f95d50c79ea1`) în loc de **IIS**
|
||||||
|
> (`ea6a5be3-f8de-4d27-a6bd-750b619b2ee2`). Win-acme reînnoia certul și-l punea în
|
||||||
|
> store (27 apr, 21 iun), dar **nu reactualiza niciodată binding-ul SNI** → binding-ul
|
||||||
|
> a rămas blocat pe certul din 2 mar, care a expirat pe 31 mai. (`wacs --list` arăta
|
||||||
|
> `roa-qr ... 1 error`.) Site-urile 1–4 erau OK pentru că au plugin-ul IIS.
|
||||||
|
>
|
||||||
|
> **Fix aplicat:** în `…\Renewals\<id>.renewal.json` (roa-qr =
|
||||||
|
> `kfRYWLrEAkSk_-XRqoobEQ`) am schimbat GUID-ul din `InstallationPluginOptions` pe
|
||||||
|
> cel de IIS, apoi `wacs.exe --renew --id <id> --force` → certul nou s-a legat
|
||||||
|
> **automat** pe binding (dovada că auto-renew-ul viitor funcționează). Backup:
|
||||||
|
> `<id>.renewal.json.bak-20260625`.
|
||||||
|
>
|
||||||
|
> **Diagnostic (ce plugin de install are fiecare renewal):**
|
||||||
|
> ```powershell
|
||||||
|
> $dir = "C:\ProgramData\win-acme\acme-v02.api.letsencrypt.org"
|
||||||
|
> Get-ChildItem $dir -Filter "*.renewal.json" | ForEach-Object {
|
||||||
|
> $j = Get-Content $_.FullName -Raw | ConvertFrom-Json
|
||||||
|
> "{0,-40} install={1}" -f $j.LastFriendlyName, $j.InstallationPluginOptions[0].Plugin
|
||||||
|
> }
|
||||||
|
> # ea6a5be3-... = IIS (corect) ; aecc502c-... = None (NU re-leagă binding-ul)
|
||||||
|
> ```
|
||||||
|
|
||||||
### Soluția: Scripturi de Monitorizare
|
### Soluția: Scripturi de Monitorizare
|
||||||
|
|
||||||
#### 1. Script PowerShell pe VM 201
|
#### 1. Script PowerShell pe VM 201
|
||||||
|
|||||||
@@ -1,172 +1,173 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Script: monitor-ssl-certificates.sh
|
# Script: monitor-ssl-certificates.sh
|
||||||
# Locatie: /opt/scripts/monitor-ssl-certificates.sh (pe pvemini)
|
# Locatie: /opt/scripts/monitor-ssl-certificates.sh (pe pvemini)
|
||||||
# Scop: Verifica certificatele SSL extern si alerteaza/forteaza reinstalare
|
# Scop: Verifica certificatele SSL extern si alerteaza/forteaza reinstalare
|
||||||
# Rulare: Cron zilnic sau la cerere
|
# Rulare: Cron zilnic sau la cerere
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# Configurare
|
# Configurare
|
||||||
DAYS_WARNING=14
|
DAYS_WARNING=14
|
||||||
DAYS_CRITICAL=7
|
DAYS_CRITICAL=7
|
||||||
LOG_FILE="/var/log/ssl-monitor.log"
|
LOG_FILE="/var/log/ssl-monitor.log"
|
||||||
EMAIL_TO="root" # Proxmox trimite la adresa configurata
|
EMAIL_TO="root" # Proxmox trimite la adresa configurata
|
||||||
|
|
||||||
# Domenii de verificat
|
# Domenii de verificat
|
||||||
# NOTA: efactura.roa.romfast.ro este un SENTINEL pentru certificatul wildcard
|
# NOTA: efactura.roa.romfast.ro este un SENTINEL pentru certificatul wildcard
|
||||||
# *.roa.romfast.ro. Wildcardul nu poate fi testat direct, asa ca verificam
|
# *.roa.romfast.ro. Wildcardul nu poate fi testat direct, asa ca verificam
|
||||||
# un subdomeniu real acoperit de el. Site ID "WILDCARD" => doar ALERTA,
|
# un subdomeniu real acoperit de el. Site ID "WILDCARD" => doar ALERTA,
|
||||||
# fara auto-renew (wildcardul e DNS-01, reinnoit de cpanel-acme-dns.ps1 pe
|
# fara auto-renew (wildcardul e DNS-01, reinnoit de cpanel-acme-dns.ps1 pe
|
||||||
# VM 201; auto-renew prin guest-exec nu mai functioneaza - exec dezactivat).
|
# VM 201; auto-renew prin guest-exec nu mai functioneaza - exec dezactivat).
|
||||||
# Context: incident expirare wildcard 2026-05-31 (vezi README VM 201).
|
# Context: incident expirare wildcard 2026-05-31 (vezi README VM 201).
|
||||||
DOMAINS=(
|
DOMAINS=(
|
||||||
"roa.romfast.ro"
|
"roa.romfast.ro"
|
||||||
"dokploy.romfast.ro"
|
"dokploy.romfast.ro"
|
||||||
"gitea.romfast.ro"
|
"gitea.romfast.ro"
|
||||||
"roa2web.romfast.ro"
|
"roa2web.romfast.ro"
|
||||||
"efactura.roa.romfast.ro"
|
"roa-qr.romfast.ro"
|
||||||
)
|
"efactura.roa.romfast.ro"
|
||||||
|
)
|
||||||
# Site IDs pentru fiecare domeniu (in aceeasi ordine)
|
|
||||||
SITE_IDS=(1 2 3 4 "WILDCARD")
|
# Site IDs pentru fiecare domeniu (in aceeasi ordine)
|
||||||
|
SITE_IDS=(1 2 3 4 5 "WILDCARD")
|
||||||
log() {
|
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
log() {
|
||||||
}
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
check_certificate() {
|
|
||||||
local domain=$1
|
check_certificate() {
|
||||||
local expiry_date expiry_epoch now_epoch days_left
|
local domain=$1
|
||||||
|
local expiry_date expiry_epoch now_epoch days_left
|
||||||
# Obtine data expirare
|
|
||||||
expiry_date=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | \
|
# Obtine data expirare
|
||||||
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
expiry_date=$(echo | openssl s_client -servername "$domain" -connect "$domain:443" 2>/dev/null | \
|
||||||
|
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
|
||||||
if [[ -z "$expiry_date" ]]; then
|
|
||||||
echo "-1"
|
if [[ -z "$expiry_date" ]]; then
|
||||||
return
|
echo "-1"
|
||||||
fi
|
return
|
||||||
|
fi
|
||||||
# Calculeaza zilele ramase
|
|
||||||
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
|
# Calculeaza zilele ramase
|
||||||
now_epoch=$(date +%s)
|
expiry_epoch=$(date -d "$expiry_date" +%s 2>/dev/null)
|
||||||
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
|
now_epoch=$(date +%s)
|
||||||
|
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
|
||||||
echo "$days_left"
|
|
||||||
}
|
echo "$days_left"
|
||||||
|
}
|
||||||
force_renew_certificate() {
|
|
||||||
local site_id=$1
|
force_renew_certificate() {
|
||||||
local domain=$2
|
local site_id=$1
|
||||||
|
local domain=$2
|
||||||
# Wildcard (*.roa) = DNS-01, reinnoit automat de cpanel-acme-dns.ps1 pe VM 201.
|
|
||||||
# Nu incercam auto-renew aici (nu e HTTP-01/siteid si guest-exec e dezactivat) -
|
# Wildcard (*.roa) = DNS-01, reinnoit automat de cpanel-acme-dns.ps1 pe VM 201.
|
||||||
# doar semnalam ca a expirat ca sa intervina cineva pe VM 201.
|
# Nu incercam auto-renew aici (nu e HTTP-01/siteid si guest-exec e dezactivat) -
|
||||||
if ! [[ "$site_id" =~ ^[0-9]+$ ]]; then
|
# doar semnalam ca a expirat ca sa intervina cineva pe VM 201.
|
||||||
log "ALERTA: $domain (wildcard *.roa) necesita interventie manuala pe VM 201 - verifica renewal-ul win-acme cu validare cPanel (cpanel-acme-dns.ps1)"
|
if ! [[ "$site_id" =~ ^[0-9]+$ ]]; then
|
||||||
return 1
|
log "ALERTA: $domain (wildcard *.roa) necesita interventie manuala pe VM 201 - verifica renewal-ul win-acme cu validare cPanel (cpanel-acme-dns.ps1)"
|
||||||
fi
|
return 1
|
||||||
|
fi
|
||||||
log "Fortez reinstalare certificat pentru $domain (Site ID: $site_id)..."
|
|
||||||
|
log "Fortez reinstalare certificat pentru $domain (Site ID: $site_id)..."
|
||||||
# Executa pe VM 201 prin Proxmox guest agent
|
|
||||||
result=$(qm guest exec 201 -- powershell -Command \
|
# Executa pe VM 201 prin Proxmox guest agent
|
||||||
"cd C:\\Tools\\win-acme; .\\wacs.exe --target iis --siteid $site_id --installation iis --force" 2>&1)
|
result=$(qm guest exec 201 -- powershell -Command \
|
||||||
|
"cd C:\\Tools\\win-acme; .\\wacs.exe --target iis --siteid $site_id --installation iis --force" 2>&1)
|
||||||
if echo "$result" | grep -q '"exitcode" : 0'; then
|
|
||||||
log "SUCCES: Certificat reinstalat pentru $domain"
|
if echo "$result" | grep -q '"exitcode" : 0'; then
|
||||||
return 0
|
log "SUCCES: Certificat reinstalat pentru $domain"
|
||||||
else
|
return 0
|
||||||
log "EROARE: Reinstalare esuata pentru $domain"
|
else
|
||||||
log "$result"
|
log "EROARE: Reinstalare esuata pentru $domain"
|
||||||
return 1
|
log "$result"
|
||||||
fi
|
return 1
|
||||||
}
|
fi
|
||||||
|
}
|
||||||
restart_iis() {
|
|
||||||
log "Restart IIS..."
|
restart_iis() {
|
||||||
qm guest exec 201 -- cmd /c "iisreset" >/dev/null 2>&1
|
log "Restart IIS..."
|
||||||
log "IIS restartat"
|
qm guest exec 201 -- cmd /c "iisreset" >/dev/null 2>&1
|
||||||
}
|
log "IIS restartat"
|
||||||
|
}
|
||||||
send_alert() {
|
|
||||||
local subject=$1
|
send_alert() {
|
||||||
local body=$2
|
local subject=$1
|
||||||
|
local body=$2
|
||||||
# Foloseste sistemul de notificari Proxmox
|
|
||||||
if command -v pvesh &>/dev/null; then
|
# Foloseste sistemul de notificari Proxmox
|
||||||
echo "$body" | mail -s "$subject" "$EMAIL_TO" 2>/dev/null || true
|
if command -v pvesh &>/dev/null; then
|
||||||
fi
|
echo "$body" | mail -s "$subject" "$EMAIL_TO" 2>/dev/null || true
|
||||||
|
fi
|
||||||
log "ALERT: $subject"
|
|
||||||
}
|
log "ALERT: $subject"
|
||||||
|
}
|
||||||
# Main
|
|
||||||
log "========== Verificare certificate SSL =========="
|
# Main
|
||||||
|
log "========== Verificare certificate SSL =========="
|
||||||
warnings=()
|
|
||||||
criticals=()
|
warnings=()
|
||||||
renewed=0
|
criticals=()
|
||||||
|
renewed=0
|
||||||
for i in "${!DOMAINS[@]}"; do
|
|
||||||
domain="${DOMAINS[$i]}"
|
for i in "${!DOMAINS[@]}"; do
|
||||||
site_id="${SITE_IDS[$i]}"
|
domain="${DOMAINS[$i]}"
|
||||||
|
site_id="${SITE_IDS[$i]}"
|
||||||
days_left=$(check_certificate "$domain")
|
|
||||||
|
days_left=$(check_certificate "$domain")
|
||||||
if [[ "$days_left" == "-1" ]]; then
|
|
||||||
log "EROARE: Nu pot verifica $domain"
|
if [[ "$days_left" == "-1" ]]; then
|
||||||
criticals+=("$domain: Nu pot obtine certificatul")
|
log "EROARE: Nu pot verifica $domain"
|
||||||
continue
|
criticals+=("$domain: Nu pot obtine certificatul")
|
||||||
fi
|
continue
|
||||||
|
fi
|
||||||
log "$domain: $days_left zile ramase"
|
|
||||||
|
log "$domain: $days_left zile ramase"
|
||||||
if [[ $days_left -lt 0 ]]; then
|
|
||||||
criticals+=("$domain: EXPIRAT!")
|
if [[ $days_left -lt 0 ]]; then
|
||||||
# Forteaza reinstalare
|
criticals+=("$domain: EXPIRAT!")
|
||||||
if force_renew_certificate "$site_id" "$domain"; then
|
# Forteaza reinstalare
|
||||||
((renewed++))
|
if force_renew_certificate "$site_id" "$domain"; then
|
||||||
fi
|
((renewed++))
|
||||||
elif [[ $days_left -lt $DAYS_CRITICAL ]]; then
|
fi
|
||||||
criticals+=("$domain: expira in $days_left zile")
|
elif [[ $days_left -lt $DAYS_CRITICAL ]]; then
|
||||||
# Forteaza reinstalare
|
criticals+=("$domain: expira in $days_left zile")
|
||||||
if force_renew_certificate "$site_id" "$domain"; then
|
# Forteaza reinstalare
|
||||||
((renewed++))
|
if force_renew_certificate "$site_id" "$domain"; then
|
||||||
fi
|
((renewed++))
|
||||||
elif [[ $days_left -lt $DAYS_WARNING ]]; then
|
fi
|
||||||
warnings+=("$domain: expira in $days_left zile")
|
elif [[ $days_left -lt $DAYS_WARNING ]]; then
|
||||||
# Forteaza reinstalare preventiv
|
warnings+=("$domain: expira in $days_left zile")
|
||||||
if force_renew_certificate "$site_id" "$domain"; then
|
# Forteaza reinstalare preventiv
|
||||||
((renewed++))
|
if force_renew_certificate "$site_id" "$domain"; then
|
||||||
fi
|
((renewed++))
|
||||||
fi
|
fi
|
||||||
done
|
fi
|
||||||
|
done
|
||||||
# Restart IIS daca am reinoit
|
|
||||||
if [[ $renewed -gt 0 ]]; then
|
# Restart IIS daca am reinoit
|
||||||
restart_iis
|
if [[ $renewed -gt 0 ]]; then
|
||||||
fi
|
restart_iis
|
||||||
|
fi
|
||||||
# Trimite alerte
|
|
||||||
if [[ ${#criticals[@]} -gt 0 ]]; then
|
# Trimite alerte
|
||||||
body="Certificate SSL CRITICE:\n\n"
|
if [[ ${#criticals[@]} -gt 0 ]]; then
|
||||||
for msg in "${criticals[@]}"; do
|
body="Certificate SSL CRITICE:\n\n"
|
||||||
body+="- $msg\n"
|
for msg in "${criticals[@]}"; do
|
||||||
done
|
body+="- $msg\n"
|
||||||
body+="\nActiuni intreprinse: $renewed certificate reinstalate"
|
done
|
||||||
send_alert "[CRITICAL] Certificate SSL expirate/aproape de expirare" "$body"
|
body+="\nActiuni intreprinse: $renewed certificate reinstalate"
|
||||||
fi
|
send_alert "[CRITICAL] Certificate SSL expirate/aproape de expirare" "$body"
|
||||||
|
fi
|
||||||
if [[ ${#warnings[@]} -gt 0 && ${#criticals[@]} -eq 0 ]]; then
|
|
||||||
body="Certificate SSL WARNING:\n\n"
|
if [[ ${#warnings[@]} -gt 0 && ${#criticals[@]} -eq 0 ]]; then
|
||||||
for msg in "${warnings[@]}"; do
|
body="Certificate SSL WARNING:\n\n"
|
||||||
body+="- $msg\n"
|
for msg in "${warnings[@]}"; do
|
||||||
done
|
body+="- $msg\n"
|
||||||
body+="\nActiuni intreprinse: $renewed certificate reinstalate"
|
done
|
||||||
send_alert "[WARNING] Certificate SSL aproape de expirare" "$body"
|
body+="\nActiuni intreprinse: $renewed certificate reinstalate"
|
||||||
fi
|
send_alert "[WARNING] Certificate SSL aproape de expirare" "$body"
|
||||||
|
fi
|
||||||
log "========== Sumar: $renewed reinstalate, ${#warnings[@]} warnings, ${#criticals[@]} critice =========="
|
|
||||||
|
log "========== Sumar: $renewed reinstalate, ${#warnings[@]} warnings, ${#criticals[@]} critice =========="
|
||||||
exit 0
|
|
||||||
|
exit 0
|
||||||
|
|||||||
Reference in New Issue
Block a user