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>
226 lines
16 KiB
HTML
226 lines
16 KiB
HTML
<!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>
|