[FEAT] Services API, Webhook, Whistleblowing, Normative + integrazioni
Sprint completo — prodotto presentation-ready:
Services API (read-only, API Key + scope):
- GET /api/services/status|compliance-summary|risks-feed|incidents-feed
- GET /api/services/controls-status|assets-critical|suppliers-risk|policies-approved
- GET /api/services/openapi (spec OpenAPI 3.0.3 JSON)
Webhook Outbound (Stripe-like HMAC-SHA256):
- CRUD api_keys + webhook_subscriptions (Settings → 2 nuovi tab)
- WebhookService: retry 3x backoff (0s/5min/30min), delivery log
- Trigger auto in IncidentController, RiskController, PolicyController
- Delivery log, test ping, processRetry
Nuovi moduli:
- WhistleblowingController (Art.32 NIS2): anonimato garantito, timeline, token tracking
- NormativeController: feed NIS2/ACN/DORA con ACK tracciato per audit
Frontend:
- whistleblowing.html: form submit anonimo/firmato + gestione CISO
- normative.html: feed con presa visione documentata + progress bar ACK
- public/docs/api.html: documentazione API dark theme (Swagger-like)
- settings.html: tab API Keys + tab Webhook
- integrations/: guide per lg231, SustainAI, AllRisk, SIEM (widget + codice)
- Sidebar: Segnalazioni + Normative aggiunte a common.js
DB: migration 007 (api_keys, webhook_subscriptions, webhook_deliveries),
008 (whistleblowing_reports + timeline),
009 (normative_updates + normative_ack + seed NIS2/ACN/DORA/ISO)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
3f4b451e2a
commit
86e9bdded2
@@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AllRisk × NIS2 Agile — Integrazione</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--primary: #06b6d4; --amber: #f59e0b;
|
||||
--gray-200: #e2e8f0; --gray-500: #64748b; --gray-700: #334155; --gray-900: #0f172a;
|
||||
--radius: 8px; --font: -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
|
||||
--mono: 'Cascadia Code','Consolas',monospace;
|
||||
}
|
||||
body { background: #f8fafc; font-family: var(--font); color: var(--gray-900); }
|
||||
.header { background: linear-gradient(135deg, #78350f, #92400e); padding: 40px 48px; color: #fff; }
|
||||
.header-badges { display: flex; gap: 10px; margin-bottom: 16px; }
|
||||
.badge { padding: 4px 12px; border-radius: 20px; font-size: 0.75rem; font-weight: 700; }
|
||||
.badge-nis2 { background: rgba(6,182,212,0.2); color: #67e8f9; border: 1px solid rgba(6,182,212,0.3); }
|
||||
.badge-ar { background: rgba(245,158,11,0.2); color: #fde68a; border: 1px solid rgba(245,158,11,0.3); }
|
||||
h1 { font-size: 1.875rem; font-weight: 800; margin-bottom: 8px; }
|
||||
.header p { color: #fde68a; font-size: 1rem; }
|
||||
.container { max-width: 960px; margin: 0 auto; padding: 40px 24px; }
|
||||
h2 { font-size: 1.25rem; font-weight: 700; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 2px solid var(--gray-200); }
|
||||
.section { margin-bottom: 48px; }
|
||||
p { color: var(--gray-500); font-size: 0.9rem; line-height: 1.7; margin-bottom: 12px; }
|
||||
pre { background: #1e293b; color: #e2e8f0; padding: 20px; border-radius: var(--radius); font-family: var(--mono); font-size: 0.8125rem; overflow-x: auto; line-height: 1.7; margin: 12px 0; }
|
||||
code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-family: var(--mono); font-size: 0.85em; color: var(--gray-700); }
|
||||
.step { display: flex; gap: 16px; margin-bottom: 20px; padding: 18px; background: #fff; border: 1px solid var(--gray-200); border-radius: var(--radius); }
|
||||
.step-num { width: 30px; height: 30px; border-radius: 50%; background: var(--amber); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; flex-shrink: 0; }
|
||||
.step-content h3 { font-size: 0.9375rem; font-weight: 700; margin-bottom: 6px; }
|
||||
.info-box { padding: 14px 16px; border-radius: var(--radius); margin: 16px 0; font-size: 0.875rem; }
|
||||
.info-amber { background: #fffbeb; border-left: 3px solid var(--amber); color: #92400e; }
|
||||
.field-map { width: 100%; border-collapse: collapse; }
|
||||
.field-map th { padding: 9px 12px; background: #f1f5f9; font-size: 0.8rem; font-weight: 700; text-align: left; }
|
||||
.field-map td { padding: 9px 12px; border-bottom: 1px solid var(--gray-200); font-size: 0.8rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-badges">
|
||||
<span class="badge badge-ar">AllRisk</span>
|
||||
<span>×</span>
|
||||
<span class="badge badge-nis2">NIS2 Agile</span>
|
||||
</div>
|
||||
<h1>Integrazione AllRisk ← NIS2 Agile</h1>
|
||||
<p>Esporta il risk register cyber NIS2 verso AllRisk per consolidamento nel registro rischi enterprise. Importazione automatica dei rischi HIGH/CRITICAL tramite API Key.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="section">
|
||||
<h2>Scenario di integrazione</h2>
|
||||
<p>AllRisk è il registro rischi enterprise centralizzato. NIS2 Agile gestisce i rischi cyber in ottica normativa (ISO 27005 / NIS2). L'integrazione permette di:</p>
|
||||
<div class="step"><div class="step-num">1</div><div class="step-content"><h3>Consolidamento automatico</h3><p>I rischi HIGH/CRITICAL di NIS2 vengono automaticamente importati in AllRisk come rischi di categoria "Cyber/IT" con score e deadline normalizzati.</p></div></div>
|
||||
<div class="step"><div class="step-num">2</div><div class="step-content"><h3>Aggiornamento bidirezionale</h3><p>AllRisk notifica NIS2 tramite webhook se un rischio enterprise si trasforma in minaccia cyber (es. incidente supply chain → rischio NIS2).</p></div></div>
|
||||
<div class="step"><div class="step-num">3</div><div class="step-content"><h3>Reporting unificato</h3><p>Report rischi enterprise unico che include dimensione NIS2 con compliance score per ogni categoria di rischio cyber (Art.21 domains).</p></div></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Mappatura campi NIS2 → AllRisk</h2>
|
||||
<table class="field-map">
|
||||
<thead><tr><th>Campo NIS2 Agile</th><th>Campo AllRisk</th><th>Trasformazione</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>risk_code</code></td><td><code>external_ref</code></td><td>Prefisso <code>NIS2-</code> + codice</td></tr>
|
||||
<tr><td><code>title</code></td><td><code>risk_name</code></td><td>Diretta</td></tr>
|
||||
<tr><td><code>risk_level</code></td><td><code>severity</code></td><td>critical→5, high→4, medium→3, low→2</td></tr>
|
||||
<tr><td><code>likelihood</code> × <code>impact</code></td><td><code>risk_score</code></td><td>Scala 0–25 → 0–100</td></tr>
|
||||
<tr><td><code>category</code></td><td><code>risk_category</code></td><td>Mappato su tassonomia AllRisk "Cyber"</td></tr>
|
||||
<tr><td><code>nis2_article</code></td><td><code>regulatory_ref</code></td><td>Prefisso "NIS2 Art."</td></tr>
|
||||
<tr><td><code>treatment</code></td><td><code>response_strategy</code></td><td>mitigate→Reduce, accept→Accept, ecc.</td></tr>
|
||||
<tr><td><code>review_date</code></td><td><code>next_review</code></td><td>Diretta (ISO 8601)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Import automatico — Script PHP</h2>
|
||||
<pre><span style="color:#7dd3fc;">// AllRisk — import_nis2_risks.php (cron settimanale)</span>
|
||||
|
||||
<span style="color:#f1fa8c;">$apiKey</span> = getenv(<span style="color:#86efac;">'NIS2_API_KEY'</span>);
|
||||
<span style="color:#f1fa8c;">$orgId</span> = getenv(<span style="color:#86efac;">'NIS2_ORG_ID'</span>);
|
||||
<span style="color:#f1fa8c;">$baseUrl</span> = <span style="color:#86efac;">'https://nis2.certisource.it/api'</span>;
|
||||
<span style="color:#f1fa8c;">$allriskOrgId</span> = getenv(<span style="color:#86efac;">'ALLRISK_ORG_ID'</span>);
|
||||
|
||||
<span style="color:#7dd3fc;">// Fetch rischi HIGH/CRITICAL aperti da NIS2</span>
|
||||
<span style="color:#f1fa8c;">$response</span> = nis2Get(<span style="color:#f1fa8c;">$baseUrl</span> . <span style="color:#86efac;">'/services/risks-feed'</span>, [
|
||||
<span style="color:#86efac;">'level'</span> => <span style="color:#86efac;">'high'</span>,
|
||||
<span style="color:#86efac;">'status'</span> => <span style="color:#86efac;">'open'</span>,
|
||||
<span style="color:#86efac;">'limit'</span> => 200,
|
||||
], <span style="color:#f1fa8c;">$apiKey</span>, <span style="color:#f1fa8c;">$orgId</span>);
|
||||
|
||||
<span style="color:#f1fa8c;">$risks</span> = <span style="color:#f1fa8c;">$response</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'risks'</span>] ?? [];
|
||||
<span style="color:#f1fa8c;">$imported</span> = 0;
|
||||
|
||||
foreach (<span style="color:#f1fa8c;">$risks</span> as <span style="color:#f1fa8c;">$risk</span>) {
|
||||
<span style="color:#f1fa8c;">$allriskPayload</span> = [
|
||||
<span style="color:#86efac;">'external_ref'</span> => <span style="color:#86efac;">'NIS2-'</span> . <span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'risk_code'</span>],
|
||||
<span style="color:#86efac;">'risk_name'</span> => <span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'title'</span>],
|
||||
<span style="color:#86efac;">'risk_category'</span> => <span style="color:#86efac;">'Cyber/IT'</span>,
|
||||
<span style="color:#86efac;">'severity'</span> => mapSeverity(<span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'risk_level'</span>]),
|
||||
<span style="color:#86efac;">'risk_score'</span> => round(<span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'risk_score'</span>] / 25 * 100),
|
||||
<span style="color:#86efac;">'regulatory_ref'</span> => <span style="color:#86efac;">'NIS2 '</span> . (<span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'nis2_article'</span>] ?? <span style="color:#86efac;">'Art.21'</span>),
|
||||
<span style="color:#86efac;">'response_strategy'</span> => mapTreatment(<span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'treatment'</span>]),
|
||||
<span style="color:#86efac;">'source_system'</span> => <span style="color:#86efac;">'NIS2 Agile'</span>,
|
||||
<span style="color:#86efac;">'organization_id'</span> => <span style="color:#f1fa8c;">$allriskOrgId</span>,
|
||||
];
|
||||
|
||||
<span style="color:#7dd3fc;">// Upsert: aggiorna se external_ref esiste, crea altrimenti</span>
|
||||
AllRiskApiClient::upsertRisk(<span style="color:#f1fa8c;">$allriskPayload</span>);
|
||||
<span style="color:#f1fa8c;">$imported</span>++;
|
||||
}
|
||||
|
||||
error_log("[NIS2→AllRisk] Importati {<span style="color:#f1fa8c;">$imported</span>} rischi");
|
||||
|
||||
<span style="color:#f1fa8c;">function</span> mapSeverity(<span style="color:#f1fa8c;">string $level</span>): int {
|
||||
return match(<span style="color:#f1fa8c;">$level</span>) { <span style="color:#86efac;">'critical'</span> => 5, <span style="color:#86efac;">'high'</span> => 4, <span style="color:#86efac;">'medium'</span> => 3, default => 2 };
|
||||
}
|
||||
<span style="color:#f1fa8c;">function</span> mapTreatment(<span style="color:#f1fa8c;">string $t</span>): string {
|
||||
return match(<span style="color:#f1fa8c;">$t</span>) {
|
||||
<span style="color:#86efac;">'mitigate'</span> => <span style="color:#86efac;">'Reduce'</span>, <span style="color:#86efac;">'accept'</span> => <span style="color:#86efac;">'Accept'</span>,
|
||||
<span style="color:#86efac;">'transfer'</span> => <span style="color:#86efac;">'Transfer'</span>, default => <span style="color:#86efac;">'Avoid'</span>
|
||||
};
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Webhook NIS2 → AllRisk (real-time)</h2>
|
||||
<p>Configura webhook per importazione immediata dei nuovi rischi HIGH/CRITICAL:</p>
|
||||
<pre><span style="color:#7dd3fc;"># Settings → Webhook in NIS2 Agile</span>
|
||||
URL: https://allrisk.certisource.it/api/v1/webhooks/nis2
|
||||
Events: risk.high_created, compliance.score_changed, incident.significant</pre>
|
||||
|
||||
<pre><span style="color:#7dd3fc;">// AllRisk — webhook receiver per NIS2</span>
|
||||
<span style="color:#f1fa8c;">$payload</span> = verifyAndDecode($_SERVER[<span style="color:#86efac;">'HTTP_X_NIS2_SIGNATURE'</span>], file_get_contents(<span style="color:#86efac;">'php://input'</span>));
|
||||
|
||||
if (<span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'event'</span>] === <span style="color:#86efac;">'risk.high_created'</span>) {
|
||||
<span style="color:#f1fa8c;">$risk</span> = <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'risk'</span>];
|
||||
AllRiskApiClient::upsertRisk([
|
||||
<span style="color:#86efac;">'external_ref'</span> => <span style="color:#86efac;">'NIS2-'</span> . <span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'id'</span>],
|
||||
<span style="color:#86efac;">'risk_name'</span> => <span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'title'</span>],
|
||||
<span style="color:#86efac;">'severity'</span> => <span style="color:#f1fa8c;">$risk</span>[<span style="color:#86efac;">'risk_level'</span>] === <span style="color:#86efac;">'critical'</span> ? 5 : 4,
|
||||
<span style="color:#86efac;">'source_system'</span>=> <span style="color:#86efac;">'NIS2 Agile'</span>,
|
||||
<span style="color:#86efac;">'alert'</span> => true, <span style="color:#7dd3fc;">// Notifica CRO AllRisk</span>
|
||||
]);
|
||||
}
|
||||
http_response_code(200);</pre>
|
||||
</div>
|
||||
|
||||
<div class="info-box info-amber">
|
||||
<strong>Nota architetturale:</strong> Il flusso è unidirezionale NIS2 → AllRisk. Per il flusso inverso (AllRisk → NIS2), AllRisk chiama l'API NIS2 per creare rischi cyber direttamente tramite le API JWT (POST /api/risks/create) usando le credenziali dell'utente integration.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>NIS2 Agile — Integrazioni</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #f8fafc; --white: #ffffff; --primary: #06b6d4;
|
||||
--gray-100: #f1f5f9; --gray-200: #e2e8f0; --gray-500: #64748b;
|
||||
--gray-700: #334155; --gray-900: #0f172a;
|
||||
--radius: 12px; --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
||||
}
|
||||
body { background: var(--bg); font-family: var(--font); color: var(--gray-900); padding: 40px 24px; }
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
h1 { font-size: 1.75rem; font-weight: 800; margin-bottom: 8px; }
|
||||
p.subtitle { color: var(--gray-500); margin-bottom: 40px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; }
|
||||
.card { background: var(--white); border-radius: var(--radius); border: 1px solid var(--gray-200); padding: 28px; transition: all 0.2s; }
|
||||
.card:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
|
||||
.card-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; font-size: 1.5rem; }
|
||||
.card-title { font-size: 1.125rem; font-weight: 700; margin-bottom: 8px; }
|
||||
.card-desc { font-size: 0.875rem; color: var(--gray-500); line-height: 1.6; margin-bottom: 20px; }
|
||||
.card-link { display: inline-flex; align-items: center; gap: 6px; color: var(--primary); font-size: 0.875rem; font-weight: 600; text-decoration: none; }
|
||||
.card-link:hover { text-decoration: underline; }
|
||||
.badge { display: inline-flex; padding: 3px 8px; border-radius: 20px; font-size: 0.7rem; font-weight: 700; margin-bottom: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>NIS2 Agile — Integrazioni</h1>
|
||||
<p class="subtitle">Pagine di integrazione per connettere NIS2 Agile con altri prodotti della suite Agile.</p>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<div class="card-icon" style="background:#f0fdf4;">📋</div>
|
||||
<span class="badge" style="background:#dcfce7; color:#166534;">Webhook + Widget</span>
|
||||
<div class="card-title">231 Agile ← NIS2</div>
|
||||
<div class="card-desc">Integra i dati di compliance NIS2 in 231 Agile. Visualizza compliance score, rischi cyber e incidenti significativi nel contesto del Modello 231.</div>
|
||||
<a href="lg231.html" class="card-link">Guida integrazione →</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon" style="background:#f0f9ff;">🌿</div>
|
||||
<span class="badge" style="background:#e0f2fe; color:#075985;">API Widget</span>
|
||||
<div class="card-title">SustainAI ← NIS2</div>
|
||||
<div class="card-desc">Alimenta i report ESG/sostenibilità di SustainAI con dati di governance cybersecurity NIS2. Compliance score e policy approvate per l'area G (Governance).</div>
|
||||
<a href="sustainai.html" class="card-link">Guida integrazione →</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon" style="background:#fef9c3;">⚡</div>
|
||||
<span class="badge" style="background:#fef9c3; color:#854d0e;">REST API</span>
|
||||
<div class="card-title">AllRisk ← NIS2</div>
|
||||
<div class="card-desc">Esporta il risk register cyber NIS2 verso AllRisk per consolidamento nel registro rischi enterprise. Sincronizzazione automatica via API Key.</div>
|
||||
<a href="allrisk.html" class="card-link">Guida integrazione →</a>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-icon" style="background:#faf5ff;">🔒</div>
|
||||
<span class="badge" style="background:#f3e8ff; color:#6b21a8;">SIEM</span>
|
||||
<div class="card-title">SIEM / SOC</div>
|
||||
<div class="card-desc">Integra NIS2 Agile con SIEM (Splunk, Elastic, IBM QRadar) tramite webhook outbound. Ricevi eventi incident.created, risk.high_created in tempo reale.</div>
|
||||
<a href="siem.html" class="card-link">Guida integrazione →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,225 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>231 Agile × NIS2 Agile — Integrazione</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg: #f8fafc; --white: #fff; --primary: #06b6d4; --green: #10b981;
|
||||
--gray-100: #f1f5f9; --gray-200: #e2e8f0; --gray-500: #64748b; --gray-700: #334155; --gray-900: #0f172a;
|
||||
--danger: #ef4444; --warning: #f59e0b; --mono: 'Cascadia Code','Consolas',monospace;
|
||||
--radius: 8px; --font: -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
|
||||
}
|
||||
body { background: var(--bg); font-family: var(--font); color: var(--gray-900); }
|
||||
.header { background: linear-gradient(135deg, #1e293b, #0f172a); padding: 40px 48px; color: #fff; }
|
||||
.header-badges { display: flex; gap: 10px; margin-bottom: 16px; }
|
||||
.badge { padding: 4px 12px; border-radius: 20px; font-size: 0.75rem; font-weight: 700; }
|
||||
.badge-nis2 { background: rgba(6,182,212,0.2); color: #67e8f9; border: 1px solid rgba(6,182,212,0.3); }
|
||||
.badge-231 { background: rgba(16,185,129,0.2); color: #6ee7b7; border: 1px solid rgba(16,185,129,0.3); }
|
||||
h1 { font-size: 1.875rem; font-weight: 800; margin-bottom: 8px; }
|
||||
.header p { color: #94a3b8; font-size: 1rem; }
|
||||
.container { max-width: 960px; margin: 0 auto; padding: 40px 24px; }
|
||||
h2 { font-size: 1.25rem; font-weight: 700; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 2px solid var(--gray-200); }
|
||||
h3 { font-size: 1rem; font-weight: 700; margin-bottom: 8px; color: var(--gray-700); }
|
||||
.section { margin-bottom: 48px; }
|
||||
p { color: var(--gray-500); font-size: 0.9rem; line-height: 1.7; margin-bottom: 12px; }
|
||||
pre, code { font-family: var(--mono); }
|
||||
pre { background: #1e293b; color: #e2e8f0; padding: 20px; border-radius: var(--radius); font-size: 0.8125rem; overflow-x: auto; line-height: 1.7; margin: 12px 0; }
|
||||
code { background: var(--gray-100); padding: 2px 6px; border-radius: 4px; font-size: 0.85em; color: var(--gray-700); }
|
||||
.step { display: flex; gap: 16px; margin-bottom: 24px; padding: 20px; background: var(--white); border: 1px solid var(--gray-200); border-radius: var(--radius); }
|
||||
.step-num { width: 32px; height: 32px; border-radius: 50%; background: var(--primary); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; flex-shrink: 0; }
|
||||
.step-content h3 { margin-bottom: 6px; }
|
||||
.info-box { padding: 14px 16px; border-radius: var(--radius); margin-bottom: 16px; font-size: 0.875rem; }
|
||||
.info-warn { background: #fff7ed; border-left: 3px solid var(--warning); color: #92400e; }
|
||||
.info-info { background: #eff6ff; border-left: 3px solid #3b82f6; color: #1e40af; }
|
||||
.info-success { background: #f0fdf4; border-left: 3px solid var(--green); color: #065f46; }
|
||||
.widget-preview { background: var(--white); border: 1px solid var(--gray-200); border-radius: var(--radius); padding: 24px; margin-top: 20px; }
|
||||
.widget-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
|
||||
.widget-title { font-size: 0.875rem; font-weight: 700; display: flex; align-items: center; gap: 8px; }
|
||||
.score-ring { width: 80px; height: 80px; display: flex; align-items: center; justify-content: center; background: conic-gradient(#06b6d4 73%, #e2e8f0 0); border-radius: 50%; font-size: 1.25rem; font-weight: 800; }
|
||||
.metric-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 16px; }
|
||||
.metric { text-align: center; padding: 12px; background: var(--gray-100); border-radius: 6px; }
|
||||
.metric-val { font-size: 1.25rem; font-weight: 700; color: var(--gray-900); }
|
||||
.metric-label { font-size: 0.7rem; color: var(--gray-500); margin-top: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-badges">
|
||||
<span class="badge badge-nis2">NIS2 Agile</span>
|
||||
<span>×</span>
|
||||
<span class="badge badge-231">231 Agile</span>
|
||||
</div>
|
||||
<h1>Integrazione 231 Agile ← NIS2 Agile</h1>
|
||||
<p>Porta i dati di compliance cybersecurity NIS2 nel contesto del Modello Organizzativo 231. Rischi cyber come presidi 231, incidenti significativi come non conformità.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="section">
|
||||
<h2>Architettura dell'integrazione</h2>
|
||||
<p>NIS2 Agile espone un'API REST con API Key e un sistema di webhook outbound. 231 Agile può consumare i dati in due modalità:</p>
|
||||
<div class="step">
|
||||
<div class="step-num">A</div>
|
||||
<div class="step-content">
|
||||
<h3>Pull (Services API)</h3>
|
||||
<p>231 Agile chiama periodicamente le API NIS2 per aggiornare il cruscotto 231. Indicato per dati storici e report periodici (mensile/trimestrale).</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="step">
|
||||
<div class="step-num">B</div>
|
||||
<div class="step-content">
|
||||
<h3>Push (Webhook outbound)</h3>
|
||||
<p>NIS2 Agile notifica 231 Agile in tempo reale su eventi critici. Indicato per incidenti significativi, rischi HIGH/CRITICAL, variazioni compliance.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Step 1 — Crea API Key in NIS2 Agile</h2>
|
||||
<div class="info-warn">Richiede ruolo <code>org_admin</code> in NIS2 Agile.</div>
|
||||
<div class="step"><div class="step-num">1</div><div class="step-content"><h3>Accedi a Settings → API Keys</h3><p>In NIS2 Agile, vai in <code>Settings</code> → tab <strong>API Keys</strong> → <strong>Nuova API Key</strong>.</p></div></div>
|
||||
<div class="step"><div class="step-num">2</div><div class="step-content"><h3>Seleziona gli scope</h3><p>Per 231 Agile ti servono: <code>read:compliance</code>, <code>read:risks</code>, <code>read:incidents</code>. Opzionale: <code>read:policies</code>.</p></div></div>
|
||||
<div class="step"><div class="step-num">3</div><div class="step-content"><h3>Salva la chiave</h3><p>La chiave <code>nis2_xxxxx</code> viene mostrata <strong>una sola volta</strong>. Copiala in <strong>231 Agile → Integrazioni → NIS2 Agile → API Key</strong>.</p></div></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Step 2 — Integrazione Pull (PHP / 231 Agile)</h2>
|
||||
<p>Aggiungi questo codice nel cron job notturno di 231 Agile per sincronizzare i dati NIS2:</p>
|
||||
<pre><span style="color:#7dd3fc;">// 231 Agile — CronJob: sync_nis2_compliance.php</span>
|
||||
|
||||
<span style="color:#f1fa8c;">$apiKey</span> = getenv(<span style="color:#86efac;">'NIS2_API_KEY'</span>); <span style="color:#7dd3fc;">// nis2_abc123...</span>
|
||||
<span style="color:#f1fa8c;">$orgId</span> = getenv(<span style="color:#86efac;">'NIS2_ORG_ID'</span>);
|
||||
<span style="color:#f1fa8c;">$baseUrl</span> = <span style="color:#86efac;">'https://nis2.certisource.it/api'</span>;
|
||||
|
||||
<span style="color:#f1fa8c;">function</span> nis2Get(<span style="color:#f1fa8c;">string $endpoint</span>, array <span style="color:#f1fa8c;">$query</span> = []): array {
|
||||
global <span style="color:#f1fa8c;">$apiKey</span>, <span style="color:#f1fa8c;">$orgId</span>, <span style="color:#f1fa8c;">$baseUrl</span>;
|
||||
<span style="color:#f1fa8c;">$url</span> = <span style="color:#f1fa8c;">$baseUrl</span> . <span style="color:#f1fa8c;">$endpoint</span>;
|
||||
if (!empty(<span style="color:#f1fa8c;">$query</span>)) <span style="color:#f1fa8c;">$url</span> .= <span style="color:#86efac;">'?'</span> . http_build_query(<span style="color:#f1fa8c;">$query</span>);
|
||||
<span style="color:#f1fa8c;">$ch</span> = curl_init(<span style="color:#f1fa8c;">$url</span>);
|
||||
curl_setopt_array(<span style="color:#f1fa8c;">$ch</span>, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
<span style="color:#86efac;">'X-API-Key: '</span> . <span style="color:#f1fa8c;">$apiKey</span>,
|
||||
<span style="color:#86efac;">'X-Organization-Id: '</span> . <span style="color:#f1fa8c;">$orgId</span>,
|
||||
],
|
||||
]);
|
||||
return json_decode(curl_exec(<span style="color:#f1fa8c;">$ch</span>), true) ?? [];
|
||||
}
|
||||
|
||||
<span style="color:#7dd3fc;">// 1. Recupera compliance summary</span>
|
||||
<span style="color:#f1fa8c;">$compliance</span> = nis2Get(<span style="color:#86efac;">'/services/compliance-summary'</span>);
|
||||
<span style="color:#f1fa8c;">$score</span> = <span style="color:#f1fa8c;">$compliance</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'overall_score'</span>] ?? 0;
|
||||
|
||||
<span style="color:#7dd3fc;">// 2. Recupera rischi HIGH/CRITICAL per mappa rischi 231</span>
|
||||
<span style="color:#f1fa8c;">$risks</span> = nis2Get(<span style="color:#86efac;">'/services/risks-feed'</span>, [<span style="color:#86efac;">'level'</span> => <span style="color:#86efac;">'high'</span>, <span style="color:#86efac;">'status'</span> => <span style="color:#86efac;">'open'</span>]);
|
||||
|
||||
<span style="color:#7dd3fc;">// 3. Recupera incidenti Art.23 aperti</span>
|
||||
<span style="color:#f1fa8c;">$incidents</span> = nis2Get(<span style="color:#86efac;">'/services/incidents-feed'</span>, [
|
||||
<span style="color:#86efac;">'significant_only'</span> => 1, <span style="color:#86efac;">'status'</span> => <span style="color:#86efac;">'open'</span>
|
||||
]);
|
||||
|
||||
<span style="color:#7dd3fc;">// 4. Salva in DB 231 Agile — tabella nis2_integration</span>
|
||||
<span style="color:#7dd3fc;">// DB231::upsert('nis2_integration', ['org_id'=>$orgId231, 'score'=>$score, ...]);</span></pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Step 3 — Webhook Push (real-time)</h2>
|
||||
<p>Configura in NIS2 Agile → Settings → Webhook un endpoint del tuo server 231 Agile per ricevere eventi in tempo reale:</p>
|
||||
<pre><span style="color:#7dd3fc;"># Endpoint da configurare in NIS2 Agile → Settings → Webhook</span>
|
||||
URL: https://app-231.certisource.it/webhooks/nis2
|
||||
Events: incident.significant, incident.deadline_warning, risk.high_created, compliance.score_changed</pre>
|
||||
|
||||
<h3 style="margin-top:20px;">Receiver PHP su 231 Agile</h3>
|
||||
<pre><span style="color:#7dd3fc;">// 231 Agile — routes/webhook_nis2.php</span>
|
||||
|
||||
<span style="color:#f1fa8c;">$secret</span> = getenv(<span style="color:#86efac;">'NIS2_WEBHOOK_SECRET'</span>);
|
||||
<span style="color:#f1fa8c;">$body</span> = file_get_contents(<span style="color:#86efac;">'php://input'</span>);
|
||||
<span style="color:#f1fa8c;">$sig</span> = $_SERVER[<span style="color:#86efac;">'HTTP_X_NIS2_SIGNATURE'</span>] ?? <span style="color:#86efac;">''</span>;
|
||||
|
||||
if (!hash_equals(<span style="color:#86efac;">'sha256='</span> . hash_hmac(<span style="color:#86efac;">'sha256'</span>, <span style="color:#f1fa8c;">$body</span>, <span style="color:#f1fa8c;">$secret</span>), <span style="color:#f1fa8c;">$sig</span>)) {
|
||||
http_response_code(401); exit;
|
||||
}
|
||||
|
||||
<span style="color:#f1fa8c;">$payload</span> = json_decode(<span style="color:#f1fa8c;">$body</span>, true);
|
||||
<span style="color:#f1fa8c;">$event</span> = <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'event'</span>];
|
||||
|
||||
switch (<span style="color:#f1fa8c;">$event</span>) {
|
||||
case <span style="color:#86efac;">'incident.significant'</span>:
|
||||
<span style="color:#7dd3fc;">// Crea non-conformità in 231 Agile</span>
|
||||
NonConformityService::createFromNis2Incident(<span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'incident'</span>]);
|
||||
break;
|
||||
case <span style="color:#86efac;">'risk.high_created'</span>:
|
||||
<span style="color:#7dd3fc;">// Aggiorna mappa rischi presidi 231</span>
|
||||
RiskService::importFromNis2(<span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'risk'</span>]);
|
||||
break;
|
||||
case <span style="color:#86efac;">'compliance.score_changed'</span>:
|
||||
<span style="color:#7dd3fc;">// Aggiorna dashboard 231 con nuovo score NIS2</span>
|
||||
DashboardService::updateNis2Score(<span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'new_score'</span>]);
|
||||
break;
|
||||
}
|
||||
http_response_code(200);</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Widget NIS2 per Dashboard 231</h2>
|
||||
<p>Embed HTML con chiamata API diretta. Copialo nella dashboard di 231 Agile:</p>
|
||||
<div class="widget-preview">
|
||||
<div class="widget-header">
|
||||
<div class="widget-title">
|
||||
<svg viewBox="0 0 24 24" fill="#06b6d4" width="18" height="18"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z"/></svg>
|
||||
NIS2 Compliance (Preview widget)
|
||||
</div>
|
||||
<span style="font-size:0.7rem; color:#64748b;">nis2.certisource.it</span>
|
||||
</div>
|
||||
<div style="display:flex; align-items:center; gap:24px;">
|
||||
<div class="score-ring">73%</div>
|
||||
<div>
|
||||
<p style="font-size:0.9rem; font-weight:700; margin-bottom:4px;">Sostanzialmente Conforme</p>
|
||||
<p style="font-size:0.75rem; color:#64748b;">D.Lgs. 138/2024 — Art.21 NIS2</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<div class="metric"><div class="metric-val" style="color:#ef4444;">2</div><div class="metric-label">Incidenti aperti</div></div>
|
||||
<div class="metric"><div class="metric-val" style="color:#f59e0b;">5</div><div class="metric-label">Rischi HIGH</div></div>
|
||||
<div class="metric"><div class="metric-val" style="color:#10b981;">12</div><div class="metric-label">Policy approvate</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<pre style="margin-top:16px;"><span style="color:#7dd3fc;"><!-- Widget NIS2 per 231 Agile dashboard --></span>
|
||||
<div id="nis2-widget"></div>
|
||||
<script>
|
||||
(async () => {
|
||||
const r = await fetch('https://nis2.certisource.it/api/services/compliance-summary', {
|
||||
headers: {
|
||||
'X-API-Key': '<span style="color:#86efac;">nis2_YOUR_KEY</span>',
|
||||
'X-Organization-Id': '<span style="color:#86efac;">YOUR_ORG_ID</span>'
|
||||
}
|
||||
});
|
||||
const { data } = await r.json();
|
||||
document.getElementById('nis2-widget').innerHTML = `
|
||||
<div style="padding:20px; border:1px solid #e2e8f0; border-radius:8px;">
|
||||
<h4 style="font-size:.875rem; font-weight:700; margin-bottom:12px;">
|
||||
🔒 NIS2 Compliance Score
|
||||
</h4>
|
||||
<div style="font-size:2rem; font-weight:800; color:#06b6d4;">
|
||||
${data.overall_score}%
|
||||
</div>
|
||||
<div style="font-size:.75rem; color:#64748b;">${data.label}</div>
|
||||
<div style="margin-top:12px; display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px;">
|
||||
<div><strong>${data.incidents.open}</strong><br><small>Incidenti</small></div>
|
||||
<div><strong>${data.risks.high}</strong><br><small>Rischi HIGH</small></div>
|
||||
<div><strong>${data.policies.approved}</strong><br><small>Policy</small></div>
|
||||
</div>
|
||||
</div>`;
|
||||
})();
|
||||
</script></pre>
|
||||
</div>
|
||||
|
||||
<div class="info-success">
|
||||
<strong>Dati mappati 231 ↔ NIS2:</strong> Rischi cyber NIS2 → Presidi 231 (ex. D.Lgs. 231/01 Art.25-septies) | Incidenti significativi → Non Conformità 231 | Compliance score → KPI Operatività 231
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,179 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SIEM × NIS2 Agile — Integrazione</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--primary: #06b6d4; --purple: #8b5cf6;
|
||||
--gray-200: #e2e8f0; --gray-500: #64748b; --gray-700: #334155;
|
||||
--radius: 8px; --font: -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
|
||||
--mono: 'Cascadia Code','Consolas',monospace;
|
||||
}
|
||||
body { background: #f8fafc; font-family: var(--font); color: #0f172a; }
|
||||
.header { background: linear-gradient(135deg, #2e1065, #3b0764); padding: 40px 48px; color: #fff; }
|
||||
.header-badges { display: flex; gap: 10px; margin-bottom: 16px; }
|
||||
.badge { padding: 4px 12px; border-radius: 20px; font-size: 0.75rem; font-weight: 700; }
|
||||
.badge-nis2 { background: rgba(6,182,212,0.2); color: #67e8f9; border: 1px solid rgba(6,182,212,0.3); }
|
||||
.badge-siem { background: rgba(139,92,246,0.2); color: #ddd6fe; border: 1px solid rgba(139,92,246,0.3); }
|
||||
h1 { font-size: 1.875rem; font-weight: 800; margin-bottom: 8px; }
|
||||
.header p { color: #ddd6fe; font-size: 1rem; }
|
||||
.container { max-width: 960px; margin: 0 auto; padding: 40px 24px; }
|
||||
h2 { font-size: 1.25rem; font-weight: 700; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 2px solid var(--gray-200); }
|
||||
.section { margin-bottom: 48px; }
|
||||
p { color: var(--gray-500); font-size: 0.9rem; line-height: 1.7; margin-bottom: 12px; }
|
||||
pre { background: #1e293b; color: #e2e8f0; padding: 20px; border-radius: var(--radius); font-family: var(--mono); font-size: 0.8125rem; overflow-x: auto; line-height: 1.7; margin: 12px 0; }
|
||||
code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-family: var(--mono); font-size: 0.85em; }
|
||||
.siem-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; margin-top: 16px; }
|
||||
.siem-card { background: #fff; border: 1px solid var(--gray-200); border-radius: var(--radius); padding: 16px; }
|
||||
.siem-card h4 { font-size: 0.875rem; font-weight: 700; margin-bottom: 6px; }
|
||||
.siem-card p { font-size: 0.8rem; color: var(--gray-500); margin: 0; }
|
||||
.info-box { padding: 14px 16px; border-radius: var(--radius); margin: 16px 0; font-size: 0.875rem; }
|
||||
.info-purple { background: #f5f3ff; border-left: 3px solid var(--purple); color: #4c1d95; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-badges">
|
||||
<span class="badge badge-siem">SIEM / SOC</span>
|
||||
<span>×</span>
|
||||
<span class="badge badge-nis2">NIS2 Agile</span>
|
||||
</div>
|
||||
<h1>Integrazione SIEM / SOC ← NIS2 Agile</h1>
|
||||
<p>Ricevi eventi NIS2 in tempo reale nel tuo SIEM. Incidenti Art.23, rischi HIGH/CRITICAL, variazioni compliance come alert automatici.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="section">
|
||||
<h2>SIEM supportati</h2>
|
||||
<div class="siem-grid">
|
||||
<div class="siem-card"><h4>Splunk Enterprise</h4><p>HTTP Event Collector (HEC) + custom TA per NIS2 Agile. Dashboard Art.23 precompilata.</p></div>
|
||||
<div class="siem-card"><h4>Elastic SIEM</h4><p>Logstash HTTP input + index template NIS2. Alert rule per incident.significant.</p></div>
|
||||
<div class="siem-card"><h4>IBM QRadar</h4><p>Universal DSM + webhook-to-syslog bridge. Evento NIS2 → offense QRadar.</p></div>
|
||||
<div class="siem-card"><h4>Microsoft Sentinel</h4><p>Logic App webhook receiver → Azure Monitor. Playbook SOAR per Art.23.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Configurazione Webhook NIS2 → SIEM</h2>
|
||||
<p>Configura in NIS2 Agile → Settings → Webhook l'endpoint del tuo SIEM:</p>
|
||||
<pre><span style="color:#7dd3fc;"># SIEM HEC / HTTP Input endpoint</span>
|
||||
URL: https://your-siem.example.com:8088/services/collector <span style="color:#7dd3fc;"># Splunk HEC</span>
|
||||
URL: https://your-elk.example.com:9200/_ingest/pipeline/nis2 <span style="color:#7dd3fc;"># Elastic</span>
|
||||
Events: incident.created, incident.significant, incident.deadline_warning,
|
||||
risk.high_created, compliance.score_changed</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Splunk HEC — Bridge PHP</h2>
|
||||
<p>Se il SIEM non supporta nativamente la firma HMAC-SHA256, usa questo bridge come proxy tra NIS2 e Splunk HEC:</p>
|
||||
<pre><span style="color:#7dd3fc;">// nis2_to_splunk_bridge.php — Deploy su server intermedio</span>
|
||||
|
||||
<span style="color:#f1fa8c;">$nis2Secret</span> = getenv(<span style="color:#86efac;">'NIS2_WEBHOOK_SECRET'</span>);
|
||||
<span style="color:#f1fa8c;">$splunkHec</span> = getenv(<span style="color:#86efac;">'SPLUNK_HEC_URL'</span>); <span style="color:#7dd3fc;">// https://splunk:8088/services/collector</span>
|
||||
<span style="color:#f1fa8c;">$splunkToken</span> = getenv(<span style="color:#86efac;">'SPLUNK_HEC_TOKEN'</span>);
|
||||
|
||||
<span style="color:#7dd3fc;">// 1. Verifica firma NIS2</span>
|
||||
<span style="color:#f1fa8c;">$body</span> = file_get_contents(<span style="color:#86efac;">'php://input'</span>);
|
||||
<span style="color:#f1fa8c;">$sig</span> = $_SERVER[<span style="color:#86efac;">'HTTP_X_NIS2_SIGNATURE'</span>] ?? <span style="color:#86efac;">''</span>;
|
||||
if (!hash_equals(<span style="color:#86efac;">'sha256='</span> . hash_hmac(<span style="color:#86efac;">'sha256'</span>, <span style="color:#f1fa8c;">$body</span>, <span style="color:#f1fa8c;">$nis2Secret</span>), <span style="color:#f1fa8c;">$sig</span>)) {
|
||||
http_response_code(401); exit;
|
||||
}
|
||||
|
||||
<span style="color:#f1fa8c;">$payload</span> = json_decode(<span style="color:#f1fa8c;">$body</span>, true);
|
||||
|
||||
<span style="color:#7dd3fc;">// 2. Trasforma in formato Splunk HEC</span>
|
||||
<span style="color:#f1fa8c;">$splunkEvent</span> = [
|
||||
<span style="color:#86efac;">'time'</span> => <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'created'</span>],
|
||||
<span style="color:#86efac;">'host'</span> => <span style="color:#86efac;">'nis2-agile'</span>,
|
||||
<span style="color:#86efac;">'source'</span> => <span style="color:#86efac;">'nis2-agile-webhook'</span>,
|
||||
<span style="color:#86efac;">'sourcetype'</span> => <span style="color:#86efac;">'nis2:event'</span>,
|
||||
<span style="color:#86efac;">'index'</span> => <span style="color:#86efac;">'nis2_compliance'</span>,
|
||||
<span style="color:#86efac;">'event'</span> => [
|
||||
<span style="color:#86efac;">'event_type'</span> => <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'event'</span>],
|
||||
<span style="color:#86efac;">'event_id'</span> => <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'id'</span>],
|
||||
<span style="color:#86efac;">'org_id'</span> => <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'org_id'</span>],
|
||||
<span style="color:#86efac;">'data'</span> => <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'data'</span>],
|
||||
<span style="color:#86efac;">'api_version'</span> => <span style="color:#f1fa8c;">$payload</span>[<span style="color:#86efac;">'api_version'</span>],
|
||||
],
|
||||
];
|
||||
|
||||
<span style="color:#7dd3fc;">// 3. Forward a Splunk HEC</span>
|
||||
<span style="color:#f1fa8c;">$ch</span> = curl_init(<span style="color:#f1fa8c;">$splunkHec</span>);
|
||||
curl_setopt_array(<span style="color:#f1fa8c;">$ch</span>, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => json_encode(<span style="color:#f1fa8c;">$splunkEvent</span>),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
<span style="color:#86efac;">'Authorization: Splunk '</span> . <span style="color:#f1fa8c;">$splunkToken</span>,
|
||||
<span style="color:#86efac;">'Content-Type: application/json'</span>,
|
||||
],
|
||||
]);
|
||||
<span style="color:#f1fa8c;">$result</span> = curl_exec(<span style="color:#f1fa8c;">$ch</span>);
|
||||
curl_close(<span style="color:#f1fa8c;">$ch</span>);
|
||||
http_response_code(200);</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Elastic — Logstash Pipeline</h2>
|
||||
<pre><span style="color:#7dd3fc;"># logstash/pipelines/nis2.conf</span>
|
||||
input {
|
||||
http {
|
||||
port => 8181
|
||||
codec => json
|
||||
<span style="color:#7dd3fc;"># Aggiungi filtro HMAC-SHA256 con plugin custom</span>
|
||||
}
|
||||
}
|
||||
filter {
|
||||
mutate {
|
||||
add_field => { "[@metadata][index]" => "nis2-compliance-%{+YYYY.MM}" }
|
||||
}
|
||||
date {
|
||||
match => [ "[created]", "UNIX" ]
|
||||
target => "@timestamp"
|
||||
}
|
||||
}
|
||||
output {
|
||||
elasticsearch {
|
||||
hosts => ["https://elasticsearch:9200"]
|
||||
index => "%{[@metadata][index]}"
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Microsoft Sentinel — Logic App</h2>
|
||||
<pre><span style="color:#7dd3fc;">// Azure Logic App — trigger HTTP + azione Send to Log Analytics</span>
|
||||
{
|
||||
"triggers": {
|
||||
"When_a_HTTP_request_is_received": {
|
||||
"type": "Request",
|
||||
"kind": "Http",
|
||||
"inputs": { "schema": {} }
|
||||
}
|
||||
},
|
||||
"actions": {
|
||||
"Send_Data_to_Log_Analytics": {
|
||||
"type": "ApiConnection",
|
||||
"inputs": {
|
||||
"body": "@{triggerBody()}",
|
||||
"headers": { "Log-Type": "NIS2AgileEvent" },
|
||||
"host": { "connection": { "name": "@parameters('$connections')['azureloganalyticsdatacollector']" } },
|
||||
"method": "post",
|
||||
"path": "/api/logs"
|
||||
}
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="info-box info-purple">
|
||||
<strong>Art.23 NIS2 + SIEM:</strong> Configurare alert SIEM su <code>incident.significant</code> e <code>incident.deadline_warning</code> permette di automatizzare il tracking delle scadenze 24h/72h/30d direttamente nel SOC. Il team può così gestire incidenti NIS2 senza uscire dal workflow SIEM.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>SustainAI × NIS2 Agile — Integrazione</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--primary: #06b6d4; --green: #10b981; --gray-200: #e2e8f0;
|
||||
--gray-500: #64748b; --gray-700: #334155; --gray-900: #0f172a;
|
||||
--radius: 8px; --font: -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;
|
||||
--mono: 'Cascadia Code','Consolas',monospace;
|
||||
}
|
||||
body { background: #f8fafc; font-family: var(--font); color: var(--gray-900); }
|
||||
.header { background: linear-gradient(135deg, #064e3b, #065f46); padding: 40px 48px; color: #fff; }
|
||||
.header-badges { display: flex; gap: 10px; margin-bottom: 16px; }
|
||||
.badge { padding: 4px 12px; border-radius: 20px; font-size: 0.75rem; font-weight: 700; }
|
||||
.badge-nis2 { background: rgba(6,182,212,0.2); color: #67e8f9; border: 1px solid rgba(6,182,212,0.3); }
|
||||
.badge-sus { background: rgba(16,185,129,0.2); color: #6ee7b7; border: 1px solid rgba(16,185,129,0.3); }
|
||||
h1 { font-size: 1.875rem; font-weight: 800; margin-bottom: 8px; }
|
||||
.header p { color: #a7f3d0; font-size: 1rem; }
|
||||
.container { max-width: 960px; margin: 0 auto; padding: 40px 24px; }
|
||||
h2 { font-size: 1.25rem; font-weight: 700; margin-bottom: 12px; padding-bottom: 10px; border-bottom: 2px solid var(--gray-200); }
|
||||
.section { margin-bottom: 48px; }
|
||||
p { color: var(--gray-500); font-size: 0.9rem; line-height: 1.7; margin-bottom: 12px; }
|
||||
pre { background: #1e293b; color: #e2e8f0; padding: 20px; border-radius: var(--radius); font-family: var(--mono); font-size: 0.8125rem; overflow-x: auto; line-height: 1.7; margin: 12px 0; }
|
||||
code { background: #f1f5f9; padding: 2px 6px; border-radius: 4px; font-family: var(--mono); font-size: 0.85em; color: var(--gray-700); }
|
||||
.step { display: flex; gap: 16px; margin-bottom: 20px; padding: 18px; background: #fff; border: 1px solid var(--gray-200); border-radius: var(--radius); }
|
||||
.step-num { width: 30px; height: 30px; border-radius: 50%; background: var(--green); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.875rem; flex-shrink: 0; }
|
||||
.info-box { padding: 14px 16px; border-radius: var(--radius); margin: 16px 0; font-size: 0.875rem; }
|
||||
.info-green { background: #f0fdf4; border-left: 3px solid var(--green); color: #065f46; }
|
||||
.mapping-table { width: 100%; border-collapse: collapse; }
|
||||
.mapping-table th { padding: 10px 14px; background: #f1f5f9; font-size: 0.8rem; text-align: left; font-weight: 700; }
|
||||
.mapping-table td { padding: 10px 14px; border-bottom: 1px solid var(--gray-200); font-size: 0.8375rem; }
|
||||
.esg-badge { padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 700; }
|
||||
.esg-g { background: #e0f2fe; color: #075985; }
|
||||
.esg-e { background: #dcfce7; color: #166534; }
|
||||
.esg-s { background: #faf5ff; color: #6b21a8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div class="header-badges">
|
||||
<span class="badge badge-sus">SustainAI</span>
|
||||
<span>×</span>
|
||||
<span class="badge badge-nis2">NIS2 Agile</span>
|
||||
</div>
|
||||
<h1>Integrazione SustainAI ← NIS2 Agile</h1>
|
||||
<p>Alimenta l'area Governance (G) e Sociale (S) dei report ESG/sostenibilità con i dati di compliance cybersecurity NIS2. La governance della sicurezza informatica è un KPI ESG rilevante (GRI 418, SASB, CSRD).</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="section">
|
||||
<h2>Mappatura NIS2 → ESG</h2>
|
||||
<p>I dati NIS2 Agile si mappano naturalmente ai framework ESG più diffusi:</p>
|
||||
<table class="mapping-table">
|
||||
<thead><tr><th>Dato NIS2 Agile</th><th>Endpoint</th><th>Pilastro ESG</th><th>Framework</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Compliance score Art.21</td><td><code>/services/compliance-summary</code></td><td><span class="esg-badge esg-g">G</span> Governance</td><td>GRI 205, CSRD</td></tr>
|
||||
<tr><td>Policy sicurezza approvate</td><td><code>/services/policies-approved</code></td><td><span class="esg-badge esg-g">G</span> Governance</td><td>ISO 27001, GRI 418</td></tr>
|
||||
<tr><td>Incidenti data breach / Art.23</td><td><code>/services/incidents-feed</code></td><td><span class="esg-badge esg-s">S</span> Sociale</td><td>GRI 418 (Privacy)</td></tr>
|
||||
<tr><td>Controlli di sicurezza implementati</td><td><code>/services/controls-status</code></td><td><span class="esg-badge esg-g">G</span> Governance</td><td>SASB</td></tr>
|
||||
<tr><td>Rischio supply chain fornitori</td><td><code>/services/suppliers-risk</code></td><td><span class="esg-badge esg-e">E</span> Ambientale + <span class="esg-badge esg-g">G</span></td><td>GRI 308</td></tr>
|
||||
<tr><td>Segnalazioni whistleblowing</td><td><code>/api/whistleblowing/stats</code></td><td><span class="esg-badge esg-s">S</span> Sociale</td><td>GRI 205 (Anti-corruzione)</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Step 1 — API Key con scope minimi</h2>
|
||||
<p>Crea in NIS2 Agile una chiave con scope limitati per SustainAI:</p>
|
||||
<pre>Scope richiesti:
|
||||
read:compliance ← score e controlli Art.21
|
||||
read:incidents ← incidenti per KPI privacy/GDPR
|
||||
read:policies ← policy approvate (governance evidence)
|
||||
read:supply_chain ← rischio fornitori ESG</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Step 2 — Sync mensile per report ESG</h2>
|
||||
<pre><span style="color:#7dd3fc;">// SustainAI — sync_nis2_esg.php (cron mensile)</span>
|
||||
|
||||
<span style="color:#f1fa8c;">$apiKey</span> = getenv(<span style="color:#86efac;">'NIS2_API_KEY'</span>);
|
||||
<span style="color:#f1fa8c;">$orgId</span> = getenv(<span style="color:#86efac;">'NIS2_ORG_ID'</span>);
|
||||
<span style="color:#f1fa8c;">$base</span> = <span style="color:#86efac;">'https://nis2.certisource.it/api'</span>;
|
||||
|
||||
<span style="color:#f1fa8c;">$headers</span> = [
|
||||
<span style="color:#86efac;">'X-API-Key: '</span> . <span style="color:#f1fa8c;">$apiKey</span>,
|
||||
<span style="color:#86efac;">'X-Organization-Id: '</span> . <span style="color:#f1fa8c;">$orgId</span>,
|
||||
];
|
||||
|
||||
<span style="color:#7dd3fc;">// G — Compliance score (KPI governance cybersecurity)</span>
|
||||
<span style="color:#f1fa8c;">$compliance</span> = nis2Get(<span style="color:#f1fa8c;">$base</span> . <span style="color:#86efac;">'/services/compliance-summary'</span>, <span style="color:#f1fa8c;">$headers</span>);
|
||||
<span style="color:#f1fa8c;">$cyberScore</span> = <span style="color:#f1fa8c;">$compliance</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'overall_score'</span>] ?? 0;
|
||||
<span style="color:#f1fa8c;">$policyCount</span> = <span style="color:#f1fa8c;">$compliance</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'policies'</span>][<span style="color:#86efac;">'approved'</span>] ?? 0;
|
||||
|
||||
<span style="color:#7dd3fc;">// S — Privacy breaches (GRI 418)</span>
|
||||
<span style="color:#f1fa8c;">$incidents</span> = nis2Get(<span style="color:#f1fa8c;">$base</span> . <span style="color:#86efac;">'/services/incidents-feed?significant_only=1'</span>, <span style="color:#f1fa8c;">$headers</span>);
|
||||
<span style="color:#f1fa8c;">$breaches</span> = array_filter(
|
||||
<span style="color:#f1fa8c;">$incidents</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'incidents'</span>] ?? [],
|
||||
fn(<span style="color:#f1fa8c;">$i</span>) => <span style="color:#f1fa8c;">$i</span>[<span style="color:#86efac;">'classification'</span>] === <span style="color:#86efac;">'data_breach'</span>
|
||||
);
|
||||
|
||||
<span style="color:#7dd3fc;">// G — Supply chain risk (ESG fornitori)</span>
|
||||
<span style="color:#f1fa8c;">$suppliers</span> = nis2Get(<span style="color:#f1fa8c;">$base</span> . <span style="color:#86efac;">'/services/suppliers-risk'</span>, <span style="color:#f1fa8c;">$headers</span>);
|
||||
<span style="color:#f1fa8c;">$highRiskSuppliers</span> = <span style="color:#f1fa8c;">$suppliers</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'stats'</span>][<span style="color:#86efac;">'high'</span>] +
|
||||
<span style="color:#f1fa8c;">$suppliers</span>[<span style="color:#86efac;">'data'</span>][<span style="color:#86efac;">'stats'</span>][<span style="color:#86efac;">'critical'</span>];
|
||||
|
||||
<span style="color:#7dd3fc;">// Aggiorna KPI ESG in SustainAI</span>
|
||||
EsgKpiService::updateCyberGovernance([
|
||||
<span style="color:#86efac;">'nis2_score'</span> => <span style="color:#f1fa8c;">$cyberScore</span>,
|
||||
<span style="color:#86efac;">'policies_approved'</span> => <span style="color:#f1fa8c;">$policyCount</span>,
|
||||
<span style="color:#86efac;">'data_breaches'</span> => count(<span style="color:#f1fa8c;">$breaches</span>),
|
||||
<span style="color:#86efac;">'high_risk_suppliers'</span> => <span style="color:#f1fa8c;">$highRiskSuppliers</span>,
|
||||
<span style="color:#86efac;">'period'</span> => date(<span style="color:#86efac;">'Y-m'</span>),
|
||||
]);</pre>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Widget NIS2 per Report ESG SustainAI</h2>
|
||||
<pre><!-- SustainAI: sezione Governance → Cybersecurity KPIs -->
|
||||
<div id="nis2-esg-widget" style="padding:20px; border:1px solid #e2e8f0; border-radius:8px; background:#f0fdf4;"></div>
|
||||
<script>
|
||||
fetch('https://nis2.certisource.it/api/services/compliance-summary', {
|
||||
headers: { 'X-API-Key': '<span style="color:#86efac;">nis2_YOUR_KEY</span>', 'X-Organization-Id': '<span style="color:#86efac;">ORG_ID</span>' }
|
||||
}).then(r => r.json()).then(({ data }) => {
|
||||
document.getElementById('nis2-esg-widget').innerHTML = `
|
||||
<h4 style="font-size:.875rem; font-weight:700; color:#065f46; margin-bottom:16px;">
|
||||
🔒 Governance Cybersecurity — NIS2 Compliance
|
||||
</h4>
|
||||
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:12px; text-align:center;">
|
||||
<div><div style="font-size:1.5rem; font-weight:800; color:#06b6d4;">${data.overall_score}%</div>
|
||||
<div style="font-size:.7rem; color:#64748b;">NIS2 Score</div></div>
|
||||
<div><div style="font-size:1.5rem; font-weight:800; color:#10b981;">${data.policies.approved}</div>
|
||||
<div style="font-size:.7rem; color:#64748b;">Policy Approvate</div></div>
|
||||
<div><div style="font-size:1.5rem; font-weight:800; color:#f59e0b;">${data.risks.high}</div>
|
||||
<div style="font-size:.7rem; color:#64748b;">Rischi HIGH</div></div>
|
||||
<div><div style="font-size:1.5rem; font-weight:800; color:#ef4444;">${data.incidents.significant}</div>
|
||||
<div style="font-size:.7rem; color:#64748b;">Incidenti Art.23</div></div>
|
||||
</div>
|
||||
<p style="font-size:.7rem; color:#94a3b8; margin-top:12px; text-align:right;">
|
||||
Fonte: NIS2 Agile — nis2.certisource.it — Aggiornato: ${new Date().toLocaleDateString('it')}
|
||||
</p>`;
|
||||
});
|
||||
</script></pre>
|
||||
</div>
|
||||
|
||||
<div class="info-box info-green">
|
||||
<strong>CSRD / ESRS E5:</strong> La cybersecurity è esplicitamente inclusa nelle ESRS come rischio materiale (ESRS 2 IRO-1). NIS2 Agile fornisce le evidenze documentali per il reporting CSRD sul governo dei rischi digitali.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user