[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:
DevEnv nis2-agile
2026-03-07 13:20:24 +01:00
co-authored by Claude Sonnet 4.6
parent 3f4b451e2a
commit 86e9bdded2
22 changed files with 5080 additions and 10 deletions
+179
View File
@@ -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>