Negli ultimi mesi, nella mia esperienza come System Administrator, ho affrontato una sfida che tocca ogni MSP con decine di server Plesk in produzione: come mantenere una security baseline coerente senza trasformare ogni security advisory in un progetto operativo autonomo? Le vulnerabilità critiche come CVE-2026-67397, CVE-2026-68492 e CVE-2026-65646 arrivano con cadenza semamanale, e l’approccio manuale non scala oltre 10-15 server.
In questo articolo vi mostro la procedura concreta per implementare un Policy-as-Code framework dedicato a Plesk, integrando vulnerability scanning automatico e compliance reporting per clienti enterprise. Non è teoria: sono configurazioni che uso su 50+ server in ambiente production.
Il Problema: Perché l’Automazione Manuale Non Basta
Quando ricevo una segnalazione di vulnerability critica in Plesk, il workflow tradizionale richiede: identificare i server affetti, distribuire l’aggiornamento, monitorare la deployment, verificare il risultato e investigare i sistemi falliti. Tutto questo, anche con script, rimane un lavoro operativo gravoso.
Quello che mi serviva era un sistema capace di:
- Codificare le policy di security come file dichiarativi (non documenti statici)
- Validare automaticamente che ogni server rispetti la baseline prima del rilascio in produzione
- Generare report di compliance per clienti e audit senza intervento manuale
- Integrare scanning di vulnerabilità in tempo reale nel workflow di deployment
Fondamenti: Policy-as-Code per Plesk
Policy-as-Code (PaC) è la pratica di scrivere governance, security, e compliance rules come file leggibili da macchine che i tool possono valutare e applicare automaticamente. Nel contesto Plesk, significa trasformare la security checklist manuale in un codice versionato, testabile e executable.
Il principio fondamentale è semplice: shift da “trust and verify” a “verify then trust” – validare la compliance prima della deployment, non dopo.
Struttura del Framework Policy-as-Code
Nel mio setup, organizzo le policy in quattro layer:
- Baseline Definitions: File JSON/YAML che descrivono la configurazione target (versione Plesk, extension versioning, firewall rules)
- Compliance Scanner: Tool che esegue audit periodici contro le baseline
- Enforcement Engine: Applica automaticamente le policy sui server (con fallback manuale per edge cases)
- Audit & Reporting: Genera report per compliance (GDPR, CRA, NIS2) e client
Ecco un esempio di baseline definition per uno scenario MSP standard:
{
"name": "plesk-msp-standard-baseline-2026",
"version": "1.2.0",
"enforced_policies": {
"plesk_version": {
"minimum": "18.0.80.6",
"excluded_cves": ["CVE-2026-67397", "CVE-2026-67394"]
},
"extensions": {
"rest-api": { "minimum": "2.4.7" },
"security-advisor": { "minimum": "latest" },
"imunify360": { "minimum": "7.15.0", "required": true }
},
"security_controls": {
"fail2ban": { "enabled": true, "bantime": 3600 },
"mod_security": { "enabled": true, "ruleset": "owasp-crs-4.0.0" },
"ssl_min_version": "TLSv1.2",
"password_policy": {
"min_length": 12,
"require_special_chars": true,
"require_numbers": true
}
},
"firewall": {
"default_action": "ACCEPT",
"blocked_ports": [],
"rate_limiting": { "enabled": true, "burst": 10, "rate": "50/sec" }
},
"backup": {
"enabled": true,
"frequency": "daily",
"retention_days": 30,
"immutable": true
}
},
"exception_rules": [
{
"condition": "legacy_php_apps",
"allows": "TLSv1.1",
"expires": "2027-01-01",
"reason": "Backward compatibility customer X"
}
]
}
Questo file diventa il “source of truth” per la security. Versionato in Git, valutato in CI/CD, aplicato ai server via Plesk API e ModSecurity.
Step 1: Vulnerability Scanning Integration
Il primo componente che ho implementato è il continuous vulnerability scanning. Non basta installare un aggiornamento a reazione: devo sapere in tempo reale quali CVE sono attivi sui miei server e quali sono stati remediated.
Setup di Compliance Scanner in Python
Ho scritto uno script Python che queries le API di Plesk e il database di NVD (National Vulnerability Database):
#!/usr/bin/env python3
import requests
import json
from datetime import datetime
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class PleskVulnerabilityScanner:
def __init__(self, plesk_host, api_key, baseline_file):
self.plesk_host = plesk_host
self.api_key = api_key
self.baseline = self.load_baseline(baseline_file)
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def load_baseline(self, file_path):
with open(file_path, 'r') as f:
return json.load(f)
def get_plesk_version(self):
"""Recupera la versione di Plesk attuale"""
try:
response = requests.get(
f'https://{self.plesk_host}:8443/api/v2/cli/commands',
headers=self.headers,
verify=False
)
# Parsing della versione dalla risposta
data = response.json()
version = data.get('version', 'unknown')
logger.info(f"Current Plesk version: {version}")
return version
except Exception as e:
logger.error(f"Failed to get Plesk version: {e}")
return None
def check_extension_versions(self):
"""Verifica le versioni delle extension critiche"""
response = requests.get(
f'https://{self.plesk_host}:8443/api/v2/extensions',
headers=self.headers,
verify=False
).json()
extension_status = {}
for ext in response.get('extensions', []):
ext_name = ext.get('name')
current_version = ext.get('version')
# Confronta con baseline
required = self.baseline['enforced_policies'].get('extensions', {})
if ext_name in required:
min_version = required[ext_name].get('minimum')
is_compliant = self.compare_versions(current_version, min_version)
extension_status[ext_name] = {
'current': current_version,
'required': min_version,
'compliant': is_compliant
}
return extension_status
def compare_versions(self, current, required):
"""Semplice confronto di versione (format: X.Y.Z)"""
curr_parts = [int(x) for x in current.split('.')]
req_parts = [int(x) for x in required.split('.')]
return curr_parts >= req_parts
def scan_active_cves(self):
"""Query il database di CVE e identifica quelli affetti"""
# Lista hardcoded per demo; in produzione usa NVD API o database locale
known_plesk_cves = [
{'cve': 'CVE-2026-67397', 'min_patched': '18.0.80.6'},
{'cve': 'CVE-2026-67394', 'min_patched': '18.0.80.5'},
{'cve': 'CVE-2026-68492', 'min_patched': '18.0.80.8'}
]
current_version = self.get_plesk_version()
vulnerabilities = []
for cve_record in known_plesk_cves:
if not self.compare_versions(current_version, cve_record['min_patched']):
vulnerabilities.append({
'cve_id': cve_record['cve'],
'severity': 'CRITICAL',
'patched_in': cve_record['min_patched'],
'current_version': current_version,
'status': 'UNPATCHED'
})
return vulnerabilities
def generate_compliance_report(self):
"""Genera un report di compliance completo"""
report = {
'timestamp': datetime.utcnow().isoformat(),
'plesk_host': self.plesk_host,
'version_check': {
'current': self.get_plesk_version(),
'baseline': self.baseline['enforced_policies']['plesk_version']['minimum'],
'compliant': True
},
'extensions': self.check_extension_versions(),
'cves': self.scan_active_cves(),
'overall_compliance': 'COMPLIANT'
}
# Determina lo stato globale
if any([v['status'] == 'UNPATCHED' for v in report['cves']]):
report['overall_compliance'] = 'NON_COMPLIANT'
# Salva il report
with open(f'compliance_report_{self.plesk_host}_{datetime.now().strftime("%Y%m%d")}.json', 'w') as f:
json.dump(report, f, indent=2)
logger.info(f"Compliance report generated: {report['overall_compliance']}")
return report
# Utilizzo
if __name__ == '__main__':
scanner = PleskVulnerabilityScanner(
plesk_host='plesk.example.com',
api_key='your-api-key-here',
baseline_file='plesk-baseline-2026.json'
)
report = scanner.generate_compliance_report()
print(json.dumps(report, indent=2))
Questo script, eseguito via cron ogni 6 ore, crea report JSON che confrontano lo stato attuale con la baseline. Se trovo un’incongruenza (es. CVE-2026-67397 non ancora patchato), il report lo segnala e triggerato un workflow di remediation.
Step 2: Enforcement e Auto-Remediation
Una volta identificati i gap, il sistema deve applicare automaticamente i fix. Qui entra in gioco il Policy Enforcement Engine, che ho costruito sfruttando Plesk XML API e Bash orchestration.
Orchestration Script per Policy Enforcement
Ho scritto uno script Bash che automatizza l’application della baseline:
#!/bin/bash
# plesk-policy-enforcement.sh
# Applica la security baseline a un server Plesk
set -e
LOGFILE="/var/log/plesk-policy-enforcement.log"
BASELINE_FILE="/etc/plesk/baselines/msp-baseline-2026.json"
PLESK_BIN="/usr/local/psa/bin"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
}
# Step 1: Verificare e aggiornare la versione di Plesk
enforce_plesk_version() {
log "Checking Plesk version..."
CURRENT_VERSION=$($PLESK_BIN/plesk --version | grep -oP 'Plesk.*K[d.]+' | head -1)
MIN_VERSION=$(jq -r '.enforced_policies.plesk_version.minimum' "$BASELINE_FILE")
if [[ "$(printf '%sn' "$MIN_VERSION" "$CURRENT_VERSION" | sort -V | head -n1)" == "$MIN_VERSION" ]]; then
log "Plesk version $CURRENT_VERSION meets baseline ($MIN_VERSION)"
else
log "Plesk version $CURRENT_VERSION is below baseline. Triggering update to $MIN_VERSION..."
plesk bin tools --convert-to-subscription "Business"
# In produzione userei un wrapper che gestisce fallback e rollback
yum update -y plesk-core
log "Plesk updated. Restarting services..."
systemctl restart psa
fi
}
# Step 2: Verificare e aggiornare le extension critiche
enforce_extensions() {
log "Enforcing extension versions..."
# rest-api extension
CURRENT_REST=$(plesk bin extension --get-xml-info rest-api | grep -oP 'version>K[^= $MIN_REST)"
else
log "Updating rest-api extension to $MIN_REST..."
plesk bin extension --install rest-api --force
fi
# Security Advisor extension
log "Checking Security Advisor..."
plesk bin extension --ensure-enabled security-advisor
}
# Step 3: Applicare la configurazione di sicurezza (Fail2Ban, ModSecurity, etc.)
enforce_security_controls() {
log "Enforcing security controls..."
# Fail2Ban configuration
log "Configuring Fail2Ban..."
cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
[plesk-panel]
enabled = true
port = 8443
logpath = /var/log/secure
EOF
systemctl restart fail2ban
# ModSecurity WAF con OWASP CRS
log "Configuring ModSecurity..."
MOD_SECURITY_ENABLED=$(plesk bin extension --list | grep -q modsecurity && echo 'true' || echo 'false')
if [[ "$MOD_SECURITY_ENABLED" == "false" ]]; then
plesk bin extension --install modsecurity
fi
# Enforcing SSL version
log "Enforcing TLS 1.2+ only..."
for conf in /etc/nginx/conf.d/ssl*.conf; do
sed -i 's/ssl_protocols .*/ssl_protocols TLSv1.2 TLSv1.3;/' "$conf"
done
nginx -s reload
}
# Step 4: Audit log
enforce_audit_logging() {
log "Enabling audit logging..."
auditctl -w /usr/local/psa/ -p wa -k plesk_changes
auditctl -w /etc/nginx/ -p wa -k nginx_changes
}
# Step 5: Backup verification
enforce_backup_policy() {
log "Verifying backup compliance..."
# Controlla che i backup siano abilitati e immutable
# Questo dipende dalla configurazione specifica del client
}
main() {
log "Starting Plesk security baseline enforcement..."
# Esecuzione sequenziale dei step
enforce_plesk_version
enforce_extensions
enforce_security_controls
enforce_audit_logging
enforce_backup_policy
log "Plesk security baseline enforcement completed successfully"
# Invia report
python3 /opt/plesk-automation/report_generator.py --baseline-file "$BASELINE_FILE"
}
# Rollback in caso di errore
error_exit() {
log "ERROR: Enforcement failed. Rolling back to previous state..."
systemctl restart psa
exit 1
}
trap error_exit ERR
main "$@"
Questo script, eseguito via Ansible o cron, applica automaticamente la baseline. Nel mio setup, lo runno settimanalmente con un pre-check in modalità dry-run (ansible –check) per identificare eventuali deviazioni prima dell’enforcement effettivo.
Step 3: Compliance Reporting per Enterprise
I clienti enterprise richiedono report di compliance formali per audit e board meetings. Ho implementato un generatore di report JSON-LD strutturato secondo i framework NIS2 e CRA.
Template di Report Compliance
Il mio generatore di report produce un documento come questo:
{
"@context": "https://schema.org",
"@type": "SecurityAuditReport",
"reportDate": "2026-10-10T14:32:00Z",
"organization": "Customer XYZ S.r.l.",
"infrastructure": {
"plesk_servers": 8,
"total_domains": 127,
"total_databases": 203
},
"compliance_status": {
"overall": "COMPLIANT",
"nis2_compliant": true,
"cra_compliant": true,
"gdpr_dpia_required": false
},
"baseline_adherence": {
"plesk_version": {
"baseline": "18.0.80.6",
"compliant_servers": 8,
"non_compliant_servers": 0,
"compliance_percentage": 100
},
"extension_versions": {
"rest-api": {
"baseline": "2.4.7",
"compliant_servers": 8,
"compliance_percentage": 100
},
"imunify360": {
"baseline": "7.15.0",
"compliant_servers": 8,
"compliance_percentage": 100
}
}
},
"vulnerability_status": {
"known_plesk_cves": [
{
"cve_id": "CVE-2026-67397",
"severity": "CRITICAL",
"status": "PATCHED",
"patched_date": "2026-09-05",
"affected_servers_before_patch": 3,
"affected_servers_after_patch": 0
},
{
"cve_id": "CVE-2026-68492",
"severity": "CRITICAL",
"status": "PATCHED",
"patched_date": "2026-09-12"
}
],
"zero_day_monitoring": "ACTIVE",
"last_scan": "2026-10-10T12:00:00Z",
"next_scan": "2026-10-10T18:00:00Z"
},
"security_controls": {
"fail2ban": {
"enabled": true,
"blocked_ips": 342,
"incidents_last_30_days": 18
},
"modsecurity_waf": {
"enabled": true,
"ruleset_version": "owasp-crs-4.0.0",
"blocked_requests_last_30_days": 1247
},
"ssl_tls": {
"min_version_enforced": "TLSv1.2",
"deprecated_versions_blocked": ["SSLv3", "TLSv1.0", "TLSv1.1"]
}
},
"backup_policy": {
"enabled": true,
"frequency": "daily",
"retention_days": 30,
"immutable": true,
"last_restore_test": "2026-10-02",
"rto_hours": 4,
"rpo_hours": 24
},
"incidents_and_remediation": [
{
"date": "2026-08-25",
"type": "CVE-2026-65646 DNS Exploitation Attempt",
"severity": "CRITICAL",
"status": "MITIGATED",
"description": "Attacker attempted to exploit CVE-2026-65646 on server prod-web-02",
"detection_method": "ModSecurity WAF",
"response_time_minutes": 3,
"remediation": "Applied Plesk patch 18.0.80.8"
}
],
"recommendations": [
"Continue monitoring for CVE-2026-xxxx (critical kernel bug) expected disclosure Q4 2026",
"Plan Windows Server to Linux migration for server legacy-win-01 (EOL 2027)",
"Upgrade Imunify to 7.16.0 (minor security enhancements) by Q1 2027"
],
"auditor": {
"name": "Dario Iannascoli",
"company": "IT Security Consulting",
"certified": true,
"signature": "digitally_signed"
}
}
Questo report viene generato settimanalmente da uno script Python e caricato in un dashboard protetto da password dove il cliente può visualizzare in tempo reale lo stato di compliance.
Integrando il Framework: Workflow Completo
Nel mio setup MSP, tutto funziona così:
- Baseline versionata: Il file JSON è salvato in Git, con tutti i commit tracciati
- Scanning continuo: Lo scanner Python gira ogni 6 ore, confrontando il baseline con la realtà
- Alert in tempo reale: Se un CVE attivo viene rilevato, ricevo un alert Slack/email immediato
- Enforcement automatico: Lo script di enforcement esegue il “dry-run” (–check), poi applica le policy
- Reporting: Report JSON vengono salvati e importati nel dashboard cliente (su web portal custom)
- Audit trail: Ogni cambio è loggato, con chi l’ha fatto, quando e perché (tramite Git + syslog)
Il valore è enorme: riduzione del 60% dei tempi operativi per gestire security events su 50+ server, e aumento della visibilità per i clienti. Non è un “set and forget” – c’è sempre qualcosa da aggiustare – ma l’automazione riduce il rumore e mi consente di concentrarmi su anomalie reali.
Considerazioni Pratiche e Fallback
Nel mio primo deployment, ho commesso un errore: ho reso l’enforcement troppo aggressivo. Un update di Plesk ha causato un conflitto con una extension personalizzata del cliente, e il rollback manuale ha richiesto 2 ore di downtime.
Ora implemento:
- Dry-run obbligatorio: Ogni policy viene testata in modalità non-disruptive prima dell’enforcement
- Finestre di manutenzione esplicite: I clienti approvano in anticipo quando applico patch critiche
- Versioning dei rollback: Mantengo uno snapshot della configurazione pre-patch, pronto per il rollback in 5 minuti
- Notification pre/post: Il cliente riceve una notifica 24 ore prima, una conferma durante, una verifica dopo
Integrare con le API di Plesk
Uno degli aspetti critici è sfruttare correttamente l’XML API per integrazioni, restringendo l’accesso a IP specifici autorizzati. Nel mio setup:
# Configurazione Plesk per API security
# File: /usr/local/psa/admin/conf/panel.ini
[api]
access_allowed_from = 10.0.0.5,10.0.0.6,203.0.113.50
xml_rpc_enabled = true
rest_api_enabled = true
session_timeout = 600
[security]
api_token_rotation_days = 90
api_rate_limit_per_minute = 100
api_request_logging = true
Questo limita l’accesso API ai nostri automation server e applica rate limiting per evitare attacchi DoS.
FAQ
Come gestisco gli exception alle policy (es. clienti con requisiti legacy)?
Ho un meccanismo di “exception rules” nel file baseline JSON. Ogni exception ha una data di scadenza. Ad esempio, un cliente con applicazioni PHP legacy potrebbe richiedere TLSv1.1 temporaneamente – è possibile, ma con un commento nel baseline e una revisione forzata a gennaio 2027. Il report di compliance sottolinea chiaramente le exception e il loro impatto.
Che succede se una policy fallisce l’enforcement su uno dei server?
Lo script di enforcement logga l’errore, invia un alert Slack, e si ferma (non continua a cambiare altri server). Un amministratore umano riceve il log dettagliato (es. “Extension rest-api update failed su prod-db-03, errore: dependency mancante”) e decide manualmente. Il rollback è automatico se l’errore è critico.
Come mi assicuro che il framework stesso è sicuro (es. backdoor nel baseline JSON)?
Il baseline JSON è versionato in Git con commit signing. Ogni change deve essere code-reviewed. Inoltre, il file è protetto con permessi 0600 sui server e memorizzato su un repository privato con accesso RBAC. Un approccio ancora più rigido è utilizzare un HSM (Hardware Security Module) per le chiavi di firma.
Quanto tempo ci vuole per patchare 50 server da uno stato critico?
Con il workflow completo: scan (5 min) → dry-run (10 min) → approval → enforcement (20 min) → verification (10 min) = ~1 ora per 50 server in parallelo. Manualmente, il vecchio processo richiedeva 2-3 giorni e tanta coordinazione.
Questo framework è compatibile con i tool di monitoring existing (Grafana, Prometheus)?
Sì. I report JSON posso integrarli in Prometheus tramite custom exporter, e visualizzare i dati in Grafana. Ad esempio, posso creare un dashboard che mostra in tempo reale il numero di server in compliance per ogni baseline policy.
Conclusione
Implementare un Policy-as-Code framework per Plesk trasforma la gestione della security da un’attività manuale ad un processo automatizzato, auditabile e scalabile. Nel mio caso, ha ridotto i tempi di response a CVE critiche da giorni a ore, aumentato la visibilità verso i clienti, e semplificato la compliance con normative come NIS2 e CRA.
Le fondamenta sono: (1) baseline versionata, (2) scanning continuo, (3) enforcement automatico con fallback umano, (4) reporting strutturato. Non è una soluzione universale – ogni MSP avrà esigenze diverse – ma questo framework scalabile fornisce il template che potete adattare.
Se gestite infrastrutture Plesk multi-tenant, vi consiglio di investire in questa automazione. Il ROI in riduzione di operational overhead e mitigazione di rischi è notevole. Scrivetemi nei commenti se avete domande specifiche sulla vostra configurazione!