[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:
co-authored by
Claude Sonnet 4.6
parent
3321509d02
commit
5ecdce7d47
@@ -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*
|
||||
Reference in New Issue
Block a user