[INTEG] Pagina integrazioni esterne + spec lg231↔NIS2
- public/integrazioniext.html: pagina pubblica con 4 tab (Services API, Guida lg231, Webhook, Quick Start) — link in sidebar - docs/integration/lg231-nis2-integration.md: spec tecnica completa per agente Claude lg231 (provider-config, Nis2Client, widget, escalation OdV) - common.js: voce sidebar → integrazioniext.html Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3321509d02
commit
5ecdce7d47
328
docs/integration/lg231-nis2-integration.md
Normal file
328
docs/integration/lg231-nis2-integration.md
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
# Integrazione lg231 Agile ↔ NIS2 Agile
|
||||||
|
|
||||||
|
> **Documento per l'agente Claude di lg231.**
|
||||||
|
> Leggi questo file prima di implementare qualsiasi integrazione con NIS2 Agile.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Perché integrare
|
||||||
|
|
||||||
|
lg231 gestisce compliance D.Lgs. 231/2001 (reati societari, OdV, MOG).
|
||||||
|
NIS2 Agile gestisce compliance Direttiva NIS2 (cybersecurity, incidenti, rischi IT).
|
||||||
|
|
||||||
|
I due mondi si sovrappongono su tre punti critici:
|
||||||
|
|
||||||
|
| Evento NIS2 | Rilevanza 231 |
|
||||||
|
|---|---|
|
||||||
|
| Incidente cybersecurity (Art.23 NIS2) | Reato presupposto 231 art.24-bis (criminalità informatica) |
|
||||||
|
| Fornitore IT ad alto rischio | Rischio 231: complicità in reati informatici via supply chain |
|
||||||
|
| Policy sicurezza non approvata | Gap nel MOG (Modello Organizzativo e di Gestione) |
|
||||||
|
| Score compliance NIS2 basso | Indicatore per l'OdV di carenza nei controlli |
|
||||||
|
| Whistleblowing sicurezza | Potenziale escalation verso OdV 231 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architettura dell'integrazione
|
||||||
|
|
||||||
|
```
|
||||||
|
lg231 Agile NIS2 Agile
|
||||||
|
──────────────────────────── ────────────────────────────
|
||||||
|
company-ms ServicesController.php
|
||||||
|
companies.metadata JSON ───► /api/services/* (REST, X-API-Key)
|
||||||
|
+ nis2_api_key /api/webhooks/* (webhook outbound)
|
||||||
|
+ nis2_org_id ◄─── WebhookController.php
|
||||||
|
+ nis2_enabled (bool) webhook → POST lg231-endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cosa fa lg231 (lato mittente/ricevente)
|
||||||
|
|
||||||
|
### 1. Aggiungere NIS2 al provider-config (company-ms)
|
||||||
|
|
||||||
|
**File**: `services/company-ms/public/index.php`
|
||||||
|
|
||||||
|
Nel metodo `PATCH /companies/{id}/provider-config`, aggiungere ai `$allowed`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$allowed = [
|
||||||
|
'certisource_pat',
|
||||||
|
'certisource_pat_expires_at',
|
||||||
|
'anthropic_api_key',
|
||||||
|
'openai_api_key',
|
||||||
|
// ── Nuovo provider NIS2 ──
|
||||||
|
'nis2_api_key', // chiave API generata in NIS2 Agile → Settings → API Keys
|
||||||
|
'nis2_org_id', // ID organizzazione NIS2 (intero, visibile in NIS2 dashboard URL)
|
||||||
|
'nis2_enabled', // bool: abilitare pull automatico dati NIS2
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Nel metodo `GET /companies/{id}/provider-config`, aggiungere alla response:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$nis2Key = $meta['nis2_api_key'] ?? null;
|
||||||
|
$nis2OrgId = $meta['nis2_org_id'] ?? null;
|
||||||
|
$nis2Enabled = (bool) ($meta['nis2_enabled'] ?? false);
|
||||||
|
|
||||||
|
// nella response array:
|
||||||
|
'nis2_configured' => !empty($nis2Key) && !empty($nis2OrgId),
|
||||||
|
'nis2_api_key' => $nis2Key, // uso interno microservizi
|
||||||
|
'nis2_org_id' => $nis2OrgId,
|
||||||
|
'nis2_enabled' => $nis2Enabled,
|
||||||
|
'nis2_key_hint' => !empty($nis2Key) ? 'nis2_...' . substr($nis2Key, -4) : null,
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Nuovo servizio NIS2Client (shared-lib o risk-ms)
|
||||||
|
|
||||||
|
Creare `shared/nis2-lib/src/Nis2Client.php`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
class Nis2Client
|
||||||
|
{
|
||||||
|
const BASE_URL = 'https://nis2.agile.software/api/services';
|
||||||
|
|
||||||
|
public static function get(
|
||||||
|
string $endpoint,
|
||||||
|
string $apiKey,
|
||||||
|
array $query = [],
|
||||||
|
int $timeout = 10
|
||||||
|
): array {
|
||||||
|
$url = self::BASE_URL . $endpoint;
|
||||||
|
if ($query) $url .= '?' . http_build_query($query);
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => $timeout,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
'X-API-Key: ' . $apiKey,
|
||||||
|
'Accept: application/json',
|
||||||
|
'X-Caller: lg231-agile/1.0',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = curl_exec($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($code !== 200 || !$body) {
|
||||||
|
return ['success' => false, 'http_code' => $code, 'data' => null];
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = json_decode($body, true);
|
||||||
|
return ['success' => true, 'http_code' => 200, 'data' => $decoded];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API NIS2 disponibili
|
||||||
|
|
||||||
|
**Base URL**: `https://nis2.agile.software/api/services`
|
||||||
|
**Auth**: Header `X-API-Key: {nis2_api_key}` (la chiave è legata a una singola org NIS2)
|
||||||
|
|
||||||
|
### Endpoint GET disponibili
|
||||||
|
|
||||||
|
| Endpoint | Scope richiesto | Cosa restituisce |
|
||||||
|
|---|---|---|
|
||||||
|
| `/status` | — (no auth) | Stato piattaforma, versione |
|
||||||
|
| `/compliance-summary` | `read:compliance` | Score NIS2 (0-100) per dominio Art.21, rischi aperti, incidenti |
|
||||||
|
| `/risks/feed` | `read:risks` | Lista rischi: id, title, level (low/medium/high/critical), status, area |
|
||||||
|
| `/incidents/feed` | `read:incidents` | Incidenti: id, title, severity, status, notified_csirt, deadline_72h |
|
||||||
|
| `/controls/status` | `read:compliance` | Stato controlli Art.21 per dominio (implemented/partial/missing) |
|
||||||
|
| `/assets/critical` | `read:assets` | Asset critici con tipo, criticità, dipendenze |
|
||||||
|
| `/suppliers/risk` | `read:suppliers` | Fornitori: nome, risk_level, ultimo_assessment, is_flagged |
|
||||||
|
| `/policies/approved` | `read:policies` | Policy approvate: titolo, area, data approvazione, versione |
|
||||||
|
|
||||||
|
**Header risposta sempre presenti**:
|
||||||
|
- `X-NIS2-API-Version: 1.0`
|
||||||
|
- `X-NIS2-Org-Id: {org_id}`
|
||||||
|
- `X-RateLimit-Limit: 100`
|
||||||
|
- `X-RateLimit-Remaining: N`
|
||||||
|
|
||||||
|
**Rate limit**: 100 req/ora per API key.
|
||||||
|
|
||||||
|
### Esempio risposta `/compliance-summary`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"organization": {
|
||||||
|
"id": 5,
|
||||||
|
"name": "Azienda XYZ S.r.l.",
|
||||||
|
"sector": "energia",
|
||||||
|
"nis2_entity_type": "essential"
|
||||||
|
},
|
||||||
|
"compliance": {
|
||||||
|
"overall_score": 73,
|
||||||
|
"label": "Parziale",
|
||||||
|
"assessment_date": "2026-03-07T10:00:00+01:00",
|
||||||
|
"domain_scores": [
|
||||||
|
{ "domain": "Gestione del rischio", "score": 80, "status": "compliant" },
|
||||||
|
{ "domain": "Sicurezza supply chain", "score": 45, "status": "partial" },
|
||||||
|
{ "domain": "Gestione incidenti", "score": 90, "status": "compliant" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"risks": {
|
||||||
|
"total": 12, "open": 8, "high_critical": 3, "mitigated": 4
|
||||||
|
},
|
||||||
|
"incidents": {
|
||||||
|
"total": 2, "open": 1, "significant": 1, "overdue": 0
|
||||||
|
},
|
||||||
|
"generated_at": "2026-03-07T13:30:00+01:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Esempio risposta `/incidents/feed`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"incidents": [
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "Ransomware server produzione",
|
||||||
|
"severity": "critical",
|
||||||
|
"status": "open",
|
||||||
|
"is_significant": true,
|
||||||
|
"notified_csirt": true,
|
||||||
|
"deadline_72h": "2026-03-09T08:00:00+01:00",
|
||||||
|
"overdue": false,
|
||||||
|
"created_at": "2026-03-07T08:00:00+01:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 1,
|
||||||
|
"filters_applied": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Come creare una API Key in NIS2
|
||||||
|
|
||||||
|
1. Login su https://nis2.agile.software con l'account dell'azienda
|
||||||
|
2. Vai su **Settings → API Keys**
|
||||||
|
3. Click **"Nuova API Key"** → nome: "lg231 Integration" → scope: `read:all`
|
||||||
|
4. Copia la chiave (mostrata una sola volta, formato: `nis2_XXXX...`)
|
||||||
|
5. In lg231: PATCH `/companies/{id}/provider-config` con `{ "nis2_api_key": "nis2_...", "nis2_org_id": 5 }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cosa mostrare in lg231
|
||||||
|
|
||||||
|
### Widget "Cybersecurity NIS2" nella company view
|
||||||
|
|
||||||
|
Chiamare `GET /compliance-summary` e mostrare:
|
||||||
|
- Gauge score 0-100 con colori (verde ≥70, arancione 40-69, rosso <40)
|
||||||
|
- Badge "Essential" / "Important" / "Volontario"
|
||||||
|
- Numero rischi high/critical aperti
|
||||||
|
- Numero incidenti aperti (con alert se `overdue: true`)
|
||||||
|
- Link "Apri NIS2 Agile" → `https://nis2.agile.software/dashboard.html`
|
||||||
|
|
||||||
|
### Integrazione Risk Register (risk-ms)
|
||||||
|
|
||||||
|
Quando lg231 crea un'analisi dei rischi per l'azienda, importare i rischi cyber NIS2:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Nel risk-ms, durante la creazione di un nuovo risk assessment:
|
||||||
|
$nis2Risks = Nis2Client::get('/risks/feed', $apiKey, ['level' => 'high,critical']);
|
||||||
|
foreach (($nis2Risks['data']['risks'] ?? []) as $r) {
|
||||||
|
// Suggerisci all'utente di includerlo nel registro 231
|
||||||
|
// Tipo: "Rischio cyber (da NIS2 Agile): " . $r['title']
|
||||||
|
// Categoria 231: art.24-bis (criminalità informatica)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Escalation Incidente → OdV (monitoring-ms)
|
||||||
|
|
||||||
|
Quando NIS2 segnala un incidente significativo (`is_significant: true`), creare attività OdV:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$incidents = Nis2Client::get('/incidents/feed', $apiKey, ['significant' => 1, 'status' => 'open']);
|
||||||
|
foreach (($incidents['data']['incidents'] ?? []) as $inc) {
|
||||||
|
// Crea odv_activity in monitoring-ms:
|
||||||
|
// type: 'cyber_incident_notifica'
|
||||||
|
// description: 'Incidente NIS2 significativo: ' . $inc['title']
|
||||||
|
// priority: 'high'
|
||||||
|
// source: 'nis2_agile'
|
||||||
|
// reference_id: $inc['id']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Webhook NIS2 → lg231 (facoltativo, avanzato)
|
||||||
|
|
||||||
|
NIS2 può inviare notifiche push su eventi. lg231 deve esporre un endpoint ricevente.
|
||||||
|
|
||||||
|
**Endpoint lg231 da creare**: `POST /api/integrations/nis2-webhook`
|
||||||
|
**Firma**: header `X-NIS2-Signature: sha256=HMAC_HEX`
|
||||||
|
|
||||||
|
Verificare la firma:
|
||||||
|
```php
|
||||||
|
$secret = $company['metadata']['nis2_webhook_secret'];
|
||||||
|
$body = file_get_contents('php://input');
|
||||||
|
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
|
||||||
|
$received = $_SERVER['HTTP_X_NIS2_SIGNATURE'] ?? '';
|
||||||
|
if (!hash_equals($expected, $received)) { return 401; }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Eventi ricevuti**:
|
||||||
|
- `incident.created` → crea alert in monitoring-ms
|
||||||
|
- `incident.deadline_warning` → notifica urgente OdV
|
||||||
|
- `risk.created_high` → aggiunge al risk register 231
|
||||||
|
- `compliance.score_changed` → aggiorna widget score
|
||||||
|
- `supplier.risk_flagged` → flagga fornitore in supply chain 231
|
||||||
|
|
||||||
|
**Registrare la subscription in NIS2**:
|
||||||
|
```
|
||||||
|
POST https://nis2.agile.software/api/webhooks/subscriptions
|
||||||
|
Authorization: Bearer {jwt}
|
||||||
|
{
|
||||||
|
"url": "https://lg231.agile.software/api/integrations/nis2-webhook",
|
||||||
|
"events": ["incident.created", "incident.deadline_warning", "risk.created_high"],
|
||||||
|
"description": "lg231 Agile integration"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Risposta: `{ "secret": "whsec_...", "id": 1 }` → salvare `secret` in `companies.metadata.nis2_webhook_secret`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compatibilità tenant
|
||||||
|
|
||||||
|
NIS2 usa `organization_id` (singolo DB).
|
||||||
|
lg231 usa `company_id` + `tenant_id` (multi-tenant).
|
||||||
|
|
||||||
|
La chiave API NIS2 è **già legata a una specifica organizzazione**: non serve passare `org_id` negli header.
|
||||||
|
Salvare `nis2_org_id` in lg231 è opzionale (solo per riferimento UI).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist implementazione lg231
|
||||||
|
|
||||||
|
- [ ] `company-ms`: aggiungere `nis2_api_key`, `nis2_org_id`, `nis2_enabled` a `provider-config`
|
||||||
|
- [ ] `shared/nis2-lib/src/Nis2Client.php`: client HTTP leggero
|
||||||
|
- [ ] Widget NIS2 nella company detail view (HTML + JS)
|
||||||
|
- [ ] `risk-ms`: import rischi cyber durante assessment
|
||||||
|
- [ ] `monitoring-ms`: escalation incidenti significativi → OdV activity
|
||||||
|
- [ ] (opzionale) endpoint webhook ricevente + subscription NIS2
|
||||||
|
- [ ] Test: `GET /api/services/status` deve rispondere 200 senza auth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note tecniche NIS2
|
||||||
|
|
||||||
|
- **Modello**: PHP 8.4, Front Controller, no framework
|
||||||
|
- **Auth**: API Key sha256-hashed in DB (`api_keys` table)
|
||||||
|
- **Scopes**: `read:all`, `read:compliance`, `read:risks`, `read:incidents`, `read:assets`, `read:policies`
|
||||||
|
- **Tabella**: `api_keys (id, organization_id, name, key_prefix, key_hash, scopes JSON, is_active, expires_at, last_used_at)`
|
||||||
|
- **Rate limit**: 100 req/h per chiave, file-based in `/tmp/nis2_ratelimit/`
|
||||||
|
- **CORS**: origine specifica (no wildcard) — lg231 deve fare chiamate server-to-server, NON da browser
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Generato: 2026-03-07 | NIS2 Agile v1.0 | Contatto: cristiano.benassati@gmail.com*
|
||||||
552
public/integrazioniext.html
Normal file
552
public/integrazioniext.html
Normal file
@ -0,0 +1,552 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>NIS2 Agile — Integrazioni Esterne</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<style>
|
||||||
|
/* ── Page-specific overrides ── */
|
||||||
|
.integ-hero {
|
||||||
|
background: linear-gradient(135deg, rgba(6,182,212,.08) 0%, rgba(99,102,241,.06) 100%);
|
||||||
|
border: 1px solid rgba(6,182,212,.2);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.integ-hero h1 { font-size: 1.6rem; color: var(--primary); margin-bottom: .5rem; }
|
||||||
|
.integ-hero p { color: var(--text-secondary); max-width: 680px; line-height: 1.6; }
|
||||||
|
|
||||||
|
.provider-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 1.25rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.provider-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
transition: border-color .2s, transform .2s;
|
||||||
|
}
|
||||||
|
.provider-card:hover { border-color: var(--primary); transform: translateY(-2px); }
|
||||||
|
.provider-card .badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: .65rem; font-weight: 700;
|
||||||
|
padding: .2rem .5rem; border-radius: 4px;
|
||||||
|
margin-bottom: .75rem;
|
||||||
|
}
|
||||||
|
.badge-available { background: rgba(34,197,94,.15); color: #22c55e; }
|
||||||
|
.badge-planned { background: rgba(234,179,8,.15); color: #eab308; }
|
||||||
|
.provider-card h3 { font-size: .95rem; margin-bottom: .4rem; }
|
||||||
|
.provider-card p { font-size: .82rem; color: var(--text-secondary); line-height: 1.5; }
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 1.1rem; font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 2rem 0 1rem;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-table { width: 100%; border-collapse: collapse; font-size: .83rem; margin-bottom: 1.5rem; }
|
||||||
|
.api-table th {
|
||||||
|
background: rgba(255,255,255,.04);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-weight: 600; text-align: left;
|
||||||
|
padding: .6rem .75rem;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.api-table td {
|
||||||
|
padding: .55rem .75rem;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,.04);
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.api-table tr:hover td { background: rgba(255,255,255,.02); }
|
||||||
|
.api-table code {
|
||||||
|
background: rgba(6,182,212,.1);
|
||||||
|
color: var(--primary);
|
||||||
|
padding: .15rem .4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: .78rem;
|
||||||
|
}
|
||||||
|
.method-get {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(34,197,94,.15); color: #22c55e;
|
||||||
|
font-size: .65rem; font-weight: 700;
|
||||||
|
padding: .15rem .4rem; border-radius: 3px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.method-post {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(249,115,22,.15); color: #f97316;
|
||||||
|
font-size: .65rem; font-weight: 700;
|
||||||
|
padding: .15rem .4rem; border-radius: 3px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-block {
|
||||||
|
background: #0f172a;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 1.25rem;
|
||||||
|
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
||||||
|
font-size: .78rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #cbd5e1;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: .75rem 0 1.25rem;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
.code-block .kw { color: #818cf8; }
|
||||||
|
.code-block .str { color: #86efac; }
|
||||||
|
.code-block .key { color: #67e8f9; }
|
||||||
|
.code-block .cmt { color: #475569; }
|
||||||
|
|
||||||
|
.steps-list {
|
||||||
|
list-style: none;
|
||||||
|
counter-reset: steps;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.steps-list li {
|
||||||
|
counter-increment: steps;
|
||||||
|
display: flex; align-items: flex-start; gap: .75rem;
|
||||||
|
padding: .65rem 0;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,.04);
|
||||||
|
font-size: .85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.steps-list li::before {
|
||||||
|
content: counter(steps);
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 24px; height: 24px;
|
||||||
|
background: rgba(6,182,212,.15);
|
||||||
|
color: var(--primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: .75rem; font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checklist { list-style: none; margin-bottom: 1.5rem; }
|
||||||
|
.checklist li {
|
||||||
|
display: flex; align-items: flex-start; gap: .6rem;
|
||||||
|
padding: .4rem 0;
|
||||||
|
font-size: .84rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
.checklist li::before {
|
||||||
|
content: '□';
|
||||||
|
color: var(--primary);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.callout {
|
||||||
|
border-left: 3px solid var(--primary);
|
||||||
|
background: rgba(6,182,212,.05);
|
||||||
|
padding: .75rem 1rem;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
font-size: .83rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.callout strong { color: var(--primary); }
|
||||||
|
|
||||||
|
.event-list { display: flex; flex-direction: column; gap: .5rem; margin-bottom: 1.5rem; }
|
||||||
|
.event-row {
|
||||||
|
display: flex; align-items: center; gap: .75rem;
|
||||||
|
background: rgba(255,255,255,.03);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: .6rem .9rem;
|
||||||
|
font-size: .82rem;
|
||||||
|
}
|
||||||
|
.event-name {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #f97316;
|
||||||
|
font-size: .78rem;
|
||||||
|
min-width: 230px;
|
||||||
|
}
|
||||||
|
.event-desc { color: var(--text-secondary); }
|
||||||
|
.event-lg231 {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: .7rem;
|
||||||
|
background: rgba(99,102,241,.12);
|
||||||
|
color: #818cf8;
|
||||||
|
padding: .15rem .5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-nav { display: flex; gap: 0; border-bottom: 1px solid var(--border); margin-bottom: 1.5rem; }
|
||||||
|
.tab-btn {
|
||||||
|
padding: .55rem 1.1rem;
|
||||||
|
font-size: .82rem; font-weight: 600;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: none; border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
cursor: pointer; transition: .15s;
|
||||||
|
}
|
||||||
|
.tab-btn.active { color: var(--primary); border-color: var(--primary); }
|
||||||
|
.tab-pane { display: none; }
|
||||||
|
.tab-pane.active { display: block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="sidebar-placeholder"></div>
|
||||||
|
|
||||||
|
<main class="main-content">
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Integrazioni Esterne</h1>
|
||||||
|
<p>API, webhook e connettori per sistemi GRC di terze parti</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Hero -->
|
||||||
|
<div class="integ-hero">
|
||||||
|
<h1>NIS2 Agile come Provider di Compliance</h1>
|
||||||
|
<p>NIS2 Agile espone una <strong>Services API REST</strong> che permette a sistemi esterni di leggere
|
||||||
|
score di conformità, rischi, incidenti e controlli in tempo reale. Auth via API Key dedicata,
|
||||||
|
rate limiting 100 req/ora, firma HMAC-SHA256 per webhook outbound.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Provider cards -->
|
||||||
|
<div class="section-title">Provider Disponibili</div>
|
||||||
|
<div class="provider-grid">
|
||||||
|
<div class="provider-card">
|
||||||
|
<span class="badge badge-available">Disponibile</span>
|
||||||
|
<h3>231 Agile (lg231)</h3>
|
||||||
|
<p>Integrazione compliance D.Lgs. 231/2001 + NIS2. Incidenti cyber → OdV,
|
||||||
|
rischi IT → registro 231, score NIS2 nel dashboard aziendale.</p>
|
||||||
|
<a href="#lg231" style="color:var(--primary);font-size:.8rem;text-decoration:none">Guida integrazione →</a>
|
||||||
|
</div>
|
||||||
|
<div class="provider-card">
|
||||||
|
<span class="badge badge-planned">Pianificato</span>
|
||||||
|
<h3>AllRisk Agile</h3>
|
||||||
|
<p>Feed rischi cyber per piattaforma ERM unificata. Risk score NIS2
|
||||||
|
mappato su categorie ISO 31000.</p>
|
||||||
|
</div>
|
||||||
|
<div class="provider-card">
|
||||||
|
<span class="badge badge-planned">Pianificato</span>
|
||||||
|
<h3>SustainAI Agile</h3>
|
||||||
|
<p>Cybersecurity come pillar ESG. Score NIS2 nel report di sostenibilità
|
||||||
|
(CSRD, GRI 418).</p>
|
||||||
|
</div>
|
||||||
|
<div class="provider-card">
|
||||||
|
<span class="badge badge-available">Disponibile</span>
|
||||||
|
<h3>SIEM / SOC Esterni</h3>
|
||||||
|
<p>Webhook su <code>incident.created</code> e <code>risk.created_high</code>
|
||||||
|
verso qualsiasi endpoint HTTPS (Splunk, Elastic, PagerDuty…).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="tab-nav">
|
||||||
|
<button class="tab-btn active" onclick="showTab('api')">Services API</button>
|
||||||
|
<button class="tab-btn" onclick="showTab('lg231')">Guida lg231</button>
|
||||||
|
<button class="tab-btn" onclick="showTab('webhook')">Webhook</button>
|
||||||
|
<button class="tab-btn" onclick="showTab('quickstart')">Quick Start</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: Services API -->
|
||||||
|
<div id="tab-api" class="tab-pane active">
|
||||||
|
<div class="section-title">Endpoint disponibili</div>
|
||||||
|
<div class="callout">
|
||||||
|
<strong>Base URL:</strong> <code>https://nis2.agile.software/api/services</code><br>
|
||||||
|
<strong>Auth:</strong> Header <code>X-API-Key: nis2_xxxx</code> — oppure <code>Authorization: Bearer nis2_xxxx</code>
|
||||||
|
</div>
|
||||||
|
<table class="api-table">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Metodo</th><th>Endpoint</th><th>Scope</th><th>Descrizione</th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/status</code></td><td>—</td><td>Health check piattaforma, versione, DB. Nessuna auth.</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/compliance-summary</code></td><td>read:compliance</td><td>Score NIS2 aggregato (0-100), domain scores Art.21, rischi aperti, incidenti</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/risks/feed</code></td><td>read:risks</td><td>Registro rischi con livello ISO 27005, status, area. Filtri: <code>?level=high,critical&status=open</code></td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/incidents/feed</code></td><td>read:incidents</td><td>Incidenti Art.23: severity, status, notifiche CSIRT, deadline 72h, overdue</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/controls/status</code></td><td>read:compliance</td><td>Stato controlli per dominio: implemented / partial / missing</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/assets/critical</code></td><td>read:assets</td><td>Asset critici con tipo, criticità, dipendenze mappate</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/suppliers/risk</code></td><td>read:suppliers</td><td>Fornitori: risk_level, ultimo assessment, is_flagged</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/policies/approved</code></td><td>read:policies</td><td>Policy approvate: titolo, area, data, versione</td></tr>
|
||||||
|
<tr><td><span class="method-get">GET</span></td><td><code>/openapi.json</code></td><td>—</td><td>Specifica OpenAPI 3.0 in JSON</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="section-title">Esempio risposta /compliance-summary</div>
|
||||||
|
<div class="code-block"><span class="cmt">// GET /api/services/compliance-summary</span>
|
||||||
|
<span class="cmt">// X-API-Key: nis2_Abc123...</span>
|
||||||
|
{
|
||||||
|
<span class="key">"success"</span>: <span class="kw">true</span>,
|
||||||
|
<span class="key">"data"</span>: {
|
||||||
|
<span class="key">"organization"</span>: { <span class="key">"id"</span>: 5, <span class="key">"name"</span>: <span class="str">"Azienda XYZ"</span>, <span class="key">"sector"</span>: <span class="str">"energia"</span>, <span class="key">"nis2_entity_type"</span>: <span class="str">"essential"</span> },
|
||||||
|
<span class="key">"compliance"</span>: {
|
||||||
|
<span class="key">"overall_score"</span>: 73,
|
||||||
|
<span class="key">"label"</span>: <span class="str">"Parziale"</span>,
|
||||||
|
<span class="key">"domain_scores"</span>: [
|
||||||
|
{ <span class="key">"domain"</span>: <span class="str">"Gestione del rischio"</span>, <span class="key">"score"</span>: 80, <span class="key">"status"</span>: <span class="str">"compliant"</span> },
|
||||||
|
{ <span class="key">"domain"</span>: <span class="str">"Sicurezza supply chain"</span>, <span class="key">"score"</span>: 45, <span class="key">"status"</span>: <span class="str">"partial"</span> }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
<span class="key">"risks"</span>: { <span class="key">"total"</span>: 12, <span class="key">"open"</span>: 8, <span class="key">"high_critical"</span>: 3 },
|
||||||
|
<span class="key">"incidents"</span>: { <span class="key">"total"</span>: 2, <span class="key">"open"</span>: 1, <span class="key">"significant"</span>: 1, <span class="key">"overdue"</span>: 0 }
|
||||||
|
}
|
||||||
|
}</div>
|
||||||
|
|
||||||
|
<div class="section-title">Headers risposta</div>
|
||||||
|
<table class="api-table">
|
||||||
|
<thead><tr><th>Header</th><th>Valore</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><code>X-NIS2-API-Version</code></td><td>1.0</td></tr>
|
||||||
|
<tr><td><code>X-NIS2-Org-Id</code></td><td>ID organizzazione (intero)</td></tr>
|
||||||
|
<tr><td><code>X-RateLimit-Limit</code></td><td>100 (req/ora per chiave)</td></tr>
|
||||||
|
<tr><td><code>X-RateLimit-Remaining</code></td><td>Richieste rimaste nella finestra</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: Guida lg231 -->
|
||||||
|
<div id="tab-lg231" class="tab-pane">
|
||||||
|
<div class="callout">
|
||||||
|
<strong>Per l'agente Claude di lg231:</strong> questo tab descrive esattamente cosa implementare in lg231 Agile per consumare NIS2 come provider di compliance cybersecurity.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Perché integrare 231 con NIS2</div>
|
||||||
|
<table class="api-table" style="margin-bottom:1.5rem">
|
||||||
|
<thead><tr><th>Evento NIS2</th><th>Rilevanza D.Lgs. 231/2001</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Incidente cybersecurity (Art.23 NIS2)</td><td>Reato presupposto art.24-bis — criminalità informatica</td></tr>
|
||||||
|
<tr><td>Fornitore IT flaggato alto rischio</td><td>Rischio complicità in reati informatici via supply chain</td></tr>
|
||||||
|
<tr><td>Policy sicurezza non approvata</td><td>Gap nel MOG — Modello Organizzativo e di Gestione</td></tr>
|
||||||
|
<tr><td>Score NIS2 < 40%</td><td>Indicatore OdV: carenza controlli, escalation consigliata</td></tr>
|
||||||
|
<tr><td>Whistleblowing sicurezza</td><td>Potenziale escalation verso OdV 231</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="section-title">1. Aggiungere NIS2 al provider-config (company-ms)</div>
|
||||||
|
<p style="font-size:.83rem;color:var(--text-secondary);margin-bottom:.75rem">
|
||||||
|
File: <code>services/company-ms/public/index.php</code> — route <code>PATCH /companies/{id}/provider-config</code>
|
||||||
|
</p>
|
||||||
|
<div class="code-block"><span class="cmt">// Aggiungere ai $allowed:</span>
|
||||||
|
$allowed = [
|
||||||
|
<span class="str">'certisource_pat'</span>,
|
||||||
|
<span class="str">'anthropic_api_key'</span>,
|
||||||
|
<span class="str">'openai_api_key'</span>,
|
||||||
|
<span class="cmt">// ── Nuovo provider NIS2 ──</span>
|
||||||
|
<span class="str">'nis2_api_key'</span>, <span class="cmt">// chiave da NIS2 → Settings → API Keys</span>
|
||||||
|
<span class="str">'nis2_org_id'</span>, <span class="cmt">// ID org NIS2 (dal URL dashboard o /api/services/status)</span>
|
||||||
|
<span class="str">'nis2_enabled'</span>, <span class="cmt">// bool: abilitare pull automatico</span>
|
||||||
|
];</div>
|
||||||
|
|
||||||
|
<div class="section-title">2. NIS2Client (shared lib o inline)</div>
|
||||||
|
<div class="code-block"><span class="kw">class</span> <span class="key">Nis2Client</span>
|
||||||
|
{
|
||||||
|
<span class="kw">const</span> BASE_URL = <span class="str">'https://nis2.agile.software/api/services'</span>;
|
||||||
|
|
||||||
|
<span class="kw">public static function</span> <span class="key">get</span>(<span class="key">string</span> $endpoint, <span class="key">string</span> $apiKey, <span class="key">array</span> $query = []): array
|
||||||
|
{
|
||||||
|
$url = self::BASE_URL . $endpoint;
|
||||||
|
<span class="kw">if</span> ($query) $url .= <span class="str">'?'</span> . http_build_query($query);
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => <span class="kw">true</span>,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_HTTPHEADER => [
|
||||||
|
<span class="str">'X-API-Key: '</span> . $apiKey,
|
||||||
|
<span class="str">'Accept: application/json'</span>,
|
||||||
|
<span class="str">'X-Caller: lg231-agile/1.0'</span>,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$body = curl_exec($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
<span class="kw">return</span> $code === 200 && $body
|
||||||
|
? [<span class="str">'success'</span> => <span class="kw">true</span>, <span class="str">'data'</span> => json_decode($body, <span class="kw">true</span>)]
|
||||||
|
: [<span class="str">'success'</span> => <span class="kw">false</span>, <span class="str">'http_code'</span> => $code];
|
||||||
|
}
|
||||||
|
}</div>
|
||||||
|
|
||||||
|
<div class="section-title">3. Widget score NIS2 nella company view</div>
|
||||||
|
<div class="code-block"><span class="cmt">// In company detail view PHP/JS:</span>
|
||||||
|
$nis2Key = $meta[<span class="str">'nis2_api_key'</span>] ?? <span class="kw">null</span>;
|
||||||
|
<span class="kw">if</span> ($nis2Key) {
|
||||||
|
$summary = Nis2Client::get(<span class="str">'/compliance-summary'</span>, $nis2Key);
|
||||||
|
<span class="cmt">// Mostrare gauge score + badge essential/important + rischi high</span>
|
||||||
|
<span class="cmt">// Link: https://nis2.agile.software/dashboard.html</span>
|
||||||
|
}</div>
|
||||||
|
|
||||||
|
<div class="section-title">4. Escalation incidenti → OdV (monitoring-ms)</div>
|
||||||
|
<div class="code-block"><span class="cmt">// Job periodico (cron o hook) nel monitoring-ms:</span>
|
||||||
|
$incidents = Nis2Client::get(<span class="str">'/incidents/feed'</span>, $apiKey,
|
||||||
|
[<span class="str">'significant'</span> => 1, <span class="str">'status'</span> => <span class="str">'open'</span>]);
|
||||||
|
|
||||||
|
<span class="kw">foreach</span> (($incidents[<span class="str">'data'</span>][<span class="str">'incidents'</span>] ?? []) <span class="kw">as</span> $inc) {
|
||||||
|
<span class="cmt">// Crea odv_activity:</span>
|
||||||
|
<span class="cmt">// type: 'cyber_incident_notifica'</span>
|
||||||
|
<span class="cmt">// description: 'Incidente NIS2 significativo: ' . $inc['title']</span>
|
||||||
|
<span class="cmt">// priority: $inc['overdue'] ? 'urgent' : 'high'</span>
|
||||||
|
<span class="cmt">// source: 'nis2_agile'</span>
|
||||||
|
<span class="cmt">// reference_id: $inc['id']</span>
|
||||||
|
}</div>
|
||||||
|
|
||||||
|
<div class="section-title">Checklist implementazione lg231</div>
|
||||||
|
<ul class="checklist">
|
||||||
|
<li>company-ms: aggiungere <code>nis2_api_key</code>, <code>nis2_org_id</code>, <code>nis2_enabled</code> a provider-config</li>
|
||||||
|
<li>shared: creare <code>Nis2Client</code> (curl wrapper leggero)</li>
|
||||||
|
<li>UI: widget NIS2 score nella company detail (gauge + badge + rischi high)</li>
|
||||||
|
<li>risk-ms: import rischi cyber durante assessment (categoria art.24-bis)</li>
|
||||||
|
<li>monitoring-ms: escalation incidenti significativi → odv_activity</li>
|
||||||
|
<li>monitoring-ms: policy NIS2 non approvate → alert OdV</li>
|
||||||
|
<li>(opz.) endpoint <code>POST /api/integrations/nis2-webhook</code> + subscription</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="callout">
|
||||||
|
<strong>Nota CORS:</strong> tutte le chiamate a NIS2 devono avvenire <strong>server-to-server</strong>
|
||||||
|
(PHP cURL), non da browser. NIS2 non accetta origini lg231 in CORS per sicurezza.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: Webhook -->
|
||||||
|
<div id="tab-webhook" class="tab-pane">
|
||||||
|
<div class="callout">
|
||||||
|
NIS2 invia notifiche push su eventi tramite webhook HTTPS firmati HMAC-SHA256.
|
||||||
|
Il sistema esterno riceve la notifica in tempo reale senza polling.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Eventi disponibili</div>
|
||||||
|
<div class="event-list">
|
||||||
|
<div class="event-row">
|
||||||
|
<span class="event-name">incident.created</span>
|
||||||
|
<span class="event-desc">Nuovo incidente di sicurezza registrato</span>
|
||||||
|
<span class="event-lg231">→ odv_activity</span>
|
||||||
|
</div>
|
||||||
|
<div class="event-row">
|
||||||
|
<span class="event-name">incident.deadline_warning</span>
|
||||||
|
<span class="event-desc">Scadenza Art.23 (24h/72h) entro 4 ore</span>
|
||||||
|
<span class="event-lg231">→ alert urgente OdV</span>
|
||||||
|
</div>
|
||||||
|
<div class="event-row">
|
||||||
|
<span class="event-name">risk.created_high</span>
|
||||||
|
<span class="event-desc">Nuovo rischio HIGH o CRITICAL creato</span>
|
||||||
|
<span class="event-lg231">→ risk register 231</span>
|
||||||
|
</div>
|
||||||
|
<div class="event-row">
|
||||||
|
<span class="event-name">compliance.score_changed</span>
|
||||||
|
<span class="event-desc">Variazione score NIS2 > 5%</span>
|
||||||
|
<span class="event-lg231">→ aggiorna widget</span>
|
||||||
|
</div>
|
||||||
|
<div class="event-row">
|
||||||
|
<span class="event-name">policy.approved</span>
|
||||||
|
<span class="event-desc">Nuova policy di sicurezza approvata</span>
|
||||||
|
<span class="event-lg231">→ aggiorna MOG ref</span>
|
||||||
|
</div>
|
||||||
|
<div class="event-row">
|
||||||
|
<span class="event-name">supplier.risk_flagged</span>
|
||||||
|
<span class="event-desc">Fornitore IT flaggato alto rischio</span>
|
||||||
|
<span class="event-lg231">→ supply chain 231</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Registrare una subscription</div>
|
||||||
|
<div class="code-block"><span class="method-post">POST</span> https://nis2.agile.software/api/webhooks/subscriptions
|
||||||
|
Authorization: Bearer {jwt_token}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
<span class="key">"url"</span>: <span class="str">"https://lg231.agile.software/api/integrations/nis2-webhook"</span>,
|
||||||
|
<span class="key">"events"</span>: [<span class="str">"incident.created"</span>, <span class="str">"incident.deadline_warning"</span>, <span class="str">"risk.created_high"</span>],
|
||||||
|
<span class="key">"description"</span>: <span class="str">"lg231 Agile integration"</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
<span class="cmt">// Risposta:</span>
|
||||||
|
{ <span class="key">"id"</span>: 1, <span class="key">"secret"</span>: <span class="str">"whsec_abc123..."</span> }
|
||||||
|
<span class="cmt">// Salvare il secret in companies.metadata.nis2_webhook_secret</span></div>
|
||||||
|
|
||||||
|
<div class="section-title">Verifica firma HMAC (endpoint ricevente)</div>
|
||||||
|
<div class="code-block">$secret = $meta[<span class="str">'nis2_webhook_secret'</span>];
|
||||||
|
$body = file_get_contents(<span class="str">'php://input'</span>);
|
||||||
|
$expected = <span class="str">'sha256='</span> . hash_hmac(<span class="str">'sha256'</span>, $body, $secret);
|
||||||
|
$received = $_SERVER[<span class="str">'HTTP_X_NIS2_SIGNATURE'</span>] ?? <span class="str">''</span>;
|
||||||
|
|
||||||
|
<span class="kw">if</span> (!hash_equals($expected, $received)) {
|
||||||
|
http_response_code(401);
|
||||||
|
<span class="kw">exit</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = $_SERVER[<span class="str">'HTTP_X_NIS2_EVENT'</span>] ?? <span class="str">''</span>;
|
||||||
|
$payload = json_decode($body, <span class="kw">true</span>);
|
||||||
|
|
||||||
|
<span class="kw">switch</span> ($event) {
|
||||||
|
<span class="kw">case</span> <span class="str">'incident.created'</span>:
|
||||||
|
<span class="cmt">// crea odv_activity...</span>
|
||||||
|
<span class="kw">break</span>;
|
||||||
|
<span class="kw">case</span> <span class="str">'risk.created_high'</span>:
|
||||||
|
<span class="cmt">// aggiungi a risk register...</span>
|
||||||
|
<span class="kw">break</span>;
|
||||||
|
}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TAB: Quick Start -->
|
||||||
|
<div id="tab-quickstart" class="tab-pane">
|
||||||
|
<div class="section-title">Creare la prima API Key</div>
|
||||||
|
<ol class="steps-list">
|
||||||
|
<li>Login su <strong>NIS2 Agile</strong> con ruolo <code>org_admin</code> o superiore</li>
|
||||||
|
<li>Vai su <strong>Settings → API Keys</strong> (sezione Integrazioni)</li>
|
||||||
|
<li>Click <strong>"Nuova API Key"</strong> → nome descrittivo (es: "lg231 Integration") → scope: <code>read:all</code></li>
|
||||||
|
<li>Copia la chiave — formato <code>nis2_XXXX...</code> — visibile una sola volta</li>
|
||||||
|
<li>Nota l'<strong>Organization ID</strong> dal URL del dashboard (es: <code>/dashboard.html</code> dopo login)</li>
|
||||||
|
<li>In lg231: salva via <code>PATCH /companies/{id}/provider-config</code> con <code>nis2_api_key</code> e <code>nis2_org_id</code></li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div class="section-title">Test rapido (curl)</div>
|
||||||
|
<div class="code-block"><span class="cmt"># 1. Verifica senza auth</span>
|
||||||
|
curl https://nis2.agile.software/api/services/status
|
||||||
|
|
||||||
|
<span class="cmt"># 2. Compliance summary</span>
|
||||||
|
curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||||
|
https://nis2.agile.software/api/services/compliance-summary | python3 -m json.tool
|
||||||
|
|
||||||
|
<span class="cmt"># 3. Incidenti significativi aperti</span>
|
||||||
|
curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||||
|
"https://nis2.agile.software/api/services/incidents/feed?significant=1&status=open"
|
||||||
|
|
||||||
|
<span class="cmt"># 4. Rischi high/critical</span>
|
||||||
|
curl -H <span class="str">"X-API-Key: nis2_TUA_CHIAVE"</span> \
|
||||||
|
"https://nis2.agile.software/api/services/risks/feed?level=high,critical"</div>
|
||||||
|
|
||||||
|
<div class="section-title">Contatti e riferimenti</div>
|
||||||
|
<table class="api-table">
|
||||||
|
<tbody>
|
||||||
|
<tr><td>App NIS2 Agile</td><td><a href="https://nis2.agile.software" style="color:var(--primary)">https://nis2.agile.software</a></td></tr>
|
||||||
|
<tr><td>Specifiche OpenAPI</td><td><code>GET /api/services/openapi.json</code></td></tr>
|
||||||
|
<tr><td>Test Runner</td><td><a href="/test-runner.php?t=Nis2Test2026" style="color:var(--primary)">test-runner.php</a></td></tr>
|
||||||
|
<tr><td>Amministratore</td><td>cristiano.benassati@gmail.com</td></tr>
|
||||||
|
<tr><td>Documento tecnico lg231</td><td><code>docs/integration/lg231-nis2-integration.md</code></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="js/common.js"></script>
|
||||||
|
<script src="js/i18n.js"></script>
|
||||||
|
<script>
|
||||||
|
function showTab(id) {
|
||||||
|
document.querySelectorAll('.tab-btn').forEach((b, i) => {
|
||||||
|
const ids = ['api', 'lg231', 'webhook', 'quickstart'];
|
||||||
|
b.classList.toggle('active', ids[i] === id);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.tab-pane').forEach(p => {
|
||||||
|
p.classList.toggle('active', p.id === 'tab-' + id);
|
||||||
|
});
|
||||||
|
// scroll to tabs
|
||||||
|
document.querySelector('.tab-nav').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -214,7 +214,7 @@ function loadSidebar() {
|
|||||||
{ name: 'Impostazioni', href: 'settings.html', icon: iconCog(), i18nKey: 'nav.settings' },
|
{ name: 'Impostazioni', href: 'settings.html', icon: iconCog(), i18nKey: 'nav.settings' },
|
||||||
{ name: 'Architettura', href: 'architecture.html', icon: iconCubeTransparent() },
|
{ name: 'Architettura', href: 'architecture.html', icon: iconCubeTransparent() },
|
||||||
{ name: 'Simulazione Demo', href: 'simulate.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"/></svg>` },
|
{ name: 'Simulazione Demo', href: 'simulate.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"/></svg>` },
|
||||||
{ name: 'Integrazioni', href: 'integrations/index.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"/><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"/></svg>` },
|
{ name: 'Integrazioni Esterne', href: 'integrazioniext.html', icon: `<svg viewBox="0 0 20 20" fill="currentColor"><path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"/><path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"/></svg>` },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user