- AssetScoringService::GRID: aggiunto campo 'desc' a tutte le 35 opzioni dei 6 criteri (significato operativo, additivo) - assets.html: ogni combo mostra il significato dell'opzione selezionata + nuovo box 'Dettaglio punteggio per criterio' (live, anche alla riapertura di una valutazione salvata) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
252 lines
14 KiB
PHP
252 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* NIS2 Agile - Asset Scoring Service
|
|
*
|
|
* Metodologia di scoring rilevanza NIS2 (requisito GV.OC-04).
|
|
* Adattata dai mockup docs/nis2/assets.html + doc-relevant-systems.html.
|
|
*
|
|
* 6 criteri pesati, punteggio 0-100:
|
|
* C1 Criticita Operativa 0-25
|
|
* C2 Impatto Interruzione 0-25
|
|
* C3 Dati Trattati 0-20
|
|
* C4 Dipendenze 0-15
|
|
* C5 Esposizione 0-10
|
|
* C6 Obblighi Normativi 0-5
|
|
*
|
|
* Soglia rilevanza NIS2: score >= 40.
|
|
* Classi: >=80 critico | 60-79 alto | 40-59 medio | 20-39 basso | <20 trascurabile.
|
|
*
|
|
* La logica e' PURA (nessun side effect / DB): si presta a unit test e riuso.
|
|
*/
|
|
class AssetScoringService
|
|
{
|
|
public const RELEVANCE_THRESHOLD = 40;
|
|
|
|
/**
|
|
* Griglia ufficiale: per ogni criterio, la lista di opzioni selezionabili
|
|
* (value => punti) con label per la UI. value e' una chiave stabile usata
|
|
* dal frontend e salvata in relevance_criteria JSON.
|
|
*/
|
|
public const GRID = [
|
|
'c1_operational_criticality' => [
|
|
'label' => 'Criticita Operativa',
|
|
'max' => 25,
|
|
'help' => 'Quanto il sistema e essenziale per l\'erogazione dei servizi core business.',
|
|
'options' => [
|
|
'critical' => ['label' => 'Critico', 'points' => 25, 'desc' => 'Sistema indispensabile: un suo fermo blocca subito i servizi core dell\'organizzazione.'],
|
|
'very_high' => ['label' => 'Molto Alto', 'points' => 20, 'desc' => 'Sistema primario: un fermo degrada gravemente i servizi core, con alternative molto limitate.'],
|
|
'high' => ['label' => 'Alto', 'points' => 15, 'desc' => 'Sistema importante: il fermo rallenta i servizi core ma esistono alternative parziali.'],
|
|
'medium' => ['label' => 'Medio', 'points' => 10, 'desc' => 'Sistema di supporto: il fermo ha impatto contenuto e gestibile sui servizi core.'],
|
|
'low' => ['label' => 'Basso', 'points' => 5, 'desc' => 'Sistema accessorio: il fermo incide poco sull\'operativita.'],
|
|
'negligible' => ['label' => 'Trascurabile', 'points' => 0, 'desc' => 'Sistema non essenziale: nessun impatto pratico sui servizi core.'],
|
|
],
|
|
],
|
|
'c2_disruption_impact' => [
|
|
'label' => 'Impatto Interruzione',
|
|
'max' => 25,
|
|
'help' => 'Conseguenze di un\'interruzione in termini di durata e utenti impattati.',
|
|
'options' => [
|
|
'gt24h_gt70' => ['label' => '>24h + >70% utenti', 'points' => 25, 'desc' => 'Interruzione oltre 24 ore che coinvolge piu del 70% degli utenti.'],
|
|
'h8_24_50_70' => ['label' => '8-24h + 50-70% utenti', 'points' => 20, 'desc' => 'Interruzione da 8 a 24 ore, dal 50 al 70% degli utenti coinvolti.'],
|
|
'h4_8_30_50' => ['label' => '4-8h + 30-50% utenti', 'points' => 15, 'desc' => 'Interruzione da 4 a 8 ore, dal 30 al 50% degli utenti coinvolti.'],
|
|
'h1_4_10_30' => ['label' => '1-4h + 10-30% utenti', 'points' => 10, 'desc' => 'Interruzione da 1 a 4 ore, dal 10 al 30% degli utenti coinvolti.'],
|
|
'lt1h_lt10' => ['label' => '<1h + <10% utenti', 'points' => 5, 'desc' => 'Interruzione sotto 1 ora, meno del 10% degli utenti coinvolti.'],
|
|
'none' => ['label' => 'Nessun impatto', 'points' => 0, 'desc' => 'Nessun impatto operativo apprezzabile in caso di interruzione.'],
|
|
],
|
|
],
|
|
'c3_data_processed' => [
|
|
'label' => 'Dati Trattati',
|
|
'max' => 20,
|
|
'help' => 'Sensibilita e criticita dei dati gestiti dal sistema.',
|
|
'options' => [
|
|
'gdpr_art9' => ['label' => 'Dati Sensibili Art.9 GDPR', 'points' => 20, 'desc' => 'Categorie particolari di dati personali (salute, biometrici, ecc.) ai sensi dell\'Art.9 GDPR.'],
|
|
'personal_large' => ['label' => 'Dati Personali larga scala', 'points' => 15, 'desc' => 'Dati personali comuni trattati su larga scala.'],
|
|
'personal_fin' => ['label' => 'Dati Personali + Finanziari', 'points' => 10, 'desc' => 'Dati personali insieme a dati finanziari o di pagamento.'],
|
|
'confidential' => ['label' => 'Dati Aziendali Riservati', 'points' => 5, 'desc' => 'Dati aziendali riservati non personali (know-how, contratti).'],
|
|
'public' => ['label' => 'Dati Pubblici', 'points' => 0, 'desc' => 'Solo dati pubblici o gia liberamente divulgabili.'],
|
|
],
|
|
],
|
|
'c4_dependencies' => [
|
|
'label' => 'Dipendenze',
|
|
'max' => 15,
|
|
'help' => 'Quanti altri sistemi critici dipendono da questo sistema.',
|
|
'options' => [
|
|
'ge5_critical' => ['label' => '>=5 sistemi critici', 'points' => 15, 'desc' => 'Da questo sistema dipendono 5 o piu sistemi critici.'],
|
|
'n3_4_critical' => ['label' => '3-4 sistemi critici', 'points' => 12, 'desc' => 'Da questo sistema dipendono 3 o 4 sistemi critici.'],
|
|
'n2_critical' => ['label' => '2 sistemi critici', 'points' => 9, 'desc' => 'Da questo sistema dipendono 2 sistemi critici.'],
|
|
'n1_critical' => ['label' => '1 sistema critico', 'points' => 6, 'desc' => 'Da questo sistema dipende 1 sistema critico.'],
|
|
'noncritical' => ['label' => '1-2 sistemi non critici', 'points' => 3, 'desc' => 'Dipendono solo 1-2 sistemi non critici.'],
|
|
'none' => ['label' => 'Nessuna dipendenza', 'points' => 0, 'desc' => 'Nessun altro sistema dipende da questo.'],
|
|
],
|
|
],
|
|
'c5_exposure' => [
|
|
'label' => 'Esposizione',
|
|
'max' => 10,
|
|
'help' => 'Superficie di attacco ed esposizione del sistema.',
|
|
'options' => [
|
|
'internet_no_mfa' => ['label' => 'Internet pubblico senza MFA', 'points' => 10, 'desc' => 'Esposto su Internet pubblico senza autenticazione a piu fattori.'],
|
|
'internet_mfa' => ['label' => 'Internet con MFA', 'points' => 8, 'desc' => 'Esposto su Internet ma protetto da autenticazione a piu fattori.'],
|
|
'partner_net' => ['label' => 'Reti partner/fornitori', 'points' => 6, 'desc' => 'Raggiungibile dalle reti di partner o fornitori.'],
|
|
'intranet' => ['label' => 'Rete aziendale intranet', 'points' => 4, 'desc' => 'Raggiungibile solo dalla rete aziendale interna.'],
|
|
'mgmt_isolated' => ['label' => 'Rete gestione isolata', 'points' => 2, 'desc' => 'Accessibile solo da una rete di gestione isolata.'],
|
|
'air_gapped' => ['label' => 'Completamente isolato', 'points' => 0, 'desc' => 'Completamente isolato, senza connettivita di rete.'],
|
|
],
|
|
],
|
|
'c6_regulatory' => [
|
|
'label' => 'Obblighi Normativi',
|
|
'max' => 5,
|
|
'help' => 'Se il sistema e soggetto a obblighi normativi o contrattuali specifici.',
|
|
'options' => [
|
|
'nis2_required' => ['label' => 'Richiesto da NIS2', 'points' => 5, 'desc' => 'Sistema che rientra negli obblighi NIS2 dell\'organizzazione.'],
|
|
'mandatory_cert' => ['label' => 'Certificazioni obbligatorie', 'points' => 4, 'desc' => 'Soggetto a certificazioni obbligatorie (es. ISO o settoriali).'],
|
|
'strict_sla' => ['label' => 'Obblighi SLA stringenti', 'points' => 3, 'desc' => 'Vincolato a SLA contrattuali stringenti.'],
|
|
'external_audit' => ['label' => 'Audit esterni regolari', 'points' => 2, 'desc' => 'Sottoposto regolarmente ad audit esterni.'],
|
|
'none' => ['label' => 'Nessun obbligo', 'points' => 0, 'desc' => 'Nessun obbligo normativo o contrattuale specifico.'],
|
|
],
|
|
],
|
|
];
|
|
|
|
/**
|
|
* Inferenza euristica dei 6 criteri GV.OC-04 dai campi di un asset
|
|
* importato da CMDB/cloud. Permette lo scoring automatico in bulk senza
|
|
* compilazione manuale della griglia. I valori restituiti sono chiavi
|
|
* VALIDE della GRID, quindi calculate() li accetta senza eccezioni.
|
|
*
|
|
* Campi letti: criticality, asset_type, data_classification,
|
|
* internet_facing, dependencies_count, regulated.
|
|
*/
|
|
public static function inferCriteria(array $a): array
|
|
{
|
|
$crit = strtolower((string) ($a['criticality'] ?? 'medium'));
|
|
|
|
// C1 Criticita Operativa <- criticality
|
|
$c1 = ['critical' => 'critical', 'high' => 'high', 'medium' => 'medium', 'low' => 'low'][$crit] ?? 'medium';
|
|
|
|
// C2 Impatto Interruzione <- criticality (proxy)
|
|
$c2 = ['critical' => 'gt24h_gt70', 'high' => 'h4_8_30_50', 'medium' => 'h1_4_10_30', 'low' => 'lt1h_lt10'][$crit] ?? 'h1_4_10_30';
|
|
|
|
// C3 Dati Trattati <- data_classification (con proxy dal tipo asset)
|
|
$dc = strtolower((string) ($a['data_classification'] ?? ''));
|
|
$c3map = [
|
|
'special' => 'gdpr_art9', 'art9' => 'gdpr_art9', 'sensitive' => 'gdpr_art9',
|
|
'personal' => 'personal_large', 'pii' => 'personal_large',
|
|
'financial' => 'personal_fin', 'confidential' => 'confidential',
|
|
'internal' => 'confidential', 'public' => 'public',
|
|
];
|
|
$c3 = $c3map[$dc] ?? ((($a['asset_type'] ?? '') === 'data') ? 'personal_large' : 'confidential');
|
|
|
|
// C4 Dipendenze <- numero dipendenze critiche
|
|
$deps = (int) ($a['dependencies_count'] ?? 0);
|
|
$c4 = $deps >= 5 ? 'ge5_critical' : ($deps >= 3 ? 'n3_4_critical' : ($deps === 2 ? 'n2_critical' : ($deps === 1 ? 'n1_critical' : 'none')));
|
|
|
|
// C5 Esposizione <- internet_facing
|
|
$c5 = !empty($a['internet_facing']) ? 'internet_mfa' : 'intranet';
|
|
|
|
// C6 Obblighi Normativi <- regulated
|
|
$c6 = !empty($a['regulated']) ? 'nis2_required' : 'none';
|
|
|
|
return [
|
|
'c1_operational_criticality' => $c1,
|
|
'c2_disruption_impact' => $c2,
|
|
'c3_data_processed' => $c3,
|
|
'c4_dependencies' => $c4,
|
|
'c5_exposure' => $c5,
|
|
'c6_regulatory' => $c6,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Calcola lo score a partire dalle selezioni dell'utente.
|
|
*
|
|
* @param array $criteria mappa criterioKey => optionValue
|
|
* es. ['c1_operational_criticality' => 'critical', ...]
|
|
* @return array{score:int, class:string, is_relevant:bool, breakdown:array, criticality:string}
|
|
* @throws InvalidArgumentException se un criterio/opzione non e valido
|
|
*/
|
|
public static function calculate(array $criteria): array
|
|
{
|
|
$score = 0;
|
|
$breakdown = [];
|
|
|
|
foreach (self::GRID as $key => $def) {
|
|
if (!array_key_exists($key, $criteria)) {
|
|
throw new InvalidArgumentException("Criterio mancante: {$key}");
|
|
}
|
|
$optVal = $criteria[$key];
|
|
if (!isset($def['options'][$optVal])) {
|
|
throw new InvalidArgumentException("Opzione non valida '{$optVal}' per criterio {$key}");
|
|
}
|
|
$pts = $def['options'][$optVal]['points'];
|
|
$score += $pts;
|
|
$breakdown[$key] = [
|
|
'value' => $optVal,
|
|
'label' => $def['options'][$optVal]['label'],
|
|
'points' => $pts,
|
|
'max' => $def['max'],
|
|
];
|
|
}
|
|
|
|
$class = self::classify($score);
|
|
|
|
return [
|
|
'score' => $score,
|
|
'class' => $class,
|
|
'is_relevant' => $score >= self::RELEVANCE_THRESHOLD,
|
|
'breakdown' => $breakdown,
|
|
// mapping verso l'enum legacy assets.criticality per coerenza UI esistente
|
|
'criticality' => self::toLegacyCriticality($score),
|
|
];
|
|
}
|
|
|
|
/** Classe testuale secondo le soglie ufficiali. */
|
|
public static function classify(int $score): string
|
|
{
|
|
if ($score >= 80) return 'critico';
|
|
if ($score >= 60) return 'alto';
|
|
if ($score >= 40) return 'medio';
|
|
if ($score >= 20) return 'basso';
|
|
return 'trascurabile';
|
|
}
|
|
|
|
/** Allinea lo score all'enum assets.criticality preesistente (low/medium/high/critical). */
|
|
public static function toLegacyCriticality(int $score): string
|
|
{
|
|
if ($score >= 80) return 'critical';
|
|
if ($score >= 60) return 'high';
|
|
if ($score >= 40) return 'medium';
|
|
return 'low';
|
|
}
|
|
|
|
/** Misure obbligatorie associate alla classe (per report GV.OC-04 / UI). */
|
|
public static function requiredMeasures(string $class): array
|
|
{
|
|
return [
|
|
'critico' => [
|
|
'Monitoraggio continuo 24/7 (SIEM/SOC)',
|
|
'Backup immutabile con test ripristino periodico',
|
|
'MFA obbligatoria + accessi privilegiati controllati (PAM)',
|
|
'Inclusione obbligatoria in BIA e piano di continuita',
|
|
'Test di vulnerabilita/penetration test almeno annuali',
|
|
],
|
|
'alto' => [
|
|
'Monitoraggio in orario lavorativo + alerting',
|
|
'Backup regolari con verifica integrita',
|
|
'MFA per accessi remoti',
|
|
'Inclusione in risk assessment ciclico',
|
|
],
|
|
'medio' => [
|
|
'Logging centralizzato',
|
|
'Backup periodici',
|
|
'Patch management documentato',
|
|
],
|
|
'basso' => [
|
|
'Inventario aggiornato',
|
|
'Patch management standard',
|
|
],
|
|
'trascurabile' => [
|
|
'Censimento in inventario asset',
|
|
],
|
|
][$class] ?? [];
|
|
}
|
|
}
|