nis2-agile/public/integrations/lg231.html
DevEnv nis2-agile 07c1a71685 [MIGRATE] Migrazione a nis2.agile.software
- Tutti i riferimenti nis2.certisource.it → nis2.agile.software
- Apache vhost HTTP nis2.agile.software attivo su Hetzner
- Script setup-nis2-agile-software.sh: certbot SSL + redirect da vecchio dominio
- .env server: APP_URL aggiornato a https://nis2.agile.software
- CLAUDE.md, docs commerciali, integrazioni, API docs aggiornati

DNS da aggiungere in Cloudflare: nis2.agile.software A 135.181.149.254 (proxy OFF)
Poi eseguire: bash /opt/devenv/scripts/setup-nis2-agile-software.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 14:07:10 +01:00

226 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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.agile.software/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.agile.software</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;">&lt;!-- Widget NIS2 per 231 Agile dashboard --&gt;</span>
&lt;div id="nis2-widget"&gt;&lt;/div&gt;
&lt;script&gt;
(async () => {
const r = await fetch('https://nis2.agile.software/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 = `
&lt;div style="padding:20px; border:1px solid #e2e8f0; border-radius:8px;"&gt;
&lt;h4 style="font-size:.875rem; font-weight:700; margin-bottom:12px;"&gt;
🔒 NIS2 Compliance Score
&lt;/h4&gt;
&lt;div style="font-size:2rem; font-weight:800; color:#06b6d4;"&gt;
${data.overall_score}%
&lt;/div&gt;
&lt;div style="font-size:.75rem; color:#64748b;"&gt;${data.label}&lt;/div&gt;
&lt;div style="margin-top:12px; display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px;"&gt;
&lt;div&gt;&lt;strong&gt;${data.incidents.open}&lt;/strong&gt;&lt;br&gt;&lt;small&gt;Incidenti&lt;/small&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;${data.risks.high}&lt;/strong&gt;&lt;br&gt;&lt;small&gt;Rischi HIGH&lt;/small&gt;&lt;/div&gt;
&lt;div&gt;&lt;strong&gt;${data.policies.approved}&lt;/strong&gt;&lt;br&gt;&lt;small&gt;Policy&lt;/small&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;`;
})();
&lt;/script&gt;</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>