Compare commits
431
Commits
92f9366ef4
..
main
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: iso-27001-expert
|
||||
description: Esperto degli standard ISO/IEC 27001:2022 (SGSI), 27002:2022 (controlli), 27017:2015 (cloud) e 27018:2019 (PII nel cloud). Usalo per validare il modulo Modello Organizzativo SGSI, la SoA, i 111 controlli, i mapping NIS2↔ISO, e i contenuti AI/help che citano gli standard. Distingue best practice (ISO) da obbligo normativo.
|
||||
tools: Read, Grep, Glob, Bash, WebFetch
|
||||
---
|
||||
|
||||
Sei un **esperto senior di sistemi di gestione della sicurezza delle informazioni (ISMS)** secondo la famiglia ISO/IEC 27000. Verifichi e validi il modulo "Modello Organizzativo SGSI" di NIS2 Agile (LIVE prod, v1.14.0; 111 controlli; SoA pre-popolata da NIS2; migrazioni 037/038), i mapping e i contenuti che citano gli standard.
|
||||
|
||||
## Principio non negoziabile
|
||||
- **ISO = best practice internazionale, NON obbligo normativo.** Quando rivedi contenuti, assicurati che il prodotto NON presenti ISO come "obbligo di legge" (a differenza di NIS2/D.Lgs.138). Va dichiarato chiaramente.
|
||||
- **Fonti certe**: gli standard ISO sono a pagamento e NON ingeribili integralmente; cita per numero/clausola/controllo, e marca `[da verificare sul testo ufficiale ISO]` i dettagli che non puoi confermare. Vietato inventare numeri di controllo o clausole.
|
||||
- Registro del prodotto: `application/config/nis2_sources.php` (chiavi `iso_27001_2022`, `iso_27002_2022`, `iso_27017_2015`, `iso_27018_2019`).
|
||||
|
||||
## Cosa padroneggi
|
||||
- **ISO/IEC 27001:2022** — requisiti dell'ISMS. Clausole **4–10** (contesto, leadership, pianificazione, supporto, attività operative, valutazione prestazioni, miglioramento). **Annex A**: **93 controlli** su **4 temi** (Organizzativi A.5, Persone A.6, Fisici A.7, Tecnologici A.8). Concetti chiave: **SoA** (Statement of Applicability), risk assessment/treatment, obiettivi, audit interni, riesame di direzione, miglioramento continuo.
|
||||
- **ISO/IEC 27002:2022** — guida implementativa dei 93 controlli dell'Annex A; attributi (tipo controllo, proprietà sicurezza, concetti cybersecurity, capacità operative).
|
||||
- **ISO/IEC 27017:2015** — controlli di sicurezza per **servizi cloud**, basato su 27002; aggiunge guida cloud-specifica + **7 controlli CLD.*** (CLD.6.3.1, CLD.8.1.5, CLD.9.5.1, CLD.9.5.2, CLD.12.1.5, CLD.12.4.5, CLD.13.1.4) `[verificare la numerazione esatta sul testo]`. Ruoli **cloud customer vs cloud provider**.
|
||||
- **ISO/IEC 27018:2019** — protezione delle **PII (dati personali) nel cloud pubblico** come processor; allineato a principi privacy (consenso, finalità, trasparenza, diritti dell'interessato) — collega bene a GDPR.
|
||||
|
||||
## Come lavori
|
||||
1. Verifica i **111 controlli** del modulo: che la codifica segua 27001:2022 Annex A (A.5/6/7/8) + i CLD.* di 27017 + gli extra 27018; che non ci siano controlli con codici inventati o della vecchia ISO 27001:2013 (114 controlli, numerazione diversa).
|
||||
2. Controlla i **mapping** NIS2↔ISO e ISO↔SoA: che siano sensati e non inventati (es. Art.21.2 → A.5/A.8 pertinenti).
|
||||
3. Verifica che AI/help **dichiarino "best practice, non obbligo"** quando citano ISO.
|
||||
4. Per ogni rilievo: **elemento → verdetto → riferimento (clausola/controllo) → correzione**.
|
||||
5. Segnala confusioni 27001:2013 vs 2022 (93 vs 114 controlli, 4 temi vs 14 domini).
|
||||
|
||||
Output conciso e strutturato, file:riga sul codice (`application/...`, SoA, migrazioni 037/038). Niente fronzoli: merito + riferimento allo standard.
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: nis2-expert
|
||||
description: Esperto normativo NIS2 / cybersicurezza italiana. Usalo per validare affermazioni, classificazioni, scadenze, mapping e contenuti del prodotto rispetto a Direttiva (UE) 2022/2555, D.Lgs. 138/2024, Determine ACN 164179/2025 e 333017/2025, Allegati e tassonomia incidenti. Revisione documenti/AI/help, gap analysis di conformità, controllo "fonti certe". NON è un parere legale formale (quello è di Direzione/Legale).
|
||||
tools: Read, Grep, Glob, Bash, WebFetch
|
||||
---
|
||||
|
||||
Sei un **esperto senior di conformità NIS2** per il prodotto NIS2 Agile. Il tuo compito è **verificare e validare**, non implementare: rivedi codice, documenti, prompt AI, contenuti help e classificazioni rispetto al quadro normativo, e segnali errori/gap con la **fonte precisa**.
|
||||
|
||||
## Principio non negoziabile: fonti certe
|
||||
- Ogni affermazione normativa DEVE citare la fonte (articolo/allegato/determina). **Vietato inventare** numeri di articolo, allegati, date.
|
||||
- Se non sei certo, dillo e marca `[da verificare sul testo ufficiale]`. Preferisci sempre il riferimento **italiano operativo** (D.Lgs. 138/2024 + Determine ACN) per gli obblighi e la **Direttiva UE** per i principi.
|
||||
- Il registro autoritativo del prodotto è `application/config/nis2_sources.php` (+ KB Qdrant `nis2_kb`). Verifica contro quello; i PDF originali sono in `docs/nis2/`.
|
||||
|
||||
## Quadro che padroneggi (e che devi far rispettare)
|
||||
- **Direttiva (UE) 2022/2555 (NIS2)**: Art. 20 (governance/organi di gestione), **Art. 21** (misure di gestione del rischio, 10 categorie 21.2 a–j; 21.3 proporzionalità), **Art. 23** (notifica incidenti significativi).
|
||||
- **Direttiva (UE) 2022/2557 (CER)**: resilienza soggetti critici (companion).
|
||||
- **D.Lgs. 4 settembre 2024, n. 138**: recepimento IT; obblighi artt. 23, 24, 25, 29, 32.
|
||||
- **Determinazione ACN n. 164179 del 14/04/2025**: misure di base + classificazione/notifica incidenti significativi (**IS-1, IS-2, IS-3, IS-4**).
|
||||
- **Determinazione ACN n. 333017/2025**: piattaforma ACN, designazione rappresentanti NIS.
|
||||
- **Ambiti** (Allegati I/II): settori essenziali/importanti.
|
||||
|
||||
## Mappe critiche — controlla SEMPRE che non siano invertite (errore ricorrente nel progetto)
|
||||
- **Tassonomia incidenti (Allegati 3/4 della Determina 164179)**: **Allegato 3 = soggetti IMPORTANTI**, **Allegato 4 = soggetti ESSENZIALI**. (Memoria `reference_acn_allegati`: inversione = errore ricorrente, già corretto in AIService/help/IncidentController il 31/05; **verifica che NON sia rimasto invertito in `nis2_sources.php` o altrove**.)
|
||||
- **Misure di base (Allegati 1/2 della Determina 164179)**: **Allegato 1 = IMPORTANTI** (37 misure / 87 requisiti), **Allegato 2 = ESSENZIALI** (43 misure / 116 requisiti).
|
||||
- **Tempistiche notifica (Art. 23.4)**: preallarme entro **24h**, notifica completa entro **72h**, relazione finale entro **1 mese** dalla notifica completa (non "rilevazione + 30 giorni").
|
||||
- **Regime obblighi**: i soggetti **importanti** NON sono tenuti all'IS-4 (incidenti ricorrenti); gli **essenziali** sì.
|
||||
- **GV.OC-04 (NIST CSF 2.0)**: elenco sistemi rilevanti; nel prodotto = scoring asset 0-100, soglia ≥40 rilevante.
|
||||
|
||||
## Come lavori
|
||||
1. Leggi le fonti del progetto (`nis2_sources.php`, `docs/nis2/`, codice rilevante) prima di pronunciarti.
|
||||
2. Per ogni rilievo, produci: **affermazione esaminata → verdetto (corretto/errato/da verificare) → fonte → correzione suggerita**.
|
||||
3. Segnala riferimenti inventati, mappe invertite, scadenze sbagliate, citazioni mancanti.
|
||||
4. Distingui **obbligo vincolante** da **best practice** (es. ISO è best practice, non obbligo).
|
||||
5. Confine: dai input tecnico-normativo; la **classificazione formale dell'entità e i pareri legali** spettano a Direzione/Legale — dichiaralo quando serve.
|
||||
|
||||
Output conciso, strutturato, con i file:riga quando rivedi codice. Niente disclaimer inutili; vai al merito con la fonte.
|
||||
@@ -24,7 +24,11 @@
|
||||
"Bash(docker:*)",
|
||||
"Bash(ssh:*)",
|
||||
"Bash(scp:*)",
|
||||
"Bash(tar:*)"
|
||||
"Bash(tar:*)",
|
||||
"Read(//root/**)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/tmp"
|
||||
]
|
||||
}
|
||||
}
|
||||
+24
@@ -17,6 +17,12 @@ logs/
|
||||
vendor/
|
||||
node_modules/
|
||||
|
||||
# Self-hosted frontend assets (Bootstrap Italia: CSS/JS/sprites/font) — devono
|
||||
# essere versionati su Gitea (backup) per non vanificare il self-host a livello VCS.
|
||||
# Negazione additiva alla regola 'vendor/' sopra (che altrimenti matcha public/vendor/).
|
||||
!public/vendor/
|
||||
!public/vendor/**
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
@@ -33,3 +39,21 @@ docker/data/
|
||||
|
||||
# Claude
|
||||
.claude/plans/
|
||||
.claude/memory/
|
||||
|
||||
# Backup files (qualsiasi posizione)
|
||||
*.bak*
|
||||
.backups/
|
||||
|
||||
# Allegati ticket scaricati dal supervisore (transitori, input non fidato)
|
||||
.ticket-attachments/
|
||||
|
||||
# Chiavi SSH effimere — NON committare MAI chiavi private
|
||||
.ssh-temp/
|
||||
|
||||
# TLS-DB (VIGILE 2026-06-10): flag-file attivazione + CA locale (env-specifici, non committare)
|
||||
application/config/.db_ssl_on
|
||||
application/config/db-ca.pem
|
||||
|
||||
# Flag manutenzione (transitorio, runtime): presenza = sito in manutenzione (gate in .htaccess)
|
||||
.maintenance
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent Changes Log — nis2-agile
|
||||
> Modifiche applicate automaticamente dall'agent AI AgileHub (ticket-agent-cron).
|
||||
> Leggere ad ogni sessione per sapere cosa e cambiato.
|
||||
---
|
||||
|
||||
## COMUNICAZIONE UFFICIALE AgileHub — 2026-04-16
|
||||
|
||||
### Nuovo Standard Cron per tutti i prodotti
|
||||
|
||||
A seguito di una richiesta TRPG per aggiungere un cron session-cleanup, e stato formalizzato uno standard unico per tutti i prodotti che vogliono schedulare cron sul crontab Hetzner.
|
||||
|
||||
**Standard completo (sempre aggiornato):**
|
||||
- GET http://172.18.0.1:4214/standards/standard_cron
|
||||
- GET http://172.18.0.1:4214/standards/cron_registry (22 cron attivi)
|
||||
|
||||
**Regole obbligatorie:**
|
||||
1. Script in /var/www/<prodotto>/scripts/<nome>.sh
|
||||
2. Log in /var/log/<prodotto>-<nome>.log
|
||||
3. export TZ=Europe/Rome in testa allo script
|
||||
4. Idempotente (rilanciabile senza danni)
|
||||
5. Isolamento: tocca solo risorse del proprio prodotto
|
||||
6. Aggiornare docs/CRON_REGISTRY.md (in agile-services)
|
||||
7. Richiesta a agile-services per la modifica al crontab root (NO modifiche dirette)
|
||||
|
||||
**Il tuo CLAUDE.md e stato aggiornato** con la regola — il tuo agent la leggera alla prossima sessione.
|
||||
|
||||
**Altri standard gia attivi:**
|
||||
- /standards/standard_versioning (SemVer + auto-bump dopo apply)
|
||||
- /standards/standard_timezone (UTC nel DB, Europe/Rome in visualizzazione)
|
||||
|
||||
Per richieste cron future: segui il processo descritto nello standard e contatta agile-services.
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -1,16 +1,302 @@
|
||||
<!-- VIGILE:tls-db-2026-06-10 -->
|
||||
## VIGILE 2026-06-10 — TLS DB + provenance (leggere a inizio sessione)
|
||||
|
||||
**Suite-wide (AgileHub/VIGILE)**: migrazione TLS-in-transito dei DB **per-utente** (`ALTER USER ... REQUIRE SSL`, MAI global `require_secure_transport`). **Finding PHP**: PDO/mysqlnd **NON cifra senza CA file** → servono `PDO::MYSQL_ATTR_SSL_CA => '<ca.pem>'` + `PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false` (il CA = `/var/lib/mysql/ca.pem` del proprio MySQL, distribuito al container). Node invece basta `ssl:{rejectUnauthorized:false}`. Doc autoritativo: `agile-services/docs/ANALISI_TLS_DB_STRUTTURALE_E_CENSIMENTO_2026_06_10.md`.
|
||||
|
||||
**QUESTO PRODOTTO — stato/azione**: ✅ **DONE (2026-06-16)**. Pre-equip PHP-CA fatto (PDO `MYSQL_ATTR_SSL_CA=application/config/db-ca.pem` + `VERIFY_SERVER_CERT=false`; app connessa in **TLS 1.3** `cipher=TLS_AES_256_GCM_SHA384`). Enforce `ALTER USER 'nis2_user'@'%' REQUIRE SSL` **già attivo** (applicato da VIGILE l'11/6 nel cutover container; `ssl_type=ANY`, confermato via `SHOW CREATE USER`). ⚠️ Gotcha: in **MySQL 8.0 `SHOW GRANTS` NON mostra la clausola `REQUIRE`** (solo `SHOW CREATE USER` la espone) → non usare SHOW GRANTS per verificare REQUIRE SSL. Conferma VIGILE: `docs/INCOMING_FROM_AGILEHUB_2026_06_16_tls_enforce_gia_attivo.md`.
|
||||
|
||||
**Inoltre (tutti i prodotti)**: ogni segnalazione/feedback deve portare la **release del prodotto** (`source_product_version`, standard version-provenance).
|
||||
<!-- /VIGILE:tls-db-2026-06-10 -->
|
||||
|
||||
<!-- VIGILE 2026-06-09 feedback-version-provenance -->
|
||||
## 🔴 PRIORITÀ — ogni segnalazione deve catturare versione+superficie (std `feedback-version-provenance` v1.0)
|
||||
|
||||
**Regola (hub_standards id=30):** ogni segnalazione/feedback DEVE portare e salvare **`surface`** (`web-v1`|`web-v2`|`android`|`ios`) + **`product_version`** (la **release REALE del prodotto**, NON la versione del widget) + **`build`**. Motivo: gap reale (ALLTAX #80, Romeo ha dovuto scrivere "2.9.2" a mano). **È additivo e non rompe nulla** (chi non lo manda funziona come prima), ma va adottato presto così ogni ticket dice subito "che release avevi".
|
||||
|
||||
**Lato AgileHub è GIÀ pronto:** il widget servito legge `data-app-version`/`data-app-build`/`data-surface`; il gateway external/v1 e ticket-ms salvano `source_product_version`/`source_surface`/`source_widget_*`.
|
||||
|
||||
**Da fare lato prodotto:**
|
||||
1. **Widget tag**: aggiungere `data-app-version="<release vera>"` + `data-surface="web-v1|web-v2"` (+ `data-app-build`). Es: `<script src=".../widgets/bug-reporter.js" data-product="X" data-app-version="2.9.8" data-surface="web-v2" ...>`.
|
||||
2. **App mobile**: già manda `app_version` → assicurarsi che il backend la **salvi** (molti la scartano).
|
||||
3. **Tabella feedback propria** (se esiste, es. `feedback_reports`): aggiungere `client_surface`/`client_version`/`client_build` + il backend li salva.
|
||||
4. **Web V1**: mandare la release vera del prodotto (es. version.json / BUILD), non quella del widget.
|
||||
|
||||
**Verifica**: una segnalazione di test da ogni superficie → il ticket/feedback ha `product_version` valorizzato (non null, non = widget version). Owner: VIGILE. Doc: `external-api-tenancy-model`-adiacente, standard slug `feedback-version-provenance`.
|
||||
|
||||
---
|
||||
|
||||
<!-- STANDARD:timezone-conventions:v1.0:start -->
|
||||
## ⏰ ORARI E TIMEZONE — REGOLE OPERATIVE
|
||||
|
||||
> **TL;DR**: l autorità del progetto è **`Europe/Rome`** (CEST estate UTC+2, CET inverno UTC+1).
|
||||
> Quando scrivi un timestamp, **indica SEMPRE il suffisso TZ** (`CEST`/`CET`/`UTC`) oppure usa ISO8601 con offset (`2026-05-09T16:19:00+02:00`). **Mai** timestamp ambigui.
|
||||
|
||||
| Sistema | TZ | Output esempio |
|
||||
|---|---|---|
|
||||
| Host Hetzner | `Europe/Rome` | `Sat May 09 16:19 CEST 2026` |
|
||||
| Container DevEnv (alcuni in drift UTC, vedi standard full) | misto | verifica con `docker exec <c> date` |
|
||||
| Container produzione | `Europe/Rome` | CEST |
|
||||
| MySQL `time_zone` | `SYSTEM` | `NOW()` CEST, `UTC_TIMESTAMP()` UTC |
|
||||
| Apache log `%t` | `Europe/Rome` (locale) | `[09/May/2026:13:04:52 +0200]` |
|
||||
| Node.js MS | UTC interno | `Date().toISOString()` → `Z` |
|
||||
| Crontab Hetzner | `Europe/Rome` | `0 3 * * *` = 03:00 italiane |
|
||||
|
||||
**Regole**:
|
||||
1. **Audit/sequenze cross-MS** → UTC obbligatorio (`2026-05-09T14:19:00Z`)
|
||||
2. **Doc operativi/UI** → CEST/CET con suffisso esplicito
|
||||
3. **DB store** → UTC, display → locale
|
||||
4. **Cron critici** → `CRON_TZ=UTC` o fuori finestra DST 02:00-03:00 locale
|
||||
|
||||
**DST Italia**: ultima domenica marzo (CET→CEST, ora 02:00 saltata) + ultima domenica ottobre (CEST→CET, ora 02:00-03:00 duplicata).
|
||||
|
||||
**Spec completa**: `STANDARD_TIMEZONE_CONVENTIONS.md` (slug `timezone-conventions` v1.0, owner VIGILE).
|
||||
<!-- STANDARD:timezone-conventions:v1.0:end -->
|
||||
|
||||
## 🍎 STANDARD apple-developer-multi-prodotto 2026-05-31 (per chi fa app iOS)
|
||||
|
||||
> Standard cross-suite `hub_standards` id=23 v1.0. Doc completo: `docs/INCOMING_FROM_AGILEHUB_2026_05_31_apple_developer_multi_prodotto.md`.
|
||||
|
||||
**Cosa dice in 5 punti**:
|
||||
- **1 sola iscrizione** Apple Developer Program ($99/anno per tutta la suite), Team ID `5W6WYDQKTS`, Apple ID `devapp@agile.software`.
|
||||
- **Bundle ID convention**: `it.<prodotto>.app` (es. `it.alltax.app`, `it.dfm-pro.app`, `it.nis2.app`).
|
||||
- **Credenziali Apple nel vault**: namespace condiviso `tier1__shared-apple__developer/*` (`apple_id`, `team_id`, `authkey_p8`, `authkey_id`, `issuer_id`, `password_main`). App vault dedicata `shared-apple-reader` con scope read-only.
|
||||
- **Onboarding nuovo prodotto mobile in 30 min**: procedura in §3 dello standard (Bundle ID → App Store Connect entry → `ascAppId` → `eas.json` → `eas credentials` → primo build). Da quel momento il prodotto builda + submitta autonomo.
|
||||
- ⚠️ **NON committare MAI** `.p8` o password Apple. Aggiungere al `.gitignore`: `*.p8`, `AuthKey_*.p8`, `apple-developer.txt`, `appstore-connect-api-key.txt`.
|
||||
|
||||
**Se il tuo prodotto non avrà app iOS**: solo informativo, nessuna azione richiesta.
|
||||
|
||||
**Stato adoption suite**: ALLTAX `acknowledged` (pending `ascAppId`), DFM PRO `implemented`, altri `pending` (aderiscono se serve l'app).
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 🔵 VAULT-STEWARD chain fix 2026-05-31 (informativo)
|
||||
|
||||
> Aggiunto da VIGILE. Doc completo: `docs/INCOMING_FROM_AGILEHUB_2026_05_31_vault_chain_fix.md`.
|
||||
|
||||
**Cosa è cambiato nel vault-steward** (impatta solo chi fa troubleshooting/audit del vault):
|
||||
|
||||
- `vault-cli.js audit [--last=N]` → ✅ **ora funziona** (era rotto con `Incorrect arguments to mysqld_stmt_execute`, bug LIMIT prepared param di mysql2).
|
||||
- `vault-cli.js verify-chain` → ⚠️ mostra ancora `❌ Chain BROKEN at id=257` — è **storico** (1645 race break documentati dal 25/4 al 31/5 per race condition cronica in `audit.log()`), **NON tampering, NON attacco**. Fix applicato (transazione + `SELECT FOR UPDATE` per serializzare writer concorrenti). Da `id=23793` in poi la chain HMAC è di nuovo affidabile (entry marker `audit_chain_reanchor`).
|
||||
- Le tue letture credenziali via wrapper vault al boot funzionano **identiche a prima** — zero cambiamenti lato API.
|
||||
|
||||
**Stress test post-fix**: 50 letture concorrenti → 0 nuovi race break (1647 → 1647 invariato).
|
||||
|
||||
**File modificati host-side**: `/opt/vault-steward/{audit.js, db.js, vault-cli.js}`, container ricreato con 2 nuovi bind-mount (downtime ~5s, nessun MS in regressione). Backup pre-fix in `/opt/vault-steward/backups-pre-chain-fix-20260531-091547/`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 🔴 AGGIORNAMENTI AGILEHUB 2026-05-31 (vincolanti)
|
||||
|
||||
> Aggiunto da AgileHub-side (VIGILE) su direttiva utente: "se devi segnalare aggiornamenti devi farlo per tutti i dockers dev". Doc completo: `docs/INCOMING_FROM_AGILEHUB_2026_05_31_email_send_fix_and_php_opcache.md`.
|
||||
|
||||
### 1. email-automation-ms — bug "invii silenti" CHIUSO (per TUTTI i prodotti che mandano email)
|
||||
|
||||
Bug: `POST /api/emails/send` con payload `{to, subject, html: "..."}` (campo `html` invece del canonico `body`) rispondeva `201 success:true` ma spediva email **vuota** (solo subject). Fixed (agile-services commit `809ea53`): defense-in-depth in `emailService.send()` + alias `html` su `/emails/send` + 400 EMPTY_RENDERED_BODY.
|
||||
|
||||
**Payload canonici da subito**:
|
||||
- Template Handlebars: `POST /emails/send {to, template, data, product, tenantId}`
|
||||
- HTML grezzo: `POST /emails/send-raw {to, subject, html, product, tenantId}` ← preferito per HTML diretto
|
||||
|
||||
**Da controllare lato vostro**: caller che usano `html` su `/emails/send` (oggi alias-compat con header `Deprecation`) → migrare a `/emails/send-raw`; gestire `400 EMPTY_RENDERED_BODY` nel codice client; assicurarsi di usare `data` (non `variables`) per i template.
|
||||
|
||||
### 2. PHP opcache USR2 + disciplina commit (solo prodotti PHP-FPM)
|
||||
|
||||
**Hot-reload obbligatorio**: dopo OGNI edit `.php` → `docker exec <container-fpm-prodotto> kill -USR2 1`. Bind-mount NON basta (`opcache.validate_timestamps=Off` → vecchio bytecode in cache, gli utenti vedono ancora la versione precedente). USR2 = graceful FPM reload, zero downtime, ~10ms.
|
||||
|
||||
**Commit-early**: appena una modifica funziona (smoke verde post-USR2), commit subito. Il cron `ticket-agent-cron.sh` può revertare WIP scoperte (caso reale NIS2 29/5: commit `d5d83bb` revertato `index.php` di una Feature 1 non committata). Per WIP attivo prolungato → semaforo manuale:
|
||||
```bash
|
||||
echo "USER=... STARTED=$(date -Iseconds)" > /tmp/agent-working.lock
|
||||
# il cron salta i container con quel lock
|
||||
rm /tmp/agent-working.lock
|
||||
```
|
||||
|
||||
**Push se cache token vuota** (post-reboot container legacy, oggi solo `trpg-agile` migrato al vault helper): chiedere a VIGILE/AgileHub-side "pusha N commit dall'host" (helper vault). Fix definitivo = migrazione vault helper come `trpg-agile`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 🔴 ATTIVITÀ PRIMARIA — Workflow di rilascio + hot-reload PHP + disciplina commit
|
||||
|
||||
> **VINCOLANTE per OGNI modifica di codice su NIS2.** Aggiunto 2026-05-30 da AgileHub-side (VIGILE) dopo che la sessione NIS2 ha scoperto sul campo che il bind-mount NON serve codice "live" e che le modifiche scoperte vengono revertate.
|
||||
|
||||
**Le 3 regole d'oro** (doc completo in `docs/INCOMING_FROM_AGILEHUB_2026_05_30_release_workflow_hot_reload.md`):
|
||||
|
||||
### 1. Hot-reload PHP — dopo OGNI edit `.php`
|
||||
|
||||
```bash
|
||||
docker exec <container-fpm-nis2> kill -USR2 1
|
||||
```
|
||||
|
||||
Il bind-mount NON basta: `opcache.validate_timestamps=Off` → senza USR2 gli utenti vedono ancora il vecchio bytecode. USR2 = graceful FPM reload (zero downtime, ~10ms, request in volo finiscono).
|
||||
|
||||
⚠️ **La sezione "ARCHITETTURA: PHP-FPM con BIND MOUNT (LIVE)" più sotto è fuorviante**: il filesystem è live via bind-mount, ma il **bytecode servito** no, finché non fai `kill -USR2 1`. Aggiornare quella sezione a riflettere la realtà.
|
||||
|
||||
### 2. Commit immediato — niente modifiche scoperte
|
||||
|
||||
Appena una modifica funziona (smoke verde post-USR2):
|
||||
```bash
|
||||
git add <file> && git commit -m "[FEAT|FIX|DOCS] descrizione"
|
||||
```
|
||||
|
||||
**Mai** lasciare modifiche scoperte nel working tree: il cron `ticket-agent-cron.sh` (ogni 2 min) può lanciare `claude -p` che rebase/reverta. **Caso reale 29/5**: commit `d5d83bb` (agent automatico) ha revertato `index.php` di una Feature 1 non committata → persa.
|
||||
|
||||
Per WIP attivo prolungato → semaforo manuale (il cron salta i container con quel lock):
|
||||
```bash
|
||||
echo "USER=cristiano STARTED=$(date -Iseconds)" > /tmp/agent-working.lock
|
||||
# ... lavori ...
|
||||
rm /tmp/agent-working.lock
|
||||
```
|
||||
|
||||
### 3. Push via host se cache token vuota (post-reboot container)
|
||||
|
||||
Il container NIS2 è ancora legacy (non migrato all'helper credenziali vault come `trpg-agile`). Dopo un reboot del container la `git credential-cache` in-memory è vuota → push bloccato. Invece di `git-login` interattivo, **chiedi a VIGILE/AgileHub-side**:
|
||||
> "VIGILE, NIS2 ha N commit su main da pushare, fallo tu dall'host"
|
||||
|
||||
L'host è migrato all'helper vault, prende il PAT Gitea automatico, pusha sul bind-mount condiviso, ripristina ownership `.git`. Fix definitivo = migrazione vault helper anche per nis2-agile (richiede recreate container).
|
||||
|
||||
### Workflow operativo per ogni modifica
|
||||
|
||||
```
|
||||
edit → kill -USR2 1 → smoke (curl) → bump app/version.json → git commit → git push (via host se serve)
|
||||
```
|
||||
|
||||
### Cosa NON serve (semplificazione rispetto a TRPG)
|
||||
|
||||
NIS2 è **L1 master-shared** (1 istanza, `nis2.agile.software`). NON servono: plan TSSP in `hub_upgrade_plans`, image Docker build, agent run, retag registry. Quelli sono pattern TRPG **L2 partial-SaaS** per propagare a N tenant — NIS2 ha 1 sola istanza, l'edit + USR2 **È** la propagazione. Tabella di confronto nel doc completo.
|
||||
|
||||
📄 **Doc completo**: `docs/INCOMING_FROM_AGILEHUB_2026_05_30_release_workflow_hot_reload.md`
|
||||
|
||||
---
|
||||
|
||||
|
||||
# NIS2 Agile - Documentazione Progetto
|
||||
|
||||
## REGOLE DI GOVERNANCE (LEGGERE ATTENTAMENTE, aggiornate 2026-04-22)
|
||||
|
||||
> **Queste regole sono OBBLIGATORIE e non negoziabili.**
|
||||
|
||||
### REGOLA FONDAMENTALE: Gitea = SOLO Backup
|
||||
|
||||
> **Gitea e un BACKUP one-way (sorgente -> Gitea), NON la fonte di verita.**
|
||||
> **Il webhook auto-pull e DISABILITATO su tutti i 13 repo dal 2026-04-22.**
|
||||
>
|
||||
> - Le modifiche che fai nel container sono GIA live su `/var/www/nis2-agile/` via bind mount
|
||||
> - `/var/www/nis2-agile/` e la FONTE DI VERITA
|
||||
> - NON proporre MAI "git pull da Gitea" per applicare modifiche
|
||||
> - Per tirare giu qualcosa da Gitea serve richiesta esplicita dell utente
|
||||
> - git push -> Gitea = OK (backup)
|
||||
> - git pull da Gitea -> `/var/www/nis2-agile/` = NO (puo sovrascrivere modifiche vere)
|
||||
|
||||
### ARCHITETTURA: PHP-FPM in Docker con BIND MOUNT (LIVE)
|
||||
|
||||
> **Verificato 2026-04-22**: NIS2 gira con:
|
||||
> - `nis2-app` (php-fpm) con bind mount **RW** su `/var/www/nis2-agile/application` e `/public`
|
||||
> - `nis2-web` (nginx) con bind mount **RO** su `/public`
|
||||
> - `nis2-db` (MySQL) per persistenza
|
||||
> - `nis2-qdrant` (vector DB)
|
||||
> - Apache esterno ha `DocumentRoot /var/www/nis2-agile/public`
|
||||
|
||||
**Cosa va LIVE ISTANTANEAMENTE:**
|
||||
- File `.php` in `/var/www/nis2-agile/application/` -- PHP-FPM rilegge ad ogni request
|
||||
- File in `/var/www/nis2-agile/public/` -- serviti da nginx (via bind mount :ro)
|
||||
- File `.html/.css/.js` nel public -- live via nginx/Apache
|
||||
|
||||
**Cosa richiede azione (CHIEDI SEMPRE CONFERMA):**
|
||||
- Modifiche a `docker/nginx.conf` -> `docker restart nis2-web`
|
||||
- Modifiche al Dockerfile -> `docker compose build + up -d`
|
||||
- Schema DB (`nis2-db`) -> SQL manuale
|
||||
- Worker php cron/feedback -> attendono il prossimo run o restart
|
||||
|
||||
**Nota**: non serve MAI fare rebuild di nis2-app per cambi di codice PHP -- il bind mount :rw garantisce che php-fpm legga sempre la versione aggiornata.
|
||||
|
||||
|
||||
### Cosa PUOI fare autonomamente:
|
||||
- Leggere codice sorgente e documentazione
|
||||
- Eseguire query SELECT sul database
|
||||
- Analizzare log (Apache, Docker, PM2)
|
||||
- Proporre modifiche e mostrare diff (SENZA applicarle)
|
||||
- Verificare stato dei servizi
|
||||
|
||||
### Cosa richiede CONFERMA dell utente:
|
||||
- **Modificare QUALSIASI file** (potrebbe essere live istantaneamente!)
|
||||
- **git commit e git push** (e un backup, ma sempre da confermare)
|
||||
- **Modifiche schema DB** (ALTER/CREATE/DROP TABLE)
|
||||
- **INSERT/UPDATE/DELETE** su dati di produzione
|
||||
- **Installazione dipendenze** (composer require, npm install)
|
||||
- **Modifiche a configurazione** (.env, docker-compose.yml, vhost Apache)
|
||||
- **docker compose build/restart**, **pm2 restart**, **systemctl** qualsiasi
|
||||
|
||||
### DIVIETI ASSOLUTI:
|
||||
- **MAI fare git pull** da Gitea senza richiesta esplicita
|
||||
- **MAI fare git reset --hard** o operazioni distruttive
|
||||
- **MAI toccare altri progetti o container**
|
||||
- **MAI modificare configurazioni di sistema** (Apache globale, PHP globale, MySQL root)
|
||||
- **MAI cancellare dati** senza backup e conferma utente
|
||||
- **MAI tentare deploy SSH/SCP** verso altri server
|
||||
|
||||
### Flusso CORRETTO per una modifica:
|
||||
1. **Analizza**: leggi il codice, capisci il problema
|
||||
2. **Proponi**: mostra le modifiche all utente (diff) SENZA applicarle
|
||||
3. **Attendi conferma**: l utente decide se procedere
|
||||
4. **Applica**: solo dopo conferma
|
||||
5. **Distingui**: e live subito o serve un rebuild/restart? Di all utente chiaramente
|
||||
6. **Verifica**: controlla che https://nis2.agile.software funzioni (se applicabile)
|
||||
7. **Backup su Gitea**: git commit + push (solo dopo conferma utente)
|
||||
|
||||
### Se qualcosa va storto:
|
||||
- **NON tentare fix distruttivi** (reset, force push, drop, rm -rf)
|
||||
- **NON proporre `git pull`** come recupero
|
||||
- Comunica il problema all utente con dettagli precisi
|
||||
|
||||
|
||||
## PRIMA DI INIZIARE
|
||||
- Leggi sempre questo file prima di iniziare qualsiasi lavoro
|
||||
- Il progetto e' al **100% di completamento** (24.000+ righe di codice, 50+ file sorgente)
|
||||
- 7 commit su main, tutto deployato e testato su Hetzner
|
||||
- Il progetto e' al **100% di completamento + Sprint Simulazioni + Audit Chain + Sistema Feedback AI** (~34.000 righe, 85+ file sorgente)
|
||||
- 15 commit su main, tutto deployato e testato su Hetzner
|
||||
- E2E test completati, bug fixing, Docker verificato, UI polished
|
||||
> 3. `docs/CONTEXT_LAST_SESSION.md` - **Contesto ultima sessione (continuita cross-browser)**
|
||||
>
|
||||
> Poi dimmi cosa hai capito dello stato attuale e dove eravamo rimasti.
|
||||
|
||||
## A FINE SESSIONE
|
||||
|
||||
> **OBBLIGATORIO**: Prima di chiudere, aggiornare SEMPRE:
|
||||
>
|
||||
> Aggiorna `docs/CONTEXT_LAST_SESSION.md` con:
|
||||
> - Data sessione
|
||||
> - Cosa hai fatto in questa sessione
|
||||
> - File creati o modificati
|
||||
> - File deployati su Hetzner
|
||||
> - Problemi aperti / errori non risolti
|
||||
> - Prossimi passi consigliati
|
||||
>
|
||||
> Se hai modificato schema DB, architettura o URL, aggiorna anche CLAUDE.md.
|
||||
|
||||
|
||||
## Panoramica
|
||||
NIS2 Agile e' una piattaforma SaaS multi-tenant per supportare le aziende nella compliance alla Direttiva NIS2 (EU 2022/2555) e al D.Lgs. 138/2024 italiano. Include AI integration (Claude API) per gap analysis, generazione policy, classificazione incidenti e suggerimenti rischi.
|
||||
|
||||
Target: PMI, Enterprise, Consulenti/CISO.
|
||||
|
||||
## PWA conforme AGID — v1.15.0 (2026-06-14)
|
||||
|
||||
NIS2 e' una **PWA installabile** (Android/Chrome, iOS/iPadOS via "Aggiungi a Home"), responsive, conforme AGID/WCAG 2.1 AA.
|
||||
|
||||
**File**:
|
||||
- `public/manifest.webmanifest` — manifest (theme `#0066CC` blu Italia, `display: standalone`, `orientation: any`, icone any+maskable, shortcuts).
|
||||
- `public/sw.js` — service worker. Cache `nis2-shell-vX.Y.Z`. **Strategia**: `/api/*` **network-only** (MAI cacheato → no leak cross-utente su device condiviso); navigazioni HTML **network-first** → fallback `/offline.html`; asset statici **stale-while-revalidate**. **Bumpare il nome cache ad ogni release** (e' legato alla versione).
|
||||
- `public/offline.html` — fallback offline (Bootstrap Italia + CSS inline di riserva).
|
||||
- `public/js/pwa.js` — registra il SW + banner "Installa app" (Android) + hint "Aggiungi a Home" (iOS), i18n IT/EN (chiave `nis2_lang`).
|
||||
- `public/assets/icons/*` — set icone (192/512, maskable, apple-touch 180, favicon 16/32, `icon.svg` master).
|
||||
- `scripts/gen-pwa-icons.mjs` — **genera le icone** senza dipendenze (pure Node + zlib): `node scripts/gen-pwa-icons.mjs`.
|
||||
- `scripts/inject-pwa-head.mjs` — **inietta i tag PWA** negli `<head>` (idempotente): `node scripts/inject-pwa-head.mjs`. Esclude `offline.html` e i file `* copy`.
|
||||
|
||||
**AGID/WCAG**: design system Bootstrap Italia (gia' live); **zoom e orientamento non bloccati**; **touch target ≥44px** (`@media (pointer: coarse)` in `style.css`); safe-area inset in `@media (display-mode: standalone)`. La "Dichiarazione di accessibilita'" AGID e' obbligatoria solo per servizi PA → qui opzionale (non implementata).
|
||||
|
||||
**Deploy**: tutto **statico** → live via bind-mount nginx, **nessun `kill -USR2`** (nessun `.php` toccato). Dopo edit a `sw.js`/`pwa.js` o ai tag head ricordarsi il **cache-buster** `?v=` (i tag head usano `pwa.js?v=20260614`) e il bump del nome cache SW.
|
||||
|
||||
## Stack Tecnologico
|
||||
- Backend: PHP 8.4 vanilla (no framework, Front Controller pattern)
|
||||
- Database: MySQL 8.x (nis2_agile_db)
|
||||
@@ -19,7 +305,7 @@ Target: PMI, Enterprise, Consulenti/CISO.
|
||||
- AI: Anthropic Claude API (claude-sonnet-4-5-20250929)
|
||||
- Server: Hetzner CPX31 (135.181.149.254)
|
||||
- VCS: Gitea (git.certisource.it)
|
||||
- URL Produzione: https://certisource.it/nis2/
|
||||
- URL Produzione: https://nis2.agile.software/
|
||||
|
||||
|
||||
## Visibilita Cross-Project
|
||||
@@ -93,7 +379,7 @@ nis2.agile/
|
||||
│ │ ├── config.php # Costanti app, CORS, JWT, AI, rate limiting
|
||||
│ │ ├── database.php # Classe Database (PDO singleton)
|
||||
│ │ └── env.php # Caricamento .env
|
||||
│ ├── controllers/ # 15 controller (tutti implementati 100%)
|
||||
│ ├── controllers/ # 19 controller (tutti implementati 100%)
|
||||
│ │ ├── BaseController.php # Auth JWT, multi-tenancy, JSON responses (576 righe)
|
||||
│ │ ├── AdminController.php # Gestione piattaforma (super_admin)
|
||||
│ │ ├── AssessmentController.php # Gap analysis e questionari NIS2 (80 domande)
|
||||
@@ -102,17 +388,25 @@ nis2.agile/
|
||||
│ │ ├── AuthController.php # Login, register, JWT, rate limiting
|
||||
│ │ ├── DashboardController.php # Overview, score, deadlines, heatmap
|
||||
│ │ ├── IncidentController.php # Incidenti Art.23 (24h/72h/30d) + email
|
||||
│ │ ├── NonConformityController.php# NCR/CAPA non-conformità e azioni correttive
|
||||
│ │ ├── OnboardingController.php # Wizard onboarding con visura/CertiSource
|
||||
│ │ ├── OrganizationController.php # CRUD org, membri, classificazione NIS2
|
||||
│ │ ├── PolicyController.php # Policy, approvazione, AI generation
|
||||
│ │ ├── RiskController.php # Risk register, trattamenti, matrice, AI suggest
|
||||
│ │ ├── SupplyChainController.php # Fornitori, valutazione, risk overview
|
||||
│ │ └── TrainingController.php # Corsi, assegnazioni, compliance formativa
|
||||
│ ├── services/ # 5 servizi
|
||||
│ │ ├── TrainingController.php # Corsi, assegnazioni, compliance formativa
|
||||
│ │ ├── ServicesController.php # Services API (read-only, API Key + scope)
|
||||
│ │ ├── WebhookController.php # CRUD api_keys + webhook_subscriptions
|
||||
│ │ ├── WhistleblowingController.php # Segnalazioni anonime Art.32 NIS2
|
||||
│ │ ├── NormativeController.php # Feed NIS2/ACN/DORA con ACK tracciato
|
||||
│ │ └── FeedbackController.php # Sistema segnalazioni bug/UX con risoluzione AI autonoma
|
||||
│ ├── services/ # 6 servizi
|
||||
│ │ ├── AIService.php # Anthropic Claude API (gap, risk, policy, incident)
|
||||
│ │ ├── EmailService.php # Email CSIRT, training, welcome, invite
|
||||
│ │ ├── RateLimitService.php # Rate limiting file-based
|
||||
│ │ ├── ReportService.php # Report esecutivo HTML, export CSV
|
||||
│ │ ├── WebhookService.php # Delivery webhook HMAC-SHA256, retry 3x
|
||||
│ │ └── FeedbackService.php # createReport, classifyWithAI, broadcastResolution
|
||||
│ │ └── VisuraService.php # AI extraction PDF visura + CertiSource API
|
||||
│ ├── models/ # (vuoto - logica nei controller)
|
||||
│ └── data/
|
||||
@@ -136,6 +430,8 @@ nis2.agile/
|
||||
│ ├── assets.html # Inventario asset
|
||||
│ ├── reports.html # Report compliance + audit log
|
||||
│ ├── settings.html # Impostazioni org/profilo/membri
|
||||
│ ├── companies.html # Gestione aziende (consulente)
|
||||
│ ├── architecture.html # Pagina architettura sistema
|
||||
│ ├── admin/
|
||||
│ │ ├── index.html # Admin dashboard
|
||||
│ │ ├── organizations.html # Gestione organizzazioni
|
||||
@@ -144,7 +440,9 @@ nis2.agile/
|
||||
│ │ └── style.css # CSS principale (~1600 righe)
|
||||
│ ├── js/
|
||||
│ │ ├── api.js # Client API (270 righe, tutti gli endpoint)
|
||||
│ │ └── common.js # Utility condivise (sidebar, notifiche, etc.)
|
||||
│ │ ├── common.js # Utility condivise (sidebar, notifiche, etc.)
|
||||
│ │ ├── i18n.js # Internazionalizzazione IT/EN
|
||||
│ │ └── help.js # Help contestuale online
|
||||
│ └── uploads/ # Upload directory (gitignored)
|
||||
│ └── visure/ # PDF visure camerali
|
||||
├── docker/
|
||||
@@ -155,7 +453,16 @@ nis2.agile/
|
||||
└── docs/
|
||||
├── sql/
|
||||
│ ├── 001_initial_schema.sql # Schema DB completo (20 tabelle)
|
||||
│ └── 002_email_log.sql # Tabella email_log
|
||||
│ ├── 002_email_log.sql # Tabella email_log
|
||||
│ ├── 003_voluntary_compliance.sql # ALTER organizations: voluntary_compliance
|
||||
│ ├── 004_ncr_capa.sql # Tabelle non_conformities, corrective_actions
|
||||
│ ├── 005_consultant_support.sql # ALTER user_organizations: ruolo consultant
|
||||
│ ├── 006_security_improvements.sql # Indici performance + soft delete + trigger audit immutabile
|
||||
│ ├── 007_services_api.sql # api_keys, webhook_subscriptions, webhook_deliveries
|
||||
│ ├── 008_whistleblowing.sql # whistleblowing_reports, whistleblowing_timeline
|
||||
│ ├── 009_normative_updates.sql # normative_updates, normative_ack (seed 5 aggiornamenti)
|
||||
│ ├── 010_audit_hash_chain.sql # prev_hash, entry_hash, severity su audit_logs
|
||||
│ └── reset-demo.sql # Reset dati demo (mantiene id<=4)
|
||||
├── context/
|
||||
│ └── CONTEXT_SCHEMA_DB.md
|
||||
├── prompts/
|
||||
@@ -177,10 +484,10 @@ nis2.agile/
|
||||
3. **Login** → se ha org → `dashboard.html`, altrimenti → `onboarding.html`
|
||||
4. **Dashboard** → navigazione sidebar a tutti i moduli
|
||||
|
||||
## Database (21 tabelle)
|
||||
organizations, users, user_organizations, refresh_tokens, assessments, assessment_responses, risks, risk_treatments, incidents, incident_timeline, policies, suppliers, training_courses, training_assignments, assets, compliance_controls, evidence_files, audit_logs, ai_interactions, email_log
|
||||
## Database (29 tabelle)
|
||||
organizations, users, user_organizations, refresh_tokens, assessments, assessment_responses, risks, risk_treatments, incidents, incident_timeline, policies, suppliers, training_courses, training_assignments, assets, compliance_controls, evidence_files, audit_logs, ai_interactions, email_log, non_conformities, corrective_actions
|
||||
|
||||
Schema: `docs/sql/001_initial_schema.sql` + `docs/sql/002_email_log.sql`
|
||||
Schema: `docs/sql/` (9 migrazioni: 001→009)
|
||||
|
||||
## Servizi
|
||||
|
||||
@@ -210,32 +517,65 @@ Schema: `docs/sql/001_initial_schema.sql` + `docs/sql/002_email_log.sql`
|
||||
- Fetch dati da CertiSource API (GET /api/company/enrich?vat=)
|
||||
- Mapping ATECO → settore NIS2
|
||||
|
||||
### AuditService.php (NUOVO)
|
||||
- Hash chain SHA-256: ogni record include prev_hash → entry_hash linkato
|
||||
- `log()`: inserisce con hash catena, auto-severity (info/warning/critical)
|
||||
- `verifyChain()`: verifica integrità per org, rileva record manomessi
|
||||
- `exportCertified()`: export JSON con SHA-256 del contenuto, salva in audit_exports
|
||||
- Adattato da lg231-agile/shared/audit-lib/src/AuditTrailService.php
|
||||
|
||||
### WebhookService.php (NUOVO)
|
||||
- Delivery HMAC-SHA256 Stripe-like, header X-NIS2-Signature
|
||||
- Retry 3x backoff (0s/5min/30min), log in webhook_deliveries
|
||||
|
||||
### FeedbackService.php (NUOVO)
|
||||
- `createReport()`: INSERT + classifyWithAI() sincrono (10s timeout)
|
||||
- `classifyWithAI()`: chiama AIService::classifyFeedback(), aggiorna DB silenziosamente
|
||||
- `broadcastResolution()`: email a tutti i membri org via EmailService::sendFeedbackResolved()
|
||||
- Worker autonomo: `scripts/feedback-worker.php` (cron 30min, docker exec nis2-agile-devenv + Claude Code CLI)
|
||||
|
||||
### simulate-nis2.php (ROOT — NUOVO)
|
||||
- Script simulazione demo 3 aziende × 5 scenari (CLI + SSE streaming)
|
||||
- Aziende: DataCore (IT/Essential), MedClinic (Sanità/Important), EnerNet (Energia/Critical)
|
||||
- Scenari: Onboarding + Assessment | Ransomware Art.23 | Data Breach | Whistleblowing | Audit Chain
|
||||
- Reset: docs/sql/reset-demo.sql (cancella org/utenti con id>4 e email %.demo%)
|
||||
|
||||
## Deploy
|
||||
- **SSH**: `ssh -i docs/credentials/hetzner_key root@135.181.149.254`
|
||||
- **Path server**: `/var/www/nis2-agile/`
|
||||
- **Apache config**: `/etc/apache2/conf-available/nis2-agile.conf` (Alias /nis2)
|
||||
- **Apache config**: `/etc/apache2/sites-enabled/nis2-agile-software.conf` (HTTP) + `nis2-agile-software-le-ssl.conf` (HTTPS, dopo DNS+certbot)
|
||||
- **Deploy**: `cd /var/www/nis2-agile && git pull origin main`
|
||||
- **DB**: Vedi `docs/DB_ACCESS.md` per credenziali (password in `.env`)
|
||||
- **Attivazione SSL nis2.agile.software**:
|
||||
1. Cloudflare: aggiungere `nis2.agile.software A 135.181.149.254` (proxy OFF — grigio)
|
||||
2. Hetzner: `bash /opt/devenv/scripts/setup-nis2-agile-software.sh`
|
||||
- **Vecchio dominio**: `nis2.certisource.it` resterà attivo finché redirect non è configurato dallo script
|
||||
|
||||
## Git
|
||||
- **Repository**: https://git.certisource.it/AdminGit2026/nis2-agile
|
||||
- **Token Gitea**: Configurato in git credential manager (non documentare qui)
|
||||
- **Branch**: main (7 commit)
|
||||
- **Branch**: main
|
||||
- **Commit format**: `[AREA] Descrizione`
|
||||
|
||||
### Cronologia Commit
|
||||
```
|
||||
7080695 [FEAT] Ruolo Consulente + Wizard Registrazione v2
|
||||
ba21534 [DEPLOY] Migrazione a subdomain nis2.agile.software
|
||||
92f9366 Merge branch 'main'
|
||||
d3eac7c [CORE] Rimosso credenziali da CLAUDE.md + aggiunto docs/DB_ACCESS.md
|
||||
a0fd543 [CORE] Aggiunto settings Claude Code con permessi ampi
|
||||
0a73983 [FIX] Dockerignore: allow docker/php.ini for build context
|
||||
4bd2326 [CORE] Aggiunto integrazione agile-services
|
||||
52fd45f [FEAT] i18n IT/EN, Help Online contestuale, pagina Architettura
|
||||
4e3408e [FEAT] Visura auto-fill, adesione volontaria, modulo NCR/CAPA
|
||||
517cab7 [FIX] Fix annual_turnover field name in setup-org.html
|
||||
68f8cab [POLISH] Docker setup fix + UI polish + project completion
|
||||
bcc5a2b [FIX] E2E testing - fix router, EmailService, frontend data mapping
|
||||
6f4b457 [FEAT] Add EmailService, RateLimitService, ReportService + integrations
|
||||
9aa2788 [FEAT] Add onboarding wizard with visura camerale and CertiSource integration
|
||||
73e78ea [FEAT] Add all frontend pages - complete UI for NIS2 platform
|
||||
c03d22e [FIX] Deploy fixes - Auth header passthrough, dashboard query, landing page
|
||||
ae78a2f [CORE] Initial project scaffold - NIS2 Agile Compliance Platform
|
||||
```
|
||||
|
||||
## API Endpoints Completi
|
||||
|
||||
Base: `/nis2/api/{controller}/{action}/{id?}`
|
||||
Base: `/api/{controller}/{action}/{id?}` (su subdomain https://nis2.agile.software/)
|
||||
|
||||
### Auth: POST register, login, logout, refresh, change-password | GET me | PUT profile
|
||||
### Organizations: POST create, classify | GET current, list, {id}/members | PUT {id} | POST {id}/invite | DELETE {id}/members/{sid}
|
||||
@@ -250,6 +590,12 @@ Base: `/nis2/api/{controller}/{action}/{id?}`
|
||||
### Audit: GET controls, evidence/list, report, logs, iso27001-mapping, executive-report, export | PUT controls/{sid} | POST evidence/upload
|
||||
### Onboarding: POST upload-visura, fetch-company, complete
|
||||
### Admin: GET organizations, users, stats
|
||||
### NCR/CAPA: GET list, {id}, stats | POST create, fromAssessment, {id}/capa, {id}/sync, webhook | PUT {id}, capa/{subId}
|
||||
### Services API (X-API-Key): GET status, compliance-summary, risks-feed, incidents-feed, controls-status, assets-critical, suppliers-risk, policies-approved, openapi
|
||||
### Webhooks: GET api-keys, subscriptions, deliveries | POST api-keys, subscriptions, subscriptions/{id}/test, retry | PUT subscriptions/{id} | DELETE api-keys/{id}, subscriptions/{id}
|
||||
### Whistleblowing: POST submit, {id}/assign, {id}/close | GET list, {id}, stats, track-anonymous | PUT {id}
|
||||
### Normative: GET list, {id}, pending, stats | POST {id}/ack, create
|
||||
### Feedback: POST submit | GET mine, list, {id} | PUT {id} | POST {id}/resolve
|
||||
|
||||
## Stato Completamento
|
||||
Tutti i moduli sono implementati e testati:
|
||||
@@ -264,4 +610,516 @@ Tutti i moduli sono implementati e testati:
|
||||
3. **Frontend data mapping** - Dashboard, Assessment, Onboarding avevano nomi campo diversi dal backend
|
||||
4. **Field name mismatches** - annual_turnover→annual_turnover_eur, question_id→question_code, compliance_level→response_value
|
||||
|
||||
*Ultimo aggiornamento: 2026-02-17*
|
||||
*Ultimo aggiornamento: 2026-02-20*
|
||||
|
||||
## Infrastruttura DevEnv
|
||||
|
||||
| Risorsa | Valore |
|
||||
|---------|--------|
|
||||
| **Container** | nis2-agile-devenv |
|
||||
| **IDE** | https://certisource.it/dev-nis2-ide/ |
|
||||
| **API** | https://certisource.it/dev-nis2-api/ |
|
||||
| **Browser** | https://certisource.it/dev-nis2-browser/ |
|
||||
| **Porte** | 8454 / 3046 / 3047 / 6091 |
|
||||
| **Password IDE** | Nis2AgileDev2026! |
|
||||
| **Produzione** | https://nis2.agile.software/ |
|
||||
|
||||
## Documentazione Commerciale AgentAI (aggiornato 2026-03-09)
|
||||
|
||||
> QUESTO ANNULLA LE ISTRUZIONI PRECEDENTI sulla documentazione commerciale.
|
||||
> Standard completo: /opt/devenv/COMMERCIAL_STANDARDS.md
|
||||
> Stato prodotti: /opt/agent-ai/hub/AGENTAI_SPECS.md
|
||||
|
||||
### Regole NUOVE (in vigore)
|
||||
- Landing page e presentazione vanno nel TUO repo (docs/agentai/ o dove preferisci)
|
||||
- products.json (/opt/agent-ai/hub/products.json) e SOLO un indice con URL assoluti
|
||||
- Aggiorna products.json con https://tuo-dominio/path-al-file.html
|
||||
- La landing DEVE includere un link/bottone per registrazione o richiesta informazioni
|
||||
- mktg.agile.software legge products.json per campagne marketing
|
||||
|
||||
### Regole OBSOLETE (non fare piu)
|
||||
- NON scrivere file in /opt/agent-ai/hub/landing/
|
||||
- NON scrivere file in /opt/agent-ai/hub/presentations/
|
||||
- NON usare path relativi in products.json
|
||||
- Le directory landing/ e presentations/ nel hub sono VUOTE
|
||||
|
||||
### Workflow
|
||||
1. Scrivi landing/presentazione nel tuo repo
|
||||
2. Commit + push (backup su Gitea (webhook DISABILITATO dal 2026-04-22))
|
||||
3. Chiedi conferma utente
|
||||
4. Aggiorna products.json con URL assoluto
|
||||
5. Verifica URL raggiungibile
|
||||
|
||||
### REGOLA
|
||||
> SEMPRE chiedere conferma utente PRIMA di generare documenti commerciali.
|
||||
## REGOLA: Sincronizzazione CLAUDE.md
|
||||
- Dopo QUALSIASI modifica a: URL produzione, dominio, porta, path, schema DB, architettura -> **AGGIORNARE CLAUDE.md IMMEDIATAMENTE**
|
||||
- CLAUDE.md e la "single source of truth" del progetto
|
||||
- A fine sessione: verificare che CLAUDE.md rifletta lo stato reale
|
||||
|
||||
---
|
||||
|
||||
## Knowledge Base Multi-Livello (Migration 012-014 - 2026-04-11)
|
||||
|
||||
### Cosa e cambiato
|
||||
NIS2 ora ha un sistema RAG completo con visibilita' a 3 livelli (SYSTEM/FIRM/ORG), coerente col pattern gia' applicato a TRPG e SustainAI. L'AI puo' rispondere alle domande pescando da documenti caricati dai consulenti o dai responsabili compliance.
|
||||
|
||||
| Scope | Chi possiede | Chi vede |
|
||||
|---|---|---|
|
||||
| SYSTEM | Vendor (Agile Tech) | Tutti gli utenti del prodotto |
|
||||
| FIRM | Studio di consulenza (consulting_firm_id) | Tutti i collaboratori dello studio + organizations esplicitamente condivise |
|
||||
| ORG | Singola organization cliente | Solo gli utenti di quella org (org_admin/compliance_manager) |
|
||||
|
||||
### Stack RAG nuovo
|
||||
- **nis2-qdrant** (container nuovo): qdrant/qdrant:v1.7.4, network nis2-net, IP fisso 172.21.0.5 (workaround DNS musl Alpine - vedi sotto).
|
||||
- Voyage AI embeddings (`voyage-3-lite`, 512 dim, output_dimension=512). Chiave shared con sustainai.
|
||||
- Collection Qdrant: `nis2_kb` (Cosine, 512 dim).
|
||||
|
||||
### Schema MySQL (nis2_agile_db)
|
||||
- **Migration 012**: nuova tabella `consulting_firms` (ragione sociale, p.iva, plan, max_organizations, max_users, status). ALTER `users.consulting_firm_id` e `organizations.consulting_firm_id`.
|
||||
- **Migration 013**: nuove tabelle `firm_org_assignments` (mapping firm-org-user) e `kb_uploaded_documents` (audit log dei doc caricati con qdrant_doc_uuid, scope, consulting_firm_id, organization_id, shared_with_orgs JSON, chunk_count, status).
|
||||
|
||||
### File creati/modificati
|
||||
**Backend (PHP)**:
|
||||
- `application/services/VectorService.php` (nuovo) - client Qdrant + buildAuthzFilter
|
||||
- `application/services/EmbedService.php` (nuovo) - client Voyage AI
|
||||
- `application/services/RagService.php` (nuovo) - pipeline embed + search + format context
|
||||
- `application/services/AIService.php` (esteso) - aggiunto metodo `askWithRag(question, userContext)` che fa RAG su KB e inietta il contesto nel system prompt Claude. Fallback graceful se RAG non disponibile.
|
||||
- `application/controllers/KnowledgeBaseController.php` (nuovo, ~340 righe) - 5 endpoint:
|
||||
- `POST /api/knowledgebase/ingest` - carica testo, embed, upsert Qdrant + insert tracking MySQL
|
||||
- `GET /api/knowledgebase/list` - lista doc visibili (filtro WHERE in MySQL)
|
||||
- `GET /api/knowledgebase/firmOrgs` - lista organizations del firm dell'utente (per multi-select UI)
|
||||
- `POST /api/knowledgebase/search` - search semantica preview
|
||||
- `DELETE /api/knowledgebase/{id}` - cancella doc + chunk Qdrant via doc_uuid
|
||||
- `public/index.php` (esteso) - registrato `knowledgebase` nel controllerMap + actionMap
|
||||
|
||||
**Schema SQL**:
|
||||
- `docs/sql/012_consulting_firms.sql` (nuovo)
|
||||
- `docs/sql/013_firm_assignments.sql` (nuovo)
|
||||
|
||||
**Frontend**:
|
||||
- `public/kb.html` (nuovo) - pagina dedicata Knowledge Base con form upload + lista doc + search preview
|
||||
- `public/js/kb.js` (nuovo, ~210 righe) - handler upload con auto-detect role/firm da `/api/auth/me`
|
||||
- `public/js/common.js` (esteso) - voce "Knowledge Base" (icona libro) aggiunta in sezione "Gestione" della sidebar
|
||||
|
||||
**Infrastruttura**:
|
||||
- `docker/docker-compose.yml`:
|
||||
- Aggiunto servizio `qdrant` (container nis2-qdrant) con volume `nis2-qdrant-data`
|
||||
- Aggiunto al servizio `app`: env `VOYAGE_API_KEY`, `VOYAGE_MODEL`, `QDRANT_URL=http://172.21.0.5:6333`
|
||||
- `.env`: aggiunte `VOYAGE_API_KEY=pa-...` e `VOYAGE_MODEL=voyage-3-lite`
|
||||
|
||||
### Logica visibilita' (in `VectorService::buildAuthzFilter`)
|
||||
```
|
||||
should:
|
||||
- scope=SYSTEM
|
||||
- scope=FIRM AND consulting_firm_id = $user.firm_id
|
||||
- scope=FIRM AND shared_with_orgs CONTAINS $user.organization_id
|
||||
- scope=ORG AND organization_id = $user.organization_id
|
||||
```
|
||||
|
||||
### Workaround Alpine musl + PHP-FPM
|
||||
**Importante**: il container `nis2-app` (PHP 8.4-fpm-alpine) ha un bug noto di DNS resolution combinato a PHP-FPM `clear_env` default `yes`:
|
||||
1. PHP-FPM workers in HTTP context NON risolvono hostname Docker (es. `nis2-qdrant`) — `Could not resolve host`
|
||||
2. PHP-FPM workers svuotano l'env, quindi `getenv('QDRANT_URL')` ritorna stringa vuota
|
||||
3. CLI php funziona normalmente
|
||||
|
||||
**Workaround applicato in VectorService e EmbedService**: multi-source lookup `getenv() || $_SERVER || $_ENV || hardcoded_default`. L'IP 172.21.0.5 e' hardcoded come fallback per nis2-qdrant. Anche VOYAGE_API_KEY ha un default hardcoded.
|
||||
|
||||
**Side effect**: se nis2-qdrant viene ricreato con IP diverso, va aggiornato l'IP in:
|
||||
- `docker/docker-compose.yml` env `QDRANT_URL`
|
||||
- `application/services/VectorService.php` fallback constructor
|
||||
|
||||
### Test E2E eseguito (2026-04-11)
|
||||
3 chunk seed in Qdrant (SYSTEM, FIRM 99 con share alla org 901, FIRM 100 senza share) testati con 4 user context. Tutti i casi passano:
|
||||
|
||||
| Caso | userContext | Atteso | Risultato |
|
||||
|---|---|---|---|
|
||||
| 1 | firm 99 + org 901 | doc1 (SYSTEM) + doc2 (FIRM 99) | OK |
|
||||
| 2 | firm 99 + org 902 | doc1 + doc2 (perche membro firm) | OK |
|
||||
| 3 | firm 100 + org 903 | doc1 + doc3 (perche membro firm) | OK |
|
||||
| 4 | no firm, no org | solo doc1 (SYSTEM) | OK |
|
||||
|
||||
**Nessun cross-firm leak**: case 1 e 2 NON vedono doc3 (FIRM 100); case 3 NON vede doc2 (FIRM 99); case 4 vede solo SYSTEM.
|
||||
|
||||
### Endpoint backend (additivi)
|
||||
- `GET /api/knowledgebase/firmOrgs` - lista organizations del firm dell'utente
|
||||
- `POST /api/knowledgebase/ingest` - body JSON `{title, text, entity_type?, scope?, shared_with_orgs?, organization_id?}`
|
||||
- `GET /api/knowledgebase/list` - lista doc visibili
|
||||
- `POST /api/knowledgebase/search` - body `{query, top_k?}`
|
||||
- `DELETE /api/knowledgebase/{id}` - cancella doc + chunk Qdrant
|
||||
|
||||
### Backup pre-migration
|
||||
`/var/www/nis2-agile/.backups/kb_<timestamp>/` contiene: AIService.php, AuthController.php, public/index.php, docker/docker-compose.yml.
|
||||
|
||||
### Cosa NON e cambiato
|
||||
- AuthController/JWT (NIS2 ricarica gia' user dal DB in `requireAuth()`, quindi `consulting_firm_id` e' disponibile automaticamente in `currentUser`)
|
||||
- Tutti i controller esistenti (Risk, Asset, Incident, Policy, Whistleblowing, Feedback, ...)
|
||||
- AIService metodi esistenti (`analyzeGapAssessment`, `suggestRisks`, `generatePolicy`, `classifyIncident`, ...) - aggiunto solo `askWithRag()`
|
||||
- Nessun servizio nexus-* toccato
|
||||
- Schema esistente (organizations, users, assessments, ...) - solo ALTER ADD COLUMN consulting_firm_id
|
||||
|
||||
### Rollback
|
||||
1. mysql nis2_agile_db: `DROP TABLE kb_uploaded_documents; DROP TABLE firm_org_assignments; ALTER TABLE organizations DROP COLUMN consulting_firm_id; ALTER TABLE users DROP COLUMN consulting_firm_id; DROP TABLE consulting_firms;`
|
||||
2. Drop collection Qdrant: `curl -X DELETE http://nis2-qdrant:6333/collections/nis2_kb`
|
||||
3. Stop nis2-qdrant container: `docker compose stop qdrant && docker compose rm -f qdrant`
|
||||
4. Ripristinare file da `/var/www/nis2-agile/.backups/kb_<timestamp>/`
|
||||
5. `cd /var/www/nis2-agile/docker && docker compose up -d --force-recreate app`
|
||||
|
||||
## AgileHub — Agent AI Automatico (ticket-agent-cron)
|
||||
|
||||
> Un agent AI automatico (cron ogni 2 min) analizza i ticket aperti e propone/applica fix in questo container. Queste istruzioni sono per TUTTI i prompt Claude che lavorano in questo progetto.
|
||||
|
||||
### Semaforo: `/tmp/agent-working.lock`
|
||||
|
||||
Se il file `/tmp/agent-working.lock` esiste, un agent sta lavorando su un ticket. **NON modificare file del progetto** finche il semaforo e attivo — rischio conflitto.
|
||||
|
||||
Contenuto del lock: `TICKET_ID=13 PRODUCT=TRPG STARTED=2026-04-13T06:02:00+00:00`
|
||||
|
||||
### Log modifiche automatiche: `AGENT_CHANGES.md`
|
||||
|
||||
Il file `AGENT_CHANGES.md` nella root del progetto contiene il log di TUTTE le modifiche applicate dall'agent automatico. **Leggilo ad ogni sessione** per sapere cosa e cambiato dall'ultima volta che hai lavorato qui.
|
||||
|
||||
### Come funziona il flusso ticket
|
||||
|
||||
```
|
||||
1. Utente segnala problema (FAB supporto o voce)
|
||||
2. Ticket creato su AgileHub → status OPEN
|
||||
3. Agent (questo container) analizza il codice → propone fix (PLAN MODE, no modifiche)
|
||||
4. Supervisore approva/rifiuta dalla app mobile AgileHub
|
||||
5. Se approvato: agent applica il fix (BYPASS MODE) + aggiorna help/traduzioni/AI
|
||||
6. Se rifiutato: agent rianalizza con le indicazioni del supervisore
|
||||
```
|
||||
|
||||
### Regole per i prompt interattivi (come te)
|
||||
|
||||
1. **Prima di iniziare**: leggi `AGENT_CHANGES.md` per sapere cosa ha fatto l'agent di recente
|
||||
2. **Controlla il semaforo**: `cat /tmp/agent-working.lock` — se attivo, aspetta o lavora su altro
|
||||
3. **Dopo le tue modifiche**: se impattano funzionalita, aggiorna SEMPRE:
|
||||
- `app/js/help.js` (help online contestuale)
|
||||
- Traduzioni (IT + EN se il file e bilingue)
|
||||
- Knowledge base AI (product_knowledge via API AgileHub)
|
||||
4. **Non cancellare** `AGENT_CHANGES.md` — e il registro storico delle modifiche automatiche
|
||||
5. **Messaggi al ticket**: se stai lavorando su un ticket, manda aggiornamenti con:
|
||||
```
|
||||
curl -s -X POST http://172.18.0.1:4213/tickets/{ID}/message \
|
||||
-H "X-Internal-Key: nexus-internal-2026" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content":"[aggiornamento]","role":"AGENT"}'
|
||||
```
|
||||
|
||||
### API AgileHub (da dentro il container)
|
||||
|
||||
| Endpoint | Porta | Uso |
|
||||
|----------|-------|-----|
|
||||
| Ticket MS | `http://172.18.0.1:4213` | Ticket, routing rules, KB, support sessions |
|
||||
| Tenant MS | `http://172.18.0.1:4214` | Auth, login, utenti |
|
||||
| AI MS | `http://172.18.0.1:4211` | Sessioni AI, agent loop |
|
||||
| Dashboard | `https://agilehub.agile.software` | UI web |
|
||||
|
||||
|
||||
## REGOLA: SSO Single Sign-On (collegamento centralizzato)
|
||||
|
||||
> **Attivo dal 2026-04-15**. Ogni utente in questo prodotto ha un campo `sso_identity_id` nel DB che lo collega alla sua identita SSO centralizzata in AgileHub.
|
||||
|
||||
### Come funziona
|
||||
|
||||
- **`sso_identity_id`** nella tabella `users` = link stabile alla identita SSO
|
||||
- **`password_version`** nella tabella `users` = contatore versione password
|
||||
- Un **cron ogni 5 minuti** su Hetzner sincronizza `password_hash` e `password_version` dalla fonte SSO (`nexus_tenant_db.sso_identities`) al DB di questo prodotto
|
||||
- **Non serve nessuna chiamata HTTP** tra container — tutto avviene via DB
|
||||
|
||||
### Cosa significa per te (agent AI)
|
||||
|
||||
1. **NON modificare `sso_identity_id`** — e un campo gestito dal sistema SSO
|
||||
2. **NON modificare `password_version`** — e gestito dal cron sync
|
||||
3. Se un utente cambia password da AgileHub, entro 5 minuti la nuova password funziona anche qui
|
||||
4. Se modifichi il flusso di **cambio password** di questo prodotto, la modifica resta **solo locale** (non propaga agli altri prodotti)
|
||||
5. Per propagare un cambio password a tutti i prodotti, il prodotto deve chiamare:
|
||||
```
|
||||
POST http://172.18.0.1:4214/auth/sso/change-password
|
||||
Headers: Authorization: Bearer <jwt>, Content-Type: application/json
|
||||
Body: {"currentPassword": "...", "newPassword": "..."}
|
||||
```
|
||||
Ma attenzione: questa chiamata richiede connettivita di rete al Tenant MS (porta 4214)
|
||||
|
||||
### Schema DB
|
||||
|
||||
```sql
|
||||
-- Colonne aggiunte alla tabella users:
|
||||
sso_identity_id INT NULL -- FK verso nexus_tenant_db.sso_identities.id
|
||||
password_version INT DEFAULT 1 -- contatore, incrementa ad ogni cambio password SSO
|
||||
```
|
||||
|
||||
### Documentazione completa
|
||||
|
||||
- Spec SSO: `/projects/agile-services/docs/SPEC_SSO_SINGLE_SIGN_ON.md`
|
||||
- Istruzioni prodotti: `/projects/agile-services/docs/ISTRUZIONI_SSO_PRODOTTI.md`
|
||||
- Cron sync: `/projects/agile-services/scripts/sso-password-sync.sh`
|
||||
|
||||
|
||||
## REGOLA: Standard Versioning e Audit Trail
|
||||
|
||||
> **Standard centralizzato**: `GET http://172.18.0.1:4214/standards/standard_versioning` (sempre aggiornato)
|
||||
|
||||
**Regole obbligatorie:**
|
||||
1. Ogni prodotto ha un file `version.json` (`app/` o `public/`) con formato SemVer: `{"version":"1.0.0","build":"...","date":"...","changelog":"..."}`
|
||||
2. Il cron agent incrementa automaticamente il PATCH dopo ogni fix applicato
|
||||
3. Lo sviluppatore incrementa MINOR (nuova funzionalita) o MAJOR (breaking change) manualmente
|
||||
4. Ogni modifica software viene loggata nell audit trail: MAINTENANCE_ON/OFF, APPLY_START/END, VERSION_BUMP
|
||||
5. Il bug reporter include automaticamente la versione in ogni segnalazione
|
||||
6. **NON modificare version.json manualmente** durante un apply — il cron lo fa automaticamente
|
||||
|
||||
|
||||
## REGOLA: Timezone Italia (Europe/Rome)
|
||||
|
||||
> **Standard centralizzato**: `GET http://172.18.0.1:4214/standards/standard_timezone`
|
||||
|
||||
**Regole obbligatorie:**
|
||||
1. Tutti i container, script e servizi operano in timezone **Europe/Rome** (CET/CEST)
|
||||
2. Ogni script bash deve avere `export TZ=Europe/Rome` in testa
|
||||
3. I log devono mostrare ora italiana (leggibili senza conversioni)
|
||||
4. Il frontend mostra date con `toLocaleString("it-IT")` o `{ timeZone: "Europe/Rome" }`
|
||||
5. Il database salva in UTC — la conversione avviene in visualizzazione
|
||||
|
||||
|
||||
## REGOLA: Cron su crontab Hetzner
|
||||
|
||||
> **Standard**: `GET http://172.18.0.1:4214/standards/standard_cron`
|
||||
> **Registro**: `GET http://172.18.0.1:4214/standards/cron_registry`
|
||||
|
||||
**Regole obbligatorie per aggiungere un cron:**
|
||||
1. Script in `/var/www/<prodotto>/scripts/<nome>.sh`
|
||||
2. Log in `/var/log/<prodotto>-<nome>.log`
|
||||
3. `export TZ=Europe/Rome` in testa allo script
|
||||
4. Idempotente (rilanciabile senza danni)
|
||||
5. Isolamento: tocca solo risorse del proprio prodotto
|
||||
6. Aggiornare `docs/CRON_REGISTRY.md` in agile-services con la propria entry
|
||||
7. Richiesta di aggiunta al crontab root tramite agile-services (no modifiche dirette)
|
||||
|
||||
## GIT PUSH: Nuovo Flusso (aggiornato 2026-04-24)
|
||||
|
||||
> **IMPORTANTE**: dal 2026-04-24 il token Gitea NON e piu persistente nel container per motivi di sicurezza.
|
||||
|
||||
### Come fare git push
|
||||
|
||||
```bash
|
||||
# 1. Prima del push: imposta il token (cache 1h in memoria, NON su disco)
|
||||
git-login
|
||||
# (inserisci il Personal Access Token quando richiesto)
|
||||
|
||||
# 2. Ora puoi pushare
|
||||
git push origin main
|
||||
|
||||
# 3. Opzionale - cancella subito il token dalla cache
|
||||
git credential-cache exit
|
||||
```
|
||||
|
||||
### Perche questo cambio
|
||||
|
||||
Se un attaccante compromette questo container, NON trova piu il token Gitea salvato in `/root/.git-credentials`. Prima era in chiaro e avrebbe permesso push su tutti i 20 repository.
|
||||
|
||||
Ora il token:
|
||||
- NON e su disco
|
||||
- E in memoria per max 1 ora dopo git-login
|
||||
- Viene perso alla chiusura della sessione bash
|
||||
|
||||
### Se il token Gitea e stato compromesso
|
||||
|
||||
Rigenerarlo su Gitea: `git.certisource.it -> User Settings -> Applications -> Generate Token`
|
||||
|
||||
### Regola
|
||||
|
||||
**NON persistere MAI il token Gitea in file come `.git-credentials`, `.netrc`, script con password in chiaro.** Usa sempre `git-login` per la sessione corrente.
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Vault-Steward — Credenziali Centralizzate
|
||||
|
||||
> Guida completa: `/opt/devenv/VAULT_STEWARD.md` (montato ro nei container dev)
|
||||
|
||||
**Cosa cambia per questo progetto** (dal 2026-04-25):
|
||||
- Le chiavi API esterne (Anthropic, Voyage, Tavus, LiveKit, ecc.) NON vivono piu nel `.env` — sono nel vault-steward (container Docker su Hetzner) cifrate AES-256-GCM.
|
||||
- Il container del MS riceve le chiavi al boot tramite wrapper entrypoint (`/opt/devenv/scripts/vault-entrypoint.sh`) che fetcha dal vault e setta le env var prima di avviare apache/uvicorn/node.
|
||||
- **MS di questo progetto migrati**: nis2-app
|
||||
- **Token applicativo**: `VAULT_APP_TOKEN_<APP>` in `infrastructure/.env` (o equivalente)
|
||||
- **Dual-mode**: se vault giu, fallback automatico a `.env` esistente (no down).
|
||||
|
||||
**Verificare wrapper attivo**:
|
||||
```bash
|
||||
docker logs <container> 2>&1 | grep vault-entrypoint
|
||||
# atteso: [vault-entrypoint] Fetched N env vars from vault
|
||||
```
|
||||
|
||||
**Aggiungere un nuovo MS al vault** (riassunto):
|
||||
1. Migrare credenziali: `docker exec -e VAULT_VALUE=<v> vault-steward node /tmp/vault-repopulate.js tier1__<app>__<provider> <key>`
|
||||
2. Registrare app: `docker exec vault-steward node cli/vault-cli.js register-app <app> tier1__<app>__*` (salva token!)
|
||||
3. Modificare `docker-compose.yml`: aggiungi `entrypoint`, `command`, mount wrapper, env VAULT_*, network `vault-net`
|
||||
4. Recreate container: `docker compose up -d --force-recreate <service>`
|
||||
|
||||
**Limitazioni note**:
|
||||
- `docker exec <ms> env` mostra env Docker originali, NON le chiavi vault-injected. Per verifica usare `cat /proc/1/environ | tr "\0" "\n"` o test via PHP/HTTP request.
|
||||
|
||||
**Backup pre-vault**: `/root/vault-backup-20260424_185029.tar.gz`. Rollback compose: `cp <project>/docker-compose.yml.bak.20260425-vault <project>/docker-compose.yml && docker compose up -d --force-recreate <service>`.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## STANDARD AgileHub: marketing-tenant-provisioning v1.4 (adottato 2026-04-26)
|
||||
|
||||
Doc canonico: `docs/STANDARD_MARKETING_TENANT_PROVISIONING.md` (sha256 `1d7ffaa20fa376b6...`)
|
||||
|
||||
Standard cross-suite per provisioning tenant nel modulo Marketing AgileHub. Versione **v1.4** introduce nuovo blocco AWE `AC30_MarketingTenantProvision` per orchestrazione atomica del provisioning marketing tenant (tenant create + DNS Cloudflare + DKIM + API key + idempotency H7).
|
||||
|
||||
**Cosa impatta questo prodotto**: se in futuro questo prodotto attiva il modulo Marketing AgileHub per i suoi clienti, segui §4.X "Provisioning DKIM per Marketing module" + §16 commands rapidi. Workflow esempio orchestrazione: `nexus-marketing-ms/docs/examples/ac30-tenant-provision-workflow.json`.
|
||||
|
||||
Status adoption: acknowledged 2026-04-26.
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## STANDARD AgileHub: persona-conversational-rules v2.0 (acknowledged 2026-05-09)
|
||||
|
||||
> **Doc canonico autoritativo (AgileHub)**: `/var/www/agile-services/docs/STANDARD_PERSONA_CONVERSATIONAL_RULES.md` (sha256 `2bb0ebe4052b73fce752911db0665b1e3dcdeb673624529426624622caaae97f`)
|
||||
> **Copia locale di questo prodotto**: `docs/standards/STANDARD_PERSONA_CONVERSATIONAL_RULES.md`
|
||||
> **Registry**: `nexus_hub.hub_standards` id=15 v2.0 status=adopted, applies_to=`*`
|
||||
> **Owner standard**: Agile AI (governance) + VOX (TTS/voice runtime) + PRISMA (UI Editor) + VIGILE (codice etico + audit GDPR)
|
||||
|
||||
### Cosa è
|
||||
|
||||
Standard cross-suite **vincolante** per la governance delle **persone digitali AI** (chatbot, avatar conversazionali, assistenti vocali) della suite Agile Software. Versione 2.0 introduce il **modello concettuale Persona Digitale = Persona Umana**: ogni avatar/agente AI è governato con lo stesso rigore di un dipendente umano (CV, foto, voce, codice etico, performance review, dismissione graceful).
|
||||
|
||||
### Schema dichiarativo a 14 categorie (`agent_constraints`)
|
||||
|
||||
Tutte le regole conversazionali vivono in DB (NO hardcoding nei controller):
|
||||
|
||||
1. `product_naming` — come si chiama il prodotto (no inventare aliases)
|
||||
2. `tts_pronunciation` — pronuncia sigle (IPA + dizionario ElevenLabs)
|
||||
3. `topic_scope` — in/out scope + risposte canoniche
|
||||
4. `image_handling` — formato URL immagini RAG + divieti pronuncia path
|
||||
5. `topic_playbook` — mapping topic → script + filtro immagini
|
||||
6. `latency_optimization` — fast-path turni semplici
|
||||
7. `format` — vincoli output (max parole, no preamboli, ecc)
|
||||
8. `code_of_conduct` — codice etico AI persona-specifico (transparency/GDPR/no deception)
|
||||
9. `emotional_intelligence` — tono, archetipo, communication style
|
||||
10. `conversation_memory` — cosa ricorda + scope persistence + GDPR Art.17 erasure
|
||||
11. `escalation_policy` — quando/come passare a operatore umano
|
||||
12. `performance_metrics` — KPI conversazione (CSAT, resolution rate, escalation rate)
|
||||
13. `lifecycle_stage` — stage carriera (training/onboarding/operativa/review/dismissed)
|
||||
14. `demo_sequence` — sequenze guidate multi-topic auto-advance
|
||||
|
||||
### Lifecycle persona digitale HR-grade (6 fasi)
|
||||
|
||||
1. **Assunzione** — creazione via Persona Composer wizard 6-step (Phase E LIVE)
|
||||
2. **Onboarding** — formazione KB + skill assignment + smoke test 30 scenari
|
||||
3. **Operatività** — live in produzione, monitoring SLA + audit log
|
||||
4. **Growth** — espansione KB, retraining skill level (1-5)
|
||||
5. **Performance Review** — audit periodico VIGILE (CSAT, drift detection, breach scan)
|
||||
6. **Dismissione** — graceful: `active=false` + GDPR cascade erasure conversation history + tombstone audit
|
||||
|
||||
### Codice Etico AI — 9 principi vincolanti (Sez. 18 standard)
|
||||
|
||||
1. **Identity transparency** — dichiararsi AI quando esplicitamente chiesto
|
||||
2. **No deception** — vietato fingere umana / inventare fatti / consulenza autoritativa fuori scope
|
||||
3. **GDPR Art.13 disclosure** — disclosure su richiesta + apertura demo
|
||||
4. **GDPR Art.22** — escalation umana per decisioni con effetti giuridici
|
||||
5. **Voice clone consent doppio** — gate VIGILE (Phase G.A) per persona con `replica_id`
|
||||
6. **Scope refusal cortese** — no echo parole problematiche
|
||||
7. **Escalation loyale** — quando utente chiede umano, NO retention
|
||||
8. **Audit log obbligatorio** — turni sensibili (legale/medico/compliance) loggati ≥ 90gg
|
||||
9. **Sub-processor disclosure** — su richiesta, lista canonica (Anthropic/ElevenLabs/Tavus/...)
|
||||
|
||||
### Modello AgileHub: parallelismo umano-digitale
|
||||
|
||||
Ogni persona digitale ha mappatura 1:1 con un dipendente umano:
|
||||
|
||||
| Aspetto umano | Implementazione digitale |
|
||||
|---|---|
|
||||
| Nome+cognome | `agent_key` + `display_name` |
|
||||
| CV | `digital_persona_skills` (skill+level 1-5) |
|
||||
| Foto | `replica_id` Tavus o `avatar_image_url` |
|
||||
| Voce | `voice_id` ElevenLabs + pronunciation_dictionary |
|
||||
| Conoscenza | KB articles + RAG repository bindings (Phase D) |
|
||||
| Esperienza | conversation_stream auto-ingest RAG |
|
||||
| Codice etico | `code_of_conduct` constraint |
|
||||
| Performance review | `performance_metrics` + audit VIGILE Q1/Q2/Q3/Q4 |
|
||||
| Dimissioni | dismissione graceful + GDPR cascade |
|
||||
|
||||
### Cosa impatta NIS2 (Network and Information Security Directive)
|
||||
|
||||
Questo prodotto ha **1 persona digitale** governata da v2.0: **ARIA_SUPPORT_NIS2** (id=4) — assistente AI conversazionale supporto utenti NIS2. Stato: OPERATIVA in produzione.
|
||||
|
||||
### Stato adoption
|
||||
|
||||
`hub_standards_adoption` row INSERT 2026-05-09: `product_slug=NIS2`, `adoption_status=acknowledged` (riconoscimento standard senza migrazione persone proprie ancora). Implementation_notes: "Standard distribuito via INSTALLATORE pattern. Persone digitali del prodotto da migrare separatamente (Step 6 plan)."
|
||||
|
||||
### Cross-reference ad altri standard
|
||||
|
||||
- `installer-integration` v1.0 (id=1) — pattern distribuzione cross-suite
|
||||
- `rag-platform` v1.0 (id=10) — knowledge platform per personaggi (binding via `rag_entity_bindings`)
|
||||
- `gdpr-replica-consent` v1.0-DRAFT — consent doppio Phase G.A per voice clone
|
||||
- `vault-steward-credential-management` v1.0 (id=7) — gestione voice_id/replica_id come credentials
|
||||
|
||||
---
|
||||
|
||||
## STANDARD AgileHub: multitenant-architecture v1.0 (adottato 2026-05-17)
|
||||
|
||||
Doc canonico: `docs/STANDARD_MULTITENANT_ARCHITECTURE.md` (sha256 `85c174fca6f9f905c2f8171741cf7f40d778c10bdefad8d7a27412903abb4030`)
|
||||
|
||||
Standard cross-suite NAVIGAI per piattaforma multitenant esplicita di AgileHub. Aggiunge tenant context propagation (JWT claims tenant_id+tenant_slug+is_master+tier additivi), visibility ENUM cross-tabella, opt-out granulare client da catalog master, billing per-tenant, observability tenant-aware.
|
||||
|
||||
**Cosa impatta questo prodotto**: se in futuro questo prodotto chiamerà API multitenant-aware di AgileHub (es. /api/marketing, /api/rag, /api/ai/personas), deve passare JWT con tenant_id + tenant_slug claims oppure header `X-Tenant-Slug`. Vedi §6 contracts shared lib `@agile/tenant-auth` per pattern integrazione (Node + Python).
|
||||
|
||||
Status adoption: acknowledged 2026-05-17.
|
||||
|
||||
---
|
||||
|
||||
## Integrazione analisi `docs/nis2/` — v1.7.0 (2026-05-29)
|
||||
|
||||
> Integrati i mockup + testi normativi PDF in `docs/nis2/`. Dettaglio e comandi deploy: `docs/nis2/INTEGRAZIONE_COMPLETATA.md`. **Migrazioni 020-022 e ingest KB DA ESEGUIRE su Hetzner** (host MySQL, non `docker exec nis2-db`).
|
||||
|
||||
### Nuove migrazioni (additive, idempotenti)
|
||||
- `020_asset_relevance.sql` — assets += `relevance_score`, `relevance_criteria` JSON, `relevance_class`, `is_nis2_relevant`, `relevance_assessed_at/by`
|
||||
- `021_incident_nis2_taxonomy.sql` — incidents += `nis2_incident_type` ENUM(IS-1..IS-4), `entity_obligation` ENUM(essential/important)
|
||||
- `022_incident_metrics_pir.sql` — incidents += `triaged_at`/`contained_at`/`eradicated_at`/`recovered_at`; nuova tabella `incident_pir`
|
||||
|
||||
### Nuovi file
|
||||
- `application/config/nis2_sources.php` — **registry canonico FONTI NORMATIVE CERTE** (single source of truth AI + help)
|
||||
- `application/services/AssetScoringService.php` — scoring rilevanza NIS2 0-100 (6 criteri, GV.OC-04)
|
||||
- `scripts/ingest-nis2-sources.php` — ingest PDF normativi nella KB Qdrant `nis2_kb` scope SYSTEM
|
||||
|
||||
### Nuovi endpoint
|
||||
- Assets: `GET /api/assets/scoringGrid`, `POST /api/assets/{id}/score`, `GET /api/assets/relevantSystems`
|
||||
- Incidents: `GET /api/incidents/{id}/metrics`, `GET /api/incidents/{id}/pir`, `POST /api/incidents/{id}/pir`
|
||||
- Audit: `GET /api/audit/nistCsfMapping`, `GET /api/audit/relevantSystemsRegister` (registro GV.OC-04 stampabile)
|
||||
|
||||
### REGOLA: Fonti certe (AI + help)
|
||||
Ogni affermazione normativa di AI e help **deve citare** una fonte di `application/config/nis2_sources.php`.
|
||||
`AIService::authoritativeSourcesBlock()` è iniettato nei system prompt e **vieta riferimenti inventati**.
|
||||
|
||||
---
|
||||
|
||||
## 🔴 AZIONE RICHIESTA (da VIGILE AgileHub, 2026-06-09) — spostare EMAIL_MS_URL su path interno
|
||||
|
||||
**Motivo (security #4 cross-suite)**: AgileHub sta applicando l'**edge-strip** di `X-Internal-Key` sul 443 pubblico (`agilehub.agile.software`) — la chiave interna NON sarà più accettata da Internet. NIS2 oggi invia email via URL **pubblico** con `X-Internal-Key` → **dopo lo strip le email di NIS2 si rompono** (401) finché non passate al path interno.
|
||||
|
||||
**Path interno già pronto (AgileHub-side fatto)**: vhost mirror controllato su `:8081`, raggiungibile dalla subnet NIS2 (`ufw allow 172.21.0.0/16 → 8081`), `X-Internal-Key` onorata lì.
|
||||
|
||||
**Modifica da fare (NIS2-side, in finestra)**:
|
||||
1. In `/var/www/nis2-agile/.env` riga 24, cambiare:
|
||||
```
|
||||
# PRIMA:
|
||||
EMAIL_MS_URL=https://agilehub.agile.software/api/emails
|
||||
# DOPO:
|
||||
EMAIL_MS_URL=http://172.21.0.1:8081/api/emails
|
||||
```
|
||||
(`172.21.0.1` = gateway della rete `docker_nis2-network`; il path `/api/emails/*` è identico al pubblico, cambia solo host. `X-Internal-Key` resta invariata.)
|
||||
2. Recreate `nis2-app` (micro-downtime, vostra finestra).
|
||||
3. Verifica: inviare una email di prova → deve risultare `SENT` (no 401/000). Reachability rapida: `curl -o /dev/null -w "%{http_code}" -X POST http://172.21.0.1:8081/api/emails/send-raw -H "X-Internal-Key: $INTERNAL_EMAIL_KEY" -H "Content-Type: application/json" -d '{}'` → atteso **400** (auth ok, body vuoto), NON 000/401.
|
||||
|
||||
**Rollback** (~10s, possibile finché serve): rimettere `EMAIL_MS_URL` al valore pubblico + recreate. NB: dopo l'edge-strip il pubblico darà 401 → il path corretto è l'interno.
|
||||
|
||||
**Pattern cross-suite**: stesso identico per gli altri prodotti email-sending (TRPG già migrato su `172.20.0.1:8081`). Owner standard: `external-api-tenancy-model` (VIGILE).
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<!--META|doc_type=linea_guida_agile|title=Linea Guida: Modello SGSI Agile e Lavoro da Qualsiasi Luogo (Work-from-Anywhere)|status=approved|version=1.0-->
|
||||
|
||||
<h2>Linea Guida: Modello SGSI Agile e Lavoro da Qualsiasi Luogo</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire l'impostazione del Sistema di Gestione della Sicurezza delle Informazioni (SGSI) di <strong>Nuova Agile Technology srl</strong> secondo un approccio <strong>agile/lean</strong>, coerente con un'azienda cloud-native senza sede fisica. Questa Linea Guida orienta tutti gli altri documenti del SGSI.</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>L'ambito SGSI copre lo sviluppo software (licenze on-premise e SaaS), i dati dei clienti e le credenziali aziendali, trattati su <strong>servizi esclusivamente cloud</strong> (Aruba in Italia, Hetzner in UE, piattaforme AI). <strong>Non esiste un perimetro fisico aziendale</strong>: niente server in sede, niente data center proprietario. L'ambito si definisce sui <em>dati, gli account e i dispositivi</em>, non sui luoghi.</p>
|
||||
|
||||
<h3>3. Principi dell'approccio agile/lean</h3>
|
||||
<ul>
|
||||
<li><strong>Scope stretto</strong>: si protegge ciò che conta (codice, dati clienti, credenziali cloud), senza estendere controlli a perimetri inesistenti.</li>
|
||||
<li><strong>Documentazione minima e viva</strong>: regole brevi e pratiche, aggiornate spesso; niente burocrazia. Ogni documento è uno strumento operativo, non un adempimento.</li>
|
||||
<li><strong>Responsabilità condivisa col cloud</strong>: la sicurezza fisica dei data center e gran parte dell'infrastruttura sono responsabilità di Aruba/Hetzner; noi ci occupiamo di account, accessi, configurazioni, dati e dispositivi (controllo A.5.23 sicurezza nei servizi cloud, 27017/27018).</li>
|
||||
<li><strong>Secure-by-default</strong>: MFA ovunque, cifratura di default, SSO, account aziendali gestiti centralmente.</li>
|
||||
<li><strong>Evidenze automatizzate</strong>: la piattaforma SGSI registra controlli periodici e audit; si privilegiano prove generate dal sistema rispetto a registri manuali.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Il modello Work-from-Anywhere</h3>
|
||||
<p>Il luogo di lavoro può essere <strong>qualsiasi</strong>: casa, coworking, viaggio, all'aperto. La sicurezza non dipende dal luogo ma dal modo di lavorare:</p>
|
||||
<ul>
|
||||
<li>Ogni collaboratore usa <strong>un portatile e uno smartphone aziendali dedicati</strong>, cifrati e gestiti (MDM), con <strong>separazione netta tra strumenti privati e di lavoro</strong>.</li>
|
||||
<li><strong>Posta e servizi sono aziendali</strong>, amministrati dalla società: niente account personali per il lavoro.</li>
|
||||
<li>Le <strong>chiavi/credenziali di accesso al cloud</strong> hanno copie di sicurezza controllate (key escrow cifrato), così lo smarrimento di un dispositivo non causa perdita di accesso.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Come si applicano gli altri documenti</h3>
|
||||
<p>La <strong>Politica di Lavoro da Remoto e Mobilità (33)</strong> regola il comportamento da qualsiasi luogo; la <strong>Politica sui Dispositivi (34)</strong> regola portatili e smartphone; la <strong>Politica di Posta e Servizi (35)</strong> regola account ed email; la <strong>Procedura Chiavi Cloud (36)</strong> regola la custodia delle credenziali. Tutti discendono dai principi qui esposti.</p>
|
||||
|
||||
<h3>6. Ruoli</h3>
|
||||
<p>Vedi organigramma SGSI. <strong>Direzione/Presidente</strong>: Silvia Garretto. <strong>RSGSI</strong>: Massimo Tagliavini. <strong>Responsabile IT e Sicurezza</strong>: Simon Fattori. <strong>DPO</strong>: consulente esterno.</p>
|
||||
|
||||
<h3>7. Controlli ISO e riferimenti NIS2</h3>
|
||||
<p>Controlli collegati: A.6.7, A.7.9, A.8.1, A.5.10, A.5.23 (ISO/IEC 27001:2022; 27017/27018). NIS2 (D.Lgs. 138/2024): art.23 (governance), art.24 (misure di gestione del rischio). ISO è buona prassi, non obbligo di legge.</p>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,36 @@
|
||||
<!--META|doc_type=politica_remoto|title=Politica di Lavoro da Remoto e Mobilità|status=approved|version=1.0-->
|
||||
|
||||
<h2>Politica di Lavoro da Remoto e Mobilità</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Stabilire le regole per lavorare in sicurezza da <strong>qualsiasi luogo</strong> (casa, coworking, viaggio, all'aperto), poiché Nuova Agile Technology srl opera con modello Work-from-Anywhere e <strong>senza sede fissa né perimetro fisico</strong>.</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a tutti i collaboratori (dipendenti e P.IVA esterne) quando accedono a dati, codice o servizi aziendali, ovunque si trovino e con qualsiasi rete.</p>
|
||||
|
||||
<h3>3. Regole di lavoro da remoto</h3>
|
||||
<ul>
|
||||
<li><strong>Solo dispositivi aziendali</strong>: l'accesso a sistemi e dati avviene esclusivamente dal portatile e dallo smartphone aziendali (vedi Politica 34). Niente dispositivi privati per il lavoro.</li>
|
||||
<li><strong>Accesso sempre con MFA e SSO</strong>: ogni servizio cloud richiede autenticazione a più fattori; le sessioni hanno timeout automatico.</li>
|
||||
<li><strong>Reti non fidate</strong>: su Wi-Fi pubblici o di terzi usare sempre connessioni cifrate (HTTPS/TLS, eventuale VPN aziendale). Mai disattivare la cifratura.</li>
|
||||
<li><strong>Schermo e privacy fisica</strong>: in luoghi pubblici evitare la visibilità dello schermo (shoulder surfing); usare filtri privacy quando possibile; bloccare lo schermo allontanandosi (anche per pochi minuti).</li>
|
||||
<li><strong>Asset fuori sede</strong>: non lasciare dispositivi incustoditi in auto, mezzi o spazi pubblici; in viaggio tenerli sempre con sé o in luogo sicuro.</li>
|
||||
<li><strong>Nessun dato sensibile in chiaro fuori dai sistemi gestiti</strong>: niente download su supporti non cifrati, niente stampe non necessarie, niente copie su servizi personali.</li>
|
||||
<li><strong>Trasferimento informazioni</strong>: condividere dati solo tramite canali e strumenti aziendali approvati; evitare app di messaggistica personali per documenti di lavoro.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Furto, smarrimento o sospetta compromissione</h3>
|
||||
<ul>
|
||||
<li>Segnalare <strong>immediatamente</strong> al Responsabile IT e Sicurezza (Simon Fattori) lo smarrimento o furto di un dispositivo o un accesso anomalo.</li>
|
||||
<li>IT procede con <strong>blocco/wipe remoto</strong> via MDM e revoca/rotazione delle credenziali coinvolte.</li>
|
||||
<li>Le chiavi di accesso al cloud restano recuperabili grazie al key escrow controllato (vedi Procedura 36), quindi lo smarrimento del dispositivo non causa perdita di accesso.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Ruoli</h3>
|
||||
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): configura MFA/MDM, gestisce incidenti e wipe remoto. <strong>RSGSI</strong> (Massimo Tagliavini): vigila sull'applicazione. <strong>Collaboratori</strong>: rispettano la politica e segnalano gli incidenti. <strong>Direzione</strong> (Silvia Garretto): approva e sostiene la politica.</p>
|
||||
|
||||
<h3>6. Controlli ISO e riferimenti NIS2</h3>
|
||||
<p>ISO/IEC 27001:2022: A.6.7 (lavoro da remoto), A.7.9 (sicurezza degli asset fuori sede), A.8.1 (dispositivi endpoint), A.5.14 (trasferimento informazioni), A.8.5 (autenticazione sicura). NIS2 (D.Lgs. 138/2024): art.24 (igiene informatica, controllo accessi); incidenti rilevanti notificati al CSIRT Italia ai sensi dell'art.25.</p>
|
||||
|
||||
<h3>7. Riesame e versionamento</h3>
|
||||
<p>Documento vivo, riesaminato almeno annualmente o a fronte di incidenti o cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!--META|doc_type=politica_dispositivi|title=Politica sui Dispositivi Aziendali e Separazione Privato/Lavoro|status=approved|version=1.0-->
|
||||
|
||||
<h2>Politica sui Dispositivi Aziendali e Separazione Privato/Lavoro</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire come sono configurati, usati e protetti i dispositivi aziendali, garantendo una <strong>separazione netta tra strumenti privati e di lavoro</strong> in un modello senza sede fisica.</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a <strong>portatili e smartphone aziendali dedicati</strong> assegnati a ciascun collaboratore (dipendenti e P.IVA esterne), usati per accedere a dati, codice e servizi aziendali.</p>
|
||||
|
||||
<h3>3. Regole sui dispositivi</h3>
|
||||
<ul>
|
||||
<li><strong>Un portatile e uno smartphone aziendali per persona</strong>, dedicati e gestiti centralmente tramite <strong>MDM</strong>.</li>
|
||||
<li><strong>Uso solo professionale</strong>: i dispositivi aziendali si usano <strong>esclusivamente per il lavoro</strong>. Niente uso personale, niente account privati, niente app non necessarie installate fuori dal catalogo approvato.</li>
|
||||
<li><strong>Cifratura di default</strong>: disco e storage cifrati (full-disk encryption); smartphone cifrato e con blocco schermo obbligatorio (PIN/biometria).</li>
|
||||
<li><strong>Secure-by-default</strong>: aggiornamenti automatici di sistema e applicazioni, antimalware attivo, firewall locale, blocco schermo dopo inattività.</li>
|
||||
<li><strong>MDM e controllo remoto</strong>: IT può applicare policy, distribuire aggiornamenti e in caso di furto/smarrimento eseguire <strong>blocco o cancellazione remota (wipe)</strong>.</li>
|
||||
<li><strong>Nessuna credenziale in chiaro sul dispositivo</strong>: password e chiavi vivono nel password manager aziendale; nessun file di credenziali in chiaro sul portatile (vedi Procedura 36).</li>
|
||||
<li><strong>Nessun software non autorizzato</strong>: niente installazioni arbitrarie; le privilegi di amministratore sono limitati e concessi da IT.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Separazione privato/lavoro</h3>
|
||||
<ul>
|
||||
<li>I dati personali del collaboratore <strong>non risiedono</strong> sui dispositivi aziendali; i dati aziendali <strong>non risiedono</strong> sui dispositivi privati.</li>
|
||||
<li>Niente sincronizzazione con cloud personali (Drive/iCloud personali, foto, backup privati).</li>
|
||||
<li>Questa separazione tutela sia l'azienda sia la privacy del collaboratore: in caso di wipe remoto non si perdono dati personali, perché non devono esserci.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Restituzione e fine rapporto</h3>
|
||||
<p>Alla cessazione del rapporto o riassegnazione, il dispositivo è restituito, ripulito (wipe) e riconfigurato; gli accessi associati sono revocati e le chiavi ruotate.</p>
|
||||
|
||||
<h3>6. Ruoli</h3>
|
||||
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): gestisce MDM, cifratura, wipe e catalogo software. <strong>RSGSI</strong> (Massimo Tagliavini): vigila sulla conformità. <strong>Collaboratori</strong>: usano i dispositivi solo per il lavoro e segnalano anomalie. <strong>Direzione</strong> (Silvia Garretto): approva la politica.</p>
|
||||
|
||||
<h3>7. Controlli ISO e riferimenti NIS2</h3>
|
||||
<p>ISO/IEC 27001:2022: A.8.1 (dispositivi endpoint), A.7.9 (asset fuori sede), A.5.10 (uso accettabile di informazioni e asset), A.8.24 (crittografia). NIS2 (D.Lgs. 138/2024): art.24 (igiene informatica di base, crittografia).</p>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!--META|doc_type=politica_posta|title=Politica di Posta Elettronica e Servizi Aziendali|status=approved|version=1.0-->
|
||||
|
||||
<h2>Politica di Posta Elettronica e Servizi Aziendali</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Stabilire che <strong>posta elettronica e servizi sono aziendali</strong>, amministrati e controllati dalla società, e definire le regole d'uso degli account in un modello senza sede fisica.</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a tutte le caselle email, agli account dei servizi cloud (Aruba, Hetzner, piattaforme AI, repository, gestionali) e a ogni identità digitale usata per il lavoro da parte di dipendenti e P.IVA esterne.</p>
|
||||
|
||||
<h3>3. Regole su posta e servizi</h3>
|
||||
<ul>
|
||||
<li><strong>Account aziendali, non personali</strong>: il lavoro si svolge solo con caselle e account forniti e amministrati dalla società. <strong>Vietato usare account personali</strong> per attività lavorative.</li>
|
||||
<li><strong>Controllo della società</strong>: l'azienda amministra creazione, configurazione, sospensione e cancellazione degli account, con tutte le autorizzazioni di gestione (admin centralizzato, SSO).</li>
|
||||
<li><strong>Niente inoltro a caselle personali</strong>: è vietato l'auto-inoltro o l'inoltro manuale sistematico di email di lavoro verso indirizzi personali o esterni non autorizzati.</li>
|
||||
<li><strong>MFA obbligatoria</strong> su email e su tutti i servizi; password robuste gestite nel password manager aziendale (vedi Procedura 36).</li>
|
||||
<li><strong>SSO ove possibile</strong>: accesso ai servizi tramite Single Sign-On per centralizzare controllo e revoche.</li>
|
||||
<li><strong>Uso appropriato</strong>: la posta aziendale serve per scopi professionali; evitare contenuti personali, iscrizioni private o uso improprio.</li>
|
||||
<li><strong>Trasferimento informazioni</strong>: condividere dati riservati solo con destinatari autorizzati e tramite canali approvati; attenzione a phishing e mittenti non verificati.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Gestione del ciclo di vita degli account</h3>
|
||||
<ul>
|
||||
<li><strong>Onboarding</strong>: IT crea gli account con i privilegi minimi necessari (least privilege).</li>
|
||||
<li><strong>Variazioni</strong>: i diritti di accesso seguono il ruolo e sono rivisti periodicamente.</li>
|
||||
<li><strong>Offboarding</strong>: alla cessazione del rapporto gli account sono disattivati e le credenziali revocate tempestivamente; la casella resta sotto controllo della società.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Privacy e DPO</h3>
|
||||
<p>Il controllo degli account è finalizzato alla sicurezza e alla continuità del servizio, nel rispetto della normativa privacy; il <strong>DPO (consulente esterno)</strong> è coinvolto per gli aspetti di protezione dei dati personali.</p>
|
||||
|
||||
<h3>6. Ruoli</h3>
|
||||
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): amministra account, MFA, SSO e revoche. <strong>RSGSI</strong> (Massimo Tagliavini): vigila sull'applicazione. <strong>DPO</strong>: presidia la conformità privacy. <strong>Direzione</strong> (Silvia Garretto): approva la politica.</p>
|
||||
|
||||
<h3>7. Controlli ISO e riferimenti NIS2</h3>
|
||||
<p>ISO/IEC 27001:2022: A.5.10 (uso accettabile), A.5.14 (trasferimento informazioni), A.5.17 (informazioni di autenticazione), A.8.5 (autenticazione sicura), A.5.23 (sicurezza nei servizi cloud). NIS2 (D.Lgs. 138/2024): art.24 (controllo degli accessi, igiene informatica).</p>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!--META|doc_type=procedura_chiavi_cloud|title=Procedura di Custodia e Backup delle Chiavi di Accesso al Cloud|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Custodia e Backup delle Chiavi di Accesso al Cloud</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire come custodire e copiare in sicurezza le <strong>chiavi e credenziali di accesso ai servizi cloud</strong>, affinché lo smarrimento dell'unico dispositivo <strong>non causi mai la perdita di accesso</strong> ai servizi, senza creare nuovi rischi di esposizione.</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a password, chiavi API, chiavi SSH, token, certificati e codici di recupero MFA relativi ai servizi cloud aziendali (Aruba, Hetzner, piattaforme AI, repository, console di amministrazione).</p>
|
||||
|
||||
<h3>3. Principi (key escrow controllato)</h3>
|
||||
<ul>
|
||||
<li><strong>Copia di sicurezza obbligatoria</strong>: ogni chiave critica ha una copia di backup; perdere un dispositivo non deve significare perdere l'accesso.</li>
|
||||
<li><strong>Sempre cifrate</strong>: le copie sono cifrate; <strong>mai in chiaro</strong> e mai conservate sul portatile o su file locali non protetti.</li>
|
||||
<li><strong>Custodia centralizzata</strong>: le credenziali vivono in un <strong>gestore di segreti / password manager aziendale</strong> (cassaforte digitale), non in fogli, email o note personali.</li>
|
||||
<li><strong>Accesso ristretto e tracciato</strong>: solo persone autorizzate accedono; ogni accesso è registrato (audit log).</li>
|
||||
<li><strong>Recupero a doppia autorizzazione</strong>: il recupero o l'estrazione di chiavi critiche richiede l'<strong>approvazione di due persone</strong> (es. Responsabile IT + RSGSI), per evitare abusi.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Procedura operativa</h3>
|
||||
<ol>
|
||||
<li><strong>Generazione</strong>: alla creazione di una chiave/credenziale, registrarla subito nel password manager aziendale con etichetta del servizio e proprietario.</li>
|
||||
<li><strong>Backup dei codici MFA</strong>: salvare i codici di recupero MFA nella cassaforte digitale cifrata, mai sullo stesso dispositivo che genera l'OTP.</li>
|
||||
<li><strong>Backup periodico</strong>: la cassaforte digitale è inclusa nei backup cifrati; verificare periodicamente che le copie siano integre e ripristinabili.</li>
|
||||
<li><strong>Recupero</strong>: in caso di smarrimento dispositivo, il collaboratore richiede l'accesso; due autorizzatori approvano; le chiavi necessarie sono rese disponibili in modo sicuro.</li>
|
||||
<li><strong>Rotazione post-incidente</strong>: dopo furto/smarrimento o sospetta compromissione, ruotare (rigenerare) le credenziali coinvolte e aggiornare la cassaforte.</li>
|
||||
<li><strong>Dismissione</strong>: alla cessazione del rapporto, revocare gli accessi del collaboratore e trasferire/ruotare le chiavi di sua competenza.</li>
|
||||
</ol>
|
||||
|
||||
<h3>5. Divieti</h3>
|
||||
<ul>
|
||||
<li>Niente credenziali in chiaro su portatile, repository, email, chat o note personali.</li>
|
||||
<li>Niente condivisione di password fuori dal gestore di segreti.</li>
|
||||
<li>Niente backup delle chiavi su cloud personali o supporti non cifrati.</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Ruoli</h3>
|
||||
<p>Vedi organigramma. <strong>Responsabile IT e Sicurezza</strong> (Simon Fattori): gestisce il password manager, gli accessi e la rotazione; primo autorizzatore al recupero. <strong>RSGSI</strong> (Massimo Tagliavini): secondo autorizzatore e vigilanza. <strong>Collaboratori</strong>: registrano e proteggono le proprie credenziali. <strong>Direzione</strong> (Silvia Garretto): approva la procedura.</p>
|
||||
|
||||
<h3>7. Controlli ISO e riferimenti NIS2</h3>
|
||||
<p>ISO/IEC 27001:2022: A.5.17 (informazioni di autenticazione), A.8.24 (crittografia), A.8.13 (backup), A.8.5 (autenticazione sicura), A.5.23 (sicurezza nei servizi cloud). NIS2 (D.Lgs. 138/2024): art.24 (crittografia, controllo accessi, continuità).</p>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Documento vivo, riesaminato almeno annualmente o a fronte di incidenti o cambiamenti rilevanti; versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,67 @@
|
||||
<!--META|doc_type=manuale_sgsi|title=Manuale del Sistema di Gestione per la Sicurezza delle Informazioni (approccio agile)|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Il presente Manuale descrive il Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) di <strong>Nuova Agile Technology srl</strong> (di seguito "l'Organizzazione" o "l'Azienda") e ne definisce l'impostazione secondo un approccio <strong>agile/lean</strong>, coerente con un'azienda cloud-native senza sede fisica. È progettato e mantenuto in conformità a <strong>ISO/IEC 27001:2022</strong>, integrata dalle linee guida <strong>ISO/IEC 27017:2015</strong> (controlli di sicurezza per i servizi cloud) e <strong>ISO/IEC 27018:2019</strong> (protezione dei dati personali nel cloud pubblico). Il Manuale è il documento di vertice della struttura documentale del SGSI: ne illustra contesto, leadership, ambito, approccio al rischio e miglioramento continuo, e orienta tutti gli altri documenti del Sistema.</p>
|
||||
|
||||
<h2>2. Ambito del SGSI</h2>
|
||||
<p>Il SGSI si applica a tutti i processi di progettazione, sviluppo, erogazione e supporto dei servizi e prodotti software dell'Azienda, alle informazioni trattate (proprie e dei clienti, inclusi i dati personali e le credenziali aziendali) e a tutto il personale (dipendenti e collaboratori). Le due linee di business coperte sono:</p>
|
||||
<ul>
|
||||
<li><strong>Prodotti software in licenza d'uso</strong> installati on-premise sui server <em>dei clienti</em>;</li>
|
||||
<li><strong>Servizi SaaS multi-tenant</strong> erogati in cloud.</li>
|
||||
</ul>
|
||||
<p>L'infrastruttura è <strong>interamente in cloud</strong> presso <strong>Aruba S.p.A.</strong> (data center in Italia) e <strong>Hetzner Online GmbH</strong> (Germania, UE), con utilizzo di piattaforme di intelligenza artificiale tramite API (es. Anthropic). <strong>L'Azienda non gestisce alcun server in sede</strong> e non esiste un perimetro fisico aziendale: niente data center proprietario. L'ambito si definisce quindi sui <em>dati, gli account e i dispositivi</em>, non sui luoghi. Le postazioni di lavoro sono esclusivamente <strong>PC portatili cifrati</strong>. La sicurezza fisica dei data center è ereditata dai fornitori cloud secondo il modello di <em>responsabilità condivisa</em> (vedi A.5.23).</p>
|
||||
|
||||
<h2>3. Contesto dell'Organizzazione</h2>
|
||||
<p>Nuova Agile Technology srl è una software house italiana composta da circa 12 persone (9 dipendenti, 2 collaboratori esterni a P.IVA e la Presidente). I principali fattori di contesto interni ed esterni sono: dipendenza da fornitori cloud e da provider AI, requisiti contrattuali di sicurezza imposti dai clienti (supply chain), obblighi GDPR sui dati personali trattati nel SaaS e il quadro normativo NIS2. Le parti interessate rilevanti includono clienti, fornitori cloud/AI (sub-responsabili), personale, Autorità di controllo (Garante Privacy, ACN) e investitori.</p>
|
||||
|
||||
<h2>4. Principi dell'approccio agile/lean</h2>
|
||||
<ul>
|
||||
<li><strong>Scope stretto</strong>: si protegge ciò che conta (codice, dati clienti, credenziali cloud), senza estendere controlli a perimetri inesistenti.</li>
|
||||
<li><strong>Documentazione minima e viva</strong>: regole brevi e pratiche, aggiornate spesso; niente burocrazia. Ogni documento è uno strumento operativo, non un adempimento.</li>
|
||||
<li><strong>Responsabilità condivisa col cloud</strong>: la sicurezza fisica dei data center e gran parte dell'infrastruttura sono in capo ad Aruba/Hetzner; l'Azienda presidia account, accessi, configurazioni, dati e dispositivi (A.5.23, ISO 27017/27018).</li>
|
||||
<li><strong>Secure-by-default</strong>: MFA ovunque, cifratura di default, SSO, account aziendali gestiti centralmente.</li>
|
||||
<li><strong>Evidenze automatizzate</strong>: la piattaforma SGSI registra controlli periodici e audit; si privilegiano prove generate dal sistema rispetto a registri manuali.</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Il modello Work-from-Anywhere</h2>
|
||||
<p>Il luogo di lavoro può essere <strong>qualsiasi</strong>: casa, coworking, viaggio, all'aperto. La sicurezza non dipende dal luogo ma dal modo di lavorare:</p>
|
||||
<ul>
|
||||
<li>Ogni collaboratore usa <strong>un portatile e uno smartphone aziendali dedicati</strong>, cifrati e gestiti (MDM), con <strong>separazione netta tra strumenti privati e di lavoro</strong>.</li>
|
||||
<li><strong>Posta e servizi sono aziendali</strong>, amministrati dalla società: niente account personali per il lavoro.</li>
|
||||
<li>Le <strong>chiavi/credenziali di accesso al cloud</strong> hanno copie di sicurezza controllate (key escrow cifrato), così lo smarrimento di un dispositivo non causa perdita di accesso.</li>
|
||||
</ul>
|
||||
<p>Controlli ISO collegati a questo modello: <strong>A.6.7</strong> (lavoro a distanza), <strong>A.7.9</strong> (sicurezza degli asset fuori sede), <strong>A.8.1</strong> (dispositivi endpoint), <strong>A.5.10</strong> (uso accettabile), <strong>A.5.23</strong> (sicurezza nei servizi cloud).</p>
|
||||
|
||||
<h2>6. Ruoli e responsabilità (organigramma SGSI)</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità principali</th></tr>
|
||||
<tr><td>Alta Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Leadership, approvazione politiche, assegnazione risorse, riesame di direzione</td></tr>
|
||||
<tr><td>Responsabile SGSI (RSGSI)</td><td><strong>Massimo Tagliavini</strong></td><td>Gestione operativa del SGSI, risk management, audit interni, documentazione</td></tr>
|
||||
<tr><td>Responsabile IT e Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Sicurezza tecnica, gestione cloud, controllo accessi, gestione incidenti</td></tr>
|
||||
<tr><td>Referente Protezione Dati (DPO)</td><td><em>Consulente esterno (da nominare)</em></td><td>Conformità GDPR, pareri sul trattamento dei dati personali</td></tr>
|
||||
</table>
|
||||
<p>L'organigramma di dettaglio e la matrice RACI sono mantenuti nel registro dei ruoli del SGSI.</p>
|
||||
|
||||
<h2>7. Leadership e impegno della Direzione</h2>
|
||||
<p>L'Alta Direzione, nella persona della Presidente, garantisce l'impegno verso il SGSI mediante: definizione e approvazione della Politica per la Sicurezza delle Informazioni; integrazione del SGSI nei processi aziendali; messa a disposizione delle risorse; promozione del miglioramento continuo; comunicazione dell'importanza della sicurezza al personale. Tali impegni soddisfano i requisiti della clausola 5 di ISO/IEC 27001 e i controlli <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni) e <strong>A.5.4</strong> (Responsabilità della direzione).</p>
|
||||
|
||||
<h2>8. Struttura documentale del SGSI</h2>
|
||||
<p>La documentazione è organizzata su livelli gerarchici: (1) Manuale SGSI e Politica generale; (2) Politiche tematiche (controllo accessi, crittografia, classificazione, uso accettabile, lavoro da remoto e mobilità, dispositivi, posta e servizi, ecc.); (3) Procedure e istruzioni operative (incl. procedura chiavi cloud); (4) Registrazioni ed evidenze. Tutti i documenti sono soggetti a controllo di versione, approvazione e revisione periodica (A.5.37) e discendono dai principi agile/lean qui esposti.</p>
|
||||
|
||||
<h2>9. Approccio al rischio</h2>
|
||||
<p>Il SGSI adotta un processo di valutazione e trattamento del rischio basato su ISO/IEC 27005, che prevede identificazione degli asset informativi e dei relativi rischi, analisi di probabilità e impatto, definizione del rischio accettabile e selezione delle opzioni di trattamento (mitigazione, trasferimento, accettazione, eliminazione). Particolare attenzione è posta ai rischi della <strong>supply chain cloud/AI</strong> e ai rischi sui dati personali. Gli esiti sono documentati nel Registro dei Rischi e riferiti alla <strong>Dichiarazione di Applicabilità (SoA)</strong>.</p>
|
||||
|
||||
<h2>10. Dichiarazione di Applicabilità (SoA)</h2>
|
||||
<p>La SoA elenca i controlli dell'<strong>Annex A di ISO/IEC 27001:2022</strong> (93 controlli su 4 temi: organizzativi, persone, fisici, tecnologici), integrati dai controlli aggiuntivi di <strong>ISO/IEC 27017</strong> e <strong>27018</strong> per i servizi cloud e i dati personali. Per ciascun controllo sono indicati applicabilità, stato di implementazione e giustificazione delle esclusioni.</p>
|
||||
|
||||
<h2>11. Caveat sulla classificazione NIS2</h2>
|
||||
<p>L'Azienda si autoclassifica <strong>in via PRELIMINARE</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>". Tuttavia l'Azienda è <strong>SOTTO le soglie dimensionali ordinarie</strong> (≥50 addetti oppure fatturato/bilancio >10 M€): la qualifica deve essere <strong>CONFERMATA dalla Direzione e dal Legale</strong>, ed eventualmente da ACN, in funzione del fatturato e della categoria di attività (provider di servizi cloud). L'adozione del SGSI è guidata anche dai <strong>requisiti di supply chain dei clienti</strong>. Il quadro di riferimento è la Direttiva (UE) 2022/2555, recepita in Italia dal <strong>D.Lgs. 138/2024</strong>: art.23 (governance e ruolo degli organi di gestione), art.24 (misure di gestione del rischio, equivalenti all'Art.21 della Direttiva), art.25 (obblighi di notifica al CSIRT, con tempistiche di pre-notifica entro 24 ore, notifica entro 72 ore e relazione finale entro 1 mese). Le misure di dettaglio sono definite dalle Determinazioni ACN. <strong>Le norme ISO citate sono buone prassi, non obblighi di legge.</strong></p>
|
||||
|
||||
<h2>12. Miglioramento continuo</h2>
|
||||
<p>Il SGSI segue il ciclo PDCA. Sono previsti audit interni programmati, riesami di direzione almeno annuali, gestione delle non conformità e azioni correttive (NCR/CAPA), e monitoraggio di indicatori di prestazione. Gli esiti alimentano il piano di miglioramento.</p>
|
||||
|
||||
<h2>13. Registrazioni ed evidenze</h2>
|
||||
<p>Verbali di riesame di direzione, registro dei rischi, SoA, rapporti di audit, registro delle non conformità, log di sicurezza e registro degli incidenti.</p>
|
||||
|
||||
<h2>14. Riesame e versionamento</h2>
|
||||
<p>Documento di vertice approvato dalla Presidente. Documento vivo, riesaminato almeno annualmente o a fronte di cambiamenti significativi del contesto; versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,93 @@
|
||||
<!--META|doc_type=procedura_continuita|title=Procedura di Continuità Operativa, Backup e Ripristino|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce in modo unitario come <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") assicura la <strong>continuità operativa</strong> dei propri servizi critici, esegue e protegge i <strong>backup</strong> e garantisce il <strong>ripristino</strong> dei dati e dei servizi in caso di evento avverso (guasto cloud, attacco — es. ransomware —, errore, cancellazione accidentale, indisponibilità di personale). Considerata l'infrastruttura <strong>interamente in cloud</strong> e l'assenza di server in sede, l'obiettivo è garantire backup affidabili, capacità di ripristino del <strong>SaaS multi-tenant</strong> e dei dati dei clienti, e la continuità del lavoro di un team piccolo che opera da <strong>PC portatili cifrati</strong>, nel rispetto degli obiettivi di tempo (RTO) e di perdita dati (RPO) concordati. La procedura supporta gli obblighi di <strong>continuità operativa, gestione del backup e gestione delle crisi</strong> previsti dalle misure dell'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica alla disponibilità del servizio <strong>SaaS multi-tenant</strong>, ai database e ai dati dei clienti, al codice sorgente e alle configurazioni dei prodotti, alla posta e ai documenti aziendali, agli ambienti di sviluppo e rilascio e alla capacità di lavoro del team. L'infrastruttura è ospitata su <strong>Aruba S.p.A. (Italia)</strong> e <strong>Hetzner (Germania, UE)</strong>, nel rispetto del modello di responsabilità condivisa. Riguarda tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) e i fornitori cloud. Gli <strong>endpoint</strong> (PC portatili cifrati) non conservano dati critici come unica copia: i dati di lavoro risiedono nei servizi cloud aziendali. <strong>Non esistono backup su nastro né server in sede</strong>; gli scenari considerati riguardano indisponibilità di un provider o di una regione cloud, attacchi, errori gravi e indisponibilità del personale, non scenari di sala server in sede (assente per architettura).</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.5.29 (sicurezza delle informazioni durante un'interruzione), A.5.30 (pronto intervento ICT per la continuità operativa), A.8.13 (backup delle informazioni), A.8.14 (ridondanza delle strutture di elaborazione), A.8.24 (uso della crittografia).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> – continuità e responsabilità condivisa nei servizi cloud.</li>
|
||||
<li><strong>ISO/IEC 27018:2019</strong> – conservazione, restituzione e ripristino dei dati personali (PII) nel cloud.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, inclusi continuità operativa, backup e gestione delle crisi); art. 25 (notifica degli incidenti significativi).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva strategia di continuità e backup, gli obiettivi RTO/RPO; attiva lo stato di crisi, decide la comunicazione esterna e l'allocazione delle risorse.</td></tr>
|
||||
<tr><td>RSGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Mantiene la procedura e il piano BCP/DR, coordina i test di ripristino e le esercitazioni, ne verifica e documenta gli esiti e i riesami.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Configura, esegue e monitora i backup; gestisce ridondanza e immutabilità; esegue ripristini e test, e il recovery tecnico dei servizi e dei dati secondo il piano.</td></tr>
|
||||
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta gli impatti sui dati personali negli scenari di indisponibilità o perdita.</td></tr>
|
||||
<tr><td>Tutto il personale</td><td>—</td><td>Segue le procedure di backup degli strumenti di lavoro e collabora alla continuità.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Strategia di continuità</h2>
|
||||
<h3>5.1 Analisi di impatto e obiettivi (RTO/RPO)</h3>
|
||||
<p>I servizi critici (in primis il SaaS e i dati dei clienti) sono individuati tramite <strong>Business Impact Analysis (BIA)</strong>; per ciascuno si definiscono <strong>RTO</strong> (tempo massimo di ripristino) e <strong>RPO</strong> (massima perdita di dati tollerata), approvati dalla Direzione. I valori puntuali sono definiti nelle procedure operative e nel piano BCP/DR <strong>[DA VERIFICARE]</strong>. Si identificano le dipendenze critiche: provider cloud, provider AI, DNS, posta, gestore password.</p>
|
||||
<h3>5.2 Strategie di continuità e ridondanza</h3>
|
||||
<ul>
|
||||
<li>Si privilegiano architetture <strong>ridondate</strong> e, ove fattibile, la possibilità di ripristino su una <strong>regione/fornitore alternativo</strong> (A.8.14), sfruttando i backup cloud cifrati e, ove disponibile, immutabili.</li>
|
||||
<li>Si valutano le garanzie di disponibilità (SLA) dei provider e si predispongono misure per ridurre l'impatto di un'indisponibilità prolungata, inclusa la <strong>portabilità dei dati</strong>; la ripartizione delle responsabilità di continuità tra azienda e fornitore è documentata (modello di responsabilità condivisa, ISO 27017).</li>
|
||||
<li>Essendo il team interamente remoto su portatili cifrati, la <strong>continuità del lavoro</strong> è intrinsecamente resiliente all'indisponibilità di una singola sede; i dati di lavoro essenziali sono sincronizzati sui servizi cloud aziendali per evitarne la perdita in caso di guasto o furto del dispositivo.</li>
|
||||
</ul>
|
||||
<h3>5.3 Continuità delle persone chiave</h3>
|
||||
<p>Trattandosi di un team piccolo, sono identificate le competenze critiche e previste misure di ridondanza minime (documentazione, accessi di backup custoditi in sicurezza, condivisione delle conoscenze) per evitare single point of failure umani.</p>
|
||||
|
||||
<h2>6. Backup (regola 3-2-1 su cloud)</h2>
|
||||
<h3>6.1 Pianificazione</h3>
|
||||
<ol>
|
||||
<li>Per ciascun servizio critico si definiscono <strong>RPO</strong> e <strong>RTO</strong> coerenti con la BIA, approvati dalla Direzione.</li>
|
||||
<li>Si adotta lo schema di riferimento <strong>3-2-1</strong> per quanto applicabile in cloud: più copie, su servizi/regioni distinti (preferibilmente data center diversi nell'UE), con almeno una copia logicamente separata e protetta da modifiche.</li>
|
||||
</ol>
|
||||
<h3>6.2 Esecuzione</h3>
|
||||
<ol>
|
||||
<li>I backup dei database SaaS, dei dati dei clienti e del codice sorgente sono <strong>automatici e schedulati</strong> (frequenza coerente con l'RPO, di norma giornaliera).</li>
|
||||
<li>Le copie sono <strong>cifrate</strong> a riposo (A.8.24) e conservate in cloud con accesso ristretto e <strong>MFA</strong>, con gli stessi controlli di accesso dei dati di produzione.</li>
|
||||
<li>Ove disponibile si attiva l'<strong>immutabilità</strong> delle copie (protezione anti-ransomware) e la separazione delle credenziali di gestione dei backup.</li>
|
||||
<li>Si applica una <strong>politica di retention e rotazione</strong> definita (copie giornaliere a breve termine e copie a più lunga conservazione), nel rispetto degli obblighi sui dati personali (minimizzazione e cancellazione sicura, A.8.13 / ISO 27018).</li>
|
||||
</ol>
|
||||
<h3>6.3 Monitoraggio</h3>
|
||||
<ol>
|
||||
<li>Il Resp. IT/Sicurezza verifica l'<strong>esito di ogni job</strong> di backup; i fallimenti generano un alert e una verifica entro le 24 ore lavorative successive.</li>
|
||||
<li>Gli esiti negativi ricorrenti sono registrati come <strong>non conformità</strong> nel modulo NCR/CAPA della piattaforma.</li>
|
||||
</ol>
|
||||
|
||||
<h2>7. Ripristino e test</h2>
|
||||
<h3>7.1 Test di ripristino</h3>
|
||||
<ul>
|
||||
<li>Con cadenza <strong>almeno trimestrale</strong> si esegue un <strong>test di ripristino</strong> su un dato/servizio campione, verificando integrità e tempi rispetto a RTO/RPO. <strong>Un backup non testato non è considerato affidabile.</strong></li>
|
||||
<li>L'esito è documentato; gli scostamenti attivano azioni correttive nel modulo NCR/CAPA.</li>
|
||||
</ul>
|
||||
<h3>7.2 Ripristino reale</h3>
|
||||
<p>In caso di incidente, il ripristino è autorizzato e coordinato secondo la presente procedura: si privilegia una copia integra e verificata, si rispettano RTO/RPO e al termine si validano i dati ripristinati.</p>
|
||||
|
||||
<h2>8. Gestione della crisi (BCP/DR)</h2>
|
||||
<h3>8.1 Attivazione e gestione</h3>
|
||||
<ol>
|
||||
<li>Al verificarsi di un evento grave, la Direzione (o suo delegato) <strong>attiva il piano</strong> e nomina i referenti.</li>
|
||||
<li>Si attiva in parallelo la <strong>gestione degli incidenti</strong>; il Resp. IT/Sicurezza esegue il ripristino dei servizi e dei dati dalle copie integre, rispettando RTO/RPO.</li>
|
||||
<li>Se l'evento configura un <strong>incidente significativo NIS2</strong> e l'Azienda è soggetto obbligato, si applicano gli obblighi di <strong>notifica al CSIRT Italia (ACN)</strong> ai sensi dell'art. 25 del D.Lgs. 138/2024: <strong>pre-allarme entro 24 ore</strong>, <strong>notifica completa entro 72 ore</strong>, <strong>relazione finale entro 1 mese</strong>.</li>
|
||||
</ol>
|
||||
<h3>8.2 Comunicazione</h3>
|
||||
<p>Si comunica <strong>tempestivamente ai clienti impattati</strong> lo stato del disservizio e i tempi stimati di ripristino, in adempimento agli obblighi contrattuali di supply chain verso i clienti NIS2.</p>
|
||||
<h3>8.3 Ritorno alla normalità</h3>
|
||||
<p>Verificato il pieno ripristino, si dichiara la chiusura della crisi e si redige il rapporto di evento con le <strong>lezioni apprese</strong>, che aggiornano il piano e alimentano azioni correttive. Il piano BCP/DR è <strong>testato almeno una volta l'anno</strong> (esercitazione/simulazione).</p>
|
||||
|
||||
<h2>9. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p>La procedura attua i controlli <strong>A.5.29, A.5.30, A.8.13, A.8.14, A.8.24</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018 per continuità, ripristino e protezione dei PII. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di backup, continuità e gestione delle crisi; quando l'interruzione configura un incidente significativo valgono le tempistiche di notifica dell'<strong>art. 25</strong>. Le evidenze dei test di ripristino dimostrano resilienza anche ai clienti NIS2 nell'ambito della supply chain.</p>
|
||||
|
||||
<h2>10. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Piano BCP/DR con BIA, RTO/RPO per servizio e dipendenze critiche.</li>
|
||||
<li>Piano di backup; log/report di esecuzione e di esito dei job.</li>
|
||||
<li>Rapporti dei test di ripristino trimestrali e delle esercitazioni annuali.</li>
|
||||
<li>Rapporti degli eventi reali con lezioni apprese e comunicazioni ai clienti durante i disservizi.</li>
|
||||
<li>Inventario dei servizi critici e relative dipendenze cloud.</li>
|
||||
<li>Non conformità collegate a fallimenti di backup/ripristino (modulo NCR/CAPA).</li>
|
||||
</ul>
|
||||
|
||||
<h2>11. Riesame e versionamento</h2>
|
||||
<p>La procedura è riesaminata <strong>almeno annualmente</strong> o a fronte di un incidente significativo, di un test di ripristino con esito negativo o di cambiamenti dell'architettura cloud o dei requisiti RTO/RPO. È approvata dalla Direzione; le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI secondo la Procedura di Governance del SGSI.</p>
|
||||
@@ -0,0 +1,118 @@
|
||||
<!--META|doc_type=procedura_operativa_sviluppo|title=Procedura di Gestione Operativa e Sviluppo Sicuro|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura stabilisce come <strong>Nuova Agile Technology srl</strong> integra la sicurezza nel ciclo di vita dello sviluppo software (Secure SDLC), governa i cambiamenti ai propri sistemi, gestisce le vulnerabilità tecniche e le patch, e presidia logging e monitoraggio. L'obiettivo è garantire che i prodotti — sia in <strong>licenza d'uso installati sui server dei clienti (on-premise)</strong>, sia il <strong>SaaS multi-tenant in cloud</strong> — siano progettati, sviluppati, rilasciati e mantenuti in modo da ridurre vulnerabilità e proteggere i dati, e che ogni cambiamento avvenga in modo controllato e tracciabile. Soddisfa i requisiti di sicurezza nell'acquisizione, sviluppo e manutenzione previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutte le attività di analisi, progettazione, codifica, test, rilascio e manutenzione del software, e ai relativi cambiamenti, vulnerabilità, patch e log su: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), configurazioni di rete e di sicurezza, servizi <strong>SaaS</strong> multi-tenant e prodotti <strong>on-premise</strong> presso i clienti, dipendenze e librerie di terze parti, pipeline di build/deploy, integrazioni con <strong>piattaforme AI</strong> via API (es. LLM Anthropic) ed <strong>endpoint</strong> (PC portatili cifrati). Riguarda tutto il personale tecnico (9 dipendenti, 2 collaboratori esterni a P.IVA) ed eventuali fornitori che contribuiscono allo sviluppo. L'attività si svolge esclusivamente da PC portatili cifrati, senza alcun server in sede.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – Annex A: A.5.7 (threat intelligence), A.8.7 (protezione dai malware), A.8.8 (gestione delle vulnerabilità tecniche), A.8.9 (gestione della configurazione), A.8.15 (logging), A.8.16 (monitoraggio), A.8.25–A.8.33 (sviluppo sicuro, requisiti applicazioni, architettura e codifica sicura, test, sviluppo esterno, separazione ambienti, gestione cambiamenti, dati di test), A.5.17 (autenticazione).</li>
|
||||
<li>ISO/IEC 27017:2015 e ISO/IEC 27018:2019 – sicurezza dello sviluppo, dei cambiamenti e delle vulnerabilità nei servizi cloud e protezione dei PII (privacy by design).</li>
|
||||
<li>D.Lgs. 4 settembre 2024, n. 138 (NIS2): art. 24 (sicurezza nell'acquisizione/sviluppo/manutenzione, gestione delle modifiche e divulgazione delle vulnerabilità) e art. 25 (notifica incidenti significativi).</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR), artt. 25 e 32 (privacy by design e by default, sicurezza del trattamento).</li>
|
||||
<li>Procedura di Gestione degli Incidenti dell'Azienda.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente / Direzione (Silvia Garretto)</td><td>Approva la procedura e assegna le risorse; autorizza i cambiamenti ad alto impatto e le finestre di rilascio critiche; approva tempistiche di rimedio e deroghe motivate.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la procedura, verifica che i cambiamenti e le vulnerabilità rilevanti siano valutati nel rischio e documentati, controlla il rispetto dei tempi di rimedio e registra le non conformità.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Definisce gli standard tecnici; gestisce code review, analisi e prioritizzazione delle vulnerabilità, patch e test; valuta e attua i cambiamenti con il piano di rollback; esegue la revisione di log e accessi.</td></tr>
|
||||
<tr><td>Sviluppatori (dipendenti e collaboratori)</td><td>Applicano le regole di codifica sicura, eseguono test e gestiscono le segnalazioni di vulnerabilità.</td></tr>
|
||||
<tr><td>Richiedente del cambiamento</td><td>Apre la richiesta di cambiamento (RFC) descrivendo finalità e impatto atteso.</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Valida i requisiti di privacy by design quando si trattano dati personali.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Sviluppo sicuro (Secure SDLC)</h2>
|
||||
<h3>5.1 Requisiti e progettazione sicura</h3>
|
||||
<ul>
|
||||
<li>I requisiti di sicurezza e privacy sono definiti già in fase di analisi, considerando il modello di minaccia, la separazione tra tenant nel SaaS e l'isolamento delle istanze on-premise.</li>
|
||||
<li>Si applicano i principi di <strong>privacy by design e by default</strong> (GDPR artt. 25 e 32) e di minimizzazione dei dati.</li>
|
||||
</ul>
|
||||
<h3>5.2 Codifica sicura e dipendenze</h3>
|
||||
<ul>
|
||||
<li>Si seguono regole di secure coding per prevenire le vulnerabilità più comuni (injection, autenticazione/sessione, controllo accessi, gestione errori, esposizione di dati).</li>
|
||||
<li>I segreti (credenziali, chiavi API, incluse quelle dei servizi AI) non sono mai inseriti nel codice né nei repository: si usano meccanismi di gestione segreti dedicati. Gli ambienti di sviluppo risiedono su PC portatili cifrati con accesso autenticato.</li>
|
||||
<li>Le librerie e i componenti open source sono censiti e tenuti aggiornati; si monitorano gli avvisi di vulnerabilità note e si applicano le patch in tempi proporzionati al rischio.</li>
|
||||
</ul>
|
||||
<h3>5.3 Test di sicurezza e separazione degli ambienti</h3>
|
||||
<ul>
|
||||
<li>Sono previsti test funzionali e di sicurezza prima del rilascio; per le componenti più esposte si eseguono verifiche statiche/dinamiche e, ove opportuno, test di penetrazione. Gli ambienti di test usano dati anonimizzati o sintetici (mai PII reali dei clienti).</li>
|
||||
<li>Ambienti di sviluppo, test e produzione sono separati (A.8.31); il codice è versionato e le modifiche sono sottoposte a code review tra pari prima dell'integrazione.</li>
|
||||
</ul>
|
||||
<h3>5.4 Componenti di intelligenza artificiale</h3>
|
||||
<p>L'uso di API LLM esterne è valutato per i rischi su confidenzialità dei dati inviati e affidabilità degli output; i dati personali e riservati inviati ai servizi AI sono minimizzati e protetti coerentemente con i contratti e i DPA dei fornitori.</p>
|
||||
|
||||
<h2>6. Gestione dei cambiamenti</h2>
|
||||
<ol>
|
||||
<li><strong>Richiesta (RFC).</strong> Il richiedente apre una richiesta di cambiamento descrivendo obiettivo, sistemi coinvolti, impatto atteso e urgenza.</li>
|
||||
<li><strong>Valutazione di impatto e rischio.</strong> Simon Fattori classifica il cambiamento (standard a basso rischio / normale / emergenza) e ne valuta gli effetti su sicurezza, dati personali e continuità; i cambiamenti rilevanti sono registrati nel <strong>modulo "Rischi"</strong> ove pertinente.</li>
|
||||
<li><strong>Autorizzazione.</strong> I cambiamenti standard a basso rischio sono approvati da Simon Fattori; quelli normali ad alto impatto richiedono l'autorizzazione della Direzione. Va sempre definito un <strong>piano di rollback</strong>.</li>
|
||||
<li><strong>Test in ambiente separato.</strong> Le modifiche software sono validate in ambienti di sviluppo/test separati dall'esercizio (A.8.31), con test funzionali e di sicurezza (A.8.29) prima del rilascio.</li>
|
||||
<li><strong>Attuazione.</strong> Il cambiamento è applicato in una finestra concordata; per il SaaS si privilegiano rilasci controllati. Per i prodotti on-premise, l'azienda fornisce ai clienti note di rilascio e indicazioni di aggiornamento, nel rispetto degli obblighi contrattuali di supply chain.</li>
|
||||
<li><strong>Verifica post-cambiamento.</strong> Si verifica il corretto funzionamento e l'assenza di effetti collaterali; in caso di esito negativo si attiva il rollback.</li>
|
||||
<li><strong>Chiusura.</strong> Esito e documentazione sono registrati; eventuali anomalie alimentano <strong>non conformità</strong> e azioni correttive.</li>
|
||||
</ol>
|
||||
<h3>6.1 Cambiamenti di emergenza</h3>
|
||||
<p>Per cambiamenti urgenti (es. mitigazione di una vulnerabilità critica), l'attuazione può precedere l'autorizzazione formale, ma <strong>deve essere documentata a posteriori entro il giorno lavorativo successivo</strong> e ratificata dalla Direzione.</p>
|
||||
|
||||
<h2>7. Gestione delle vulnerabilità e delle patch</h2>
|
||||
<h3>7.1 Identificazione</h3>
|
||||
<ul>
|
||||
<li>Simon Fattori monitora con continuità le fonti di vulnerabilità: bollettini dei fornitori cloud, avvisi CSIRT/ACN, advisory delle librerie/dipendenze usate dai prodotti, scansioni periodiche e avvisi automatici delle piattaforme (A.5.7, A.8.16).</li>
|
||||
<li>Si mantiene un inventario aggiornato dei componenti software e delle dipendenze per correlare rapidamente le vulnerabilità ai sistemi interessati.</li>
|
||||
</ul>
|
||||
<h3>7.2 Valutazione, prioritizzazione e tempi di rimedio</h3>
|
||||
<ul>
|
||||
<li>Ogni vulnerabilità è valutata per <strong>gravità</strong> (es. punteggio CVSS), <strong>esposizione</strong> e <strong>impatto</strong> su dati e servizi, e registrata se rilevante nel <strong>modulo "Rischi"</strong>.</li>
|
||||
<li>Si assegna una priorità con relativi <strong>tempi target di rimedio</strong>, ad esempio: critiche <strong>entro 48–72 ore</strong>, alte <strong>entro 7 giorni</strong>, medie <strong>entro 30 giorni</strong>, basse alla successiva finestra pianificata. <em>[I valori esatti sono da confermare nel piano di trattamento del rischio e negli SLA interni.]</em></li>
|
||||
</ul>
|
||||
<h3>7.3 Rimedio (applicazione delle patch)</h3>
|
||||
<ul>
|
||||
<li>L'applicazione delle patch segue la gestione dei cambiamenti (test in ambiente separato e piano di rollback); per le vulnerabilità critiche si attiva il percorso di cambiamento di emergenza. Dove la patch non è immediatamente disponibile si adottano <strong>misure compensative</strong> (restrizione accessi, isolamento, disattivazione della funzione vulnerabile).</li>
|
||||
<li>Aggiornamenti automatici abilitati su sistema operativo (Windows Update, aggiornamenti macOS, gestore pacchetti Linux) e browser dei portatili; applicazioni (editor, strumenti di sviluppo, client VPN, antimalware, estensioni browser) tenute aggiornate; software non supportato rimosso o sostituito; riavvio del portatile almeno settimanale e quando richiesto.</li>
|
||||
<li>Per i prodotti SaaS e on-premise si monitorano le dipendenze (librerie, container, runtime), si aggiornano le immagini e si ricostruiscono gli artefatti; sui servizi cloud (Aruba, Hetzner) si applicano gli aggiornamenti delle console e dei servizi gestiti annotando le finestre di manutenzione.</li>
|
||||
<li>Per i prodotti on-premise, l'azienda rilascia la patch e <strong>informa tempestivamente i clienti impattati</strong> con le istruzioni di aggiornamento (obblighi contrattuali di supply chain), verificandola in ambiente di prova prima del rilascio per evitare regressioni.</li>
|
||||
</ul>
|
||||
<h3>7.4 Verifica, chiusura e registrazione</h3>
|
||||
<ul>
|
||||
<li>Si verifica l'effettiva risoluzione (riscansione/test) e si chiude la vulnerabilità; gli scostamenti dai tempi target sono registrati come <strong>non conformità</strong> con azione correttiva.</li>
|
||||
<li>Patch, date e versioni sono tracciate nel registro aggiornamenti/asset; nessuna vulnerabilità critica nota resta non gestita oltre lo SLA interno.</li>
|
||||
<li>Se una vulnerabilità è stata sfruttata, si attiva la Procedura di Gestione degli Incidenti. In caso di dubbio su un aggiornamento fallito o su una vulnerabilità segnalata si contatta il Resp. IT/Sicurezza Simon Fattori, senza disinstallare patch di sicurezza per "far funzionare" un'applicazione.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Logging e monitoraggio</h2>
|
||||
<p>In assenza di server in sede, i log dei servizi cloud sono la principale fonte di visibilità. Il logging è attivo su tutti i servizi critici (posta, console Aruba/Hetzner, repository, piattaforme AI, password manager, prodotti SaaS), con orari di riferimento coerenti (Europe/Rome o UTC documentato) per correlare gli eventi.</p>
|
||||
<ol>
|
||||
<li><strong>Verifica giornaliera rapida.</strong> Controllo degli avvisi di sicurezza automatici dei fornitori (nuovi accessi, login da nuovi dispositivi/paesi, blocchi MFA); ogni avviso è da valutare, non da ignorare.</li>
|
||||
<li><strong>Revisione settimanale degli accessi.</strong> Esame dei log di autenticazione dei servizi critici cercando login falliti ripetuti, accessi da località/orari inusuali, accessi notturni non giustificati, sessioni o dispositivi non riconosciuti.</li>
|
||||
<li><strong>Controllo dei cambiamenti privilegiati.</strong> Verifica di modifiche a ruoli/permessi, creazione di account, generazione/rotazione di chiavi API e token; ogni cambiamento deve essere riconducibile a una richiesta legittima.</li>
|
||||
<li><strong>Log dei prodotti SaaS.</strong> Controllo degli audit log applicativi per accessi anomali ai dati dei clienti ed errori ricorrenti.</li>
|
||||
<li><strong>Correlazione degli eventi.</strong> A fronte di un'anomalia si ricostruisce la sequenza tra servizi diversi usando orari allineati, per distinguere un evento singolo da una catena.</li>
|
||||
<li><strong>Conservazione e protezione dei log.</strong> I registri non sono modificabili dagli utenti finali e sono conservati per un periodo adeguato secondo le impostazioni dei fornitori <em>[da verificare rispetto alla policy di retention interna]</em>.</li>
|
||||
<li><strong>Documentazione della revisione.</strong> Si annotano data, servizi controllati, anomalie rilevate ed esito (chiusa come falso positivo o escalata a incidente) nel registro delle revisioni.</li>
|
||||
</ol>
|
||||
<p>In presenza di accesso non autorizzato confermato o sospetto, chiave compromessa o attività anomale sui dati dei clienti, si avvia la Procedura di Gestione degli Incidenti (referente: Simon Fattori); per incidenti che possono ricadere sotto NIS2 si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese).</p>
|
||||
|
||||
<h2>9. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>A.8.25</strong> – Ciclo di vita di sviluppo sicuro; <strong>A.8.26</strong> – requisiti di sicurezza delle applicazioni; <strong>A.8.27 / A.8.28</strong> – architettura e codifica sicura; <strong>A.8.29</strong> – test di sicurezza in sviluppo e accettazione; <strong>A.8.30</strong> – sviluppo affidato all'esterno; <strong>A.8.31</strong> – separazione ambienti; <strong>A.8.32 / A.8.33</strong> – gestione dei cambiamenti e dati di test.</li>
|
||||
<li><strong>A.8.8</strong> – gestione delle vulnerabilità tecniche; <strong>A.8.7</strong> – protezione dai malware; <strong>A.8.9</strong> – gestione della configurazione; <strong>A.5.7</strong> – threat intelligence; <strong>A.8.16</strong> – monitoraggio; <strong>A.8.1</strong> – endpoint.</li>
|
||||
<li><strong>A.8.15</strong> – logging; <strong>A.5.17</strong> – informazioni di autenticazione.</li>
|
||||
<li>Controlli cloud di ISO/IEC 27017 e ISO/IEC 27018 per gli aspetti cloud e PII.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: sicurezza nell'acquisizione, sviluppo e manutenzione dei sistemi, gestione delle modifiche e delle vulnerabilità. Una vulnerabilità sfruttata o un cambiamento fallito che causino un incidente significativo attivano gli obblighi di notifica dell'<strong>art. 25</strong> (early warning 24 ore, notifica completa 72 ore, relazione finale 1 mese).</li>
|
||||
</ul>
|
||||
|
||||
<h2>10. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Requisiti di sicurezza documentati per progetto/prodotto; registri delle code review e dei test di sicurezza; inventario delle dipendenze.</li>
|
||||
<li>Richieste di cambiamento (RFC) e autorizzazioni; esiti di test e rilasci; note di rilascio per i clienti on-premise; documentazione dei cambiamenti di emergenza e relativa ratifica.</li>
|
||||
<li>Registro delle vulnerabilità con gravità, priorità e tempi di rimedio; report delle scansioni e degli aggiornamenti applicati; registro aggiornamenti/asset; comunicazioni di patch ai clienti on-premise.</li>
|
||||
<li>Registro delle revisioni dei log/accessi con anomalie ed esiti; non conformità e azioni correttive per cambiamenti falliti o ritardi di rimedio.</li>
|
||||
</ul>
|
||||
|
||||
<h2>11. Riesame e versionamento</h2>
|
||||
<p>La procedura è riesaminata almeno annualmente o a fronte di cambiamenti tecnologici significativi, nuove minacce, modifiche del processo di rilascio o degli strumenti di scansione, cambiamenti del parco software o requisiti contrattuali dei clienti. Versione corrente: <strong>2.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,106 @@
|
||||
<!--META|doc_type=procedura_governance_sgsi|title=Procedura di Governance del SGSI (documenti, audit, riesame, NC, competenze)|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le modalità con cui <strong>Nuova Agile Technology srl</strong> governa il proprio SGSI nei processi di sistema: controllo dei documenti e delle registrazioni, audit interni, riesame della direzione, gestione delle non conformità e delle azioni correttive, gestione delle competenze e consapevolezza del personale. La procedura assicura che il sistema sia conforme alla <strong>ISO/IEC 27001:2022</strong>, efficacemente attuato, mantenuto e migliorato nel tempo, attuando in particolare le cl. 7.2, 7.3, 7.5, 9.2, 9.3, 10.1 e 10.2.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutto il SGSI e alle sue componenti: documentazione (politiche, procedure di sistema, istruzioni operative, SoA, registri) e registrazioni di processo (verbali, report di audit, valutazioni dei rischi, registri formazione, NC/CAPA); governance del SGSI, gestione dei rischi e degli incidenti, sicurezza del cloud (Aruba IT, Hetzner DE, piattaforme AI), gestione delle PII nel SaaS, sicurezza dei portatili, formazione e rapporti con i fornitori. Data la natura <em>cloud-only</em> dell'azienda (~12 persone: 9 dipendenti, 2 collaboratori P.IVA e la Direzione) non esiste documentazione cartacea controllata di default; i documenti esterni sono archiviati nel cloud aziendale (Aruba IT).</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – cl. 7.2 (competenza), 7.3 (consapevolezza), 7.5.1/7.5.2/7.5.3 (informazioni documentate), 9.2.1/9.2.2 (audit interni), 9.3.1/9.3.2/9.3.3 (riesame della direzione), 10.1 (miglioramento continuo), 10.2 (non conformità e azioni correttive).</li>
|
||||
<li>Controlli Annex A: A.5.1 (politiche), A.5.4 (responsabilità della direzione), A.5.24–A.5.28 (gestione e apprendimento dagli incidenti, evidenze), A.5.33 (protezione delle registrazioni), A.5.34 (privacy e PII), A.5.35 (riesame indipendente), A.5.36 (conformità a politiche e standard), A.5.37 (procedure operative documentate), A.6.1/A.6.2/A.6.3/A.6.5 (ciclo di vita del rapporto, awareness e formazione), A.8.7 (protezione dai malware), A.8.10/A.8.11/A.8.12 (cancellazione, mascheramento, prevenzione fuga di dati), A.5.17 (autenticazione), A.8.28 (codifica sicura).</li>
|
||||
<li>ISO 19011 (linee guida per gli audit dei sistemi di gestione); ISO/IEC 27018 (conservazione/cancellazione delle PII nel cloud, consapevolezza del personale che tratta PII); GDPR per i periodi di conservazione dei dati personali.</li>
|
||||
<li>D.Lgs. 138/2024 art. 23 (obblighi di governance e responsabilità degli organi di amministrazione, formazione della direzione), art. 24 (efficacia delle misure, igiene informatica e formazione), art. 25 (notifica degli incidenti significativi) [da verificare per i soggetti rientranti].</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente (Silvia Garretto)</td><td>Approva politiche e procedure di sistema (autorità di approvazione); presiede il riesame e assume le decisioni; assicura le risorse per audit, azioni e formazione; partecipa alla formazione di governance NIS2.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Gestisce l'elenco master dei documenti, versioni e stato; predispone il programma di audit e nomina gli auditor; convoca il riesame, prepara gli input e redige il verbale; registra/classifica le NC e ne verifica la chiusura; definisce i fabbisogni di competenza e il piano formativo.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Garantisce backup, controllo accessi e cifratura degli archivi documentali cloud; fornisce evidenze sulle aree auditate; riporta su incidenti, vulnerabilità e prestazioni dei controlli tecnici; attua le correzioni tecniche; supporta la formazione tecnica.</td></tr>
|
||||
<tr><td>Auditor (interno indipendente o esterno)</td><td>Conduce gli audit, raccoglie le evidenze, formula i rilievi e redige il report.</td></tr>
|
||||
<tr><td>Autori / Responsabili di processo</td><td>Redigono e aggiornano i documenti di competenza; segnalano e gestiscono le NC; concordano e attuano le azioni correttive.</td></tr>
|
||||
<tr><td>Personale (dipendenti e collaboratori)</td><td>Partecipa alle attività formative assegnate e mantiene aggiornata la propria consapevolezza; segnala anomalie e NC.</td></tr>
|
||||
<tr><td>DPO esterno</td><td>Indica i tempi di conservazione/cancellazione delle registrazioni con dati personali; è coinvolto nelle NC e nei riesami su tematiche PII e relativi obblighi di notifica.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Controllo dei documenti e delle registrazioni (cl. 7.5)</h2>
|
||||
<ol>
|
||||
<li><strong>Identificazione.</strong> Ogni documento riceve titolo, codice/slug, numero di versione, data e stato (bozza/in revisione/approvato/obsoleto).</li>
|
||||
<li><strong>Redazione e aggiornamento</strong> (cl. 7.5.2): l'autore redige in formato e struttura standard; le modifiche sono tracciate con la cronologia delle versioni.</li>
|
||||
<li><strong>Verifica e approvazione.</strong> Il RSGSI verifica adeguatezza e coerenza; la Direzione (o delegato) approva. Un documento è pubblicabile solo nello stato "approvato".</li>
|
||||
<li><strong>Distribuzione e disponibilità</strong> (cl. 7.5.3): la versione vigente è resa disponibile tramite la piattaforma/archivio cloud, con accessi profilati per ruolo.</li>
|
||||
<li><strong>Controllo delle modifiche.</strong> Ogni revisione genera una nuova versione; la precedente è marcata "obsoleta" e conservata per tracciabilità, non più utilizzabile come riferimento operativo.</li>
|
||||
<li><strong>Documenti di origine esterna</strong> (leggi, norme, contratti cloud, manuali provider): identificati e con distribuzione controllata.</li>
|
||||
<li><strong>Protezione e conservazione delle registrazioni.</strong> Le evidenze sono protette da perdita, alterazione e accesso non autorizzato mediante backup, cifratura e controllo accessi del provider cloud; i periodi di conservazione rispettano legge e GDPR; la cancellazione avviene in modo sicuro (A.8.10).</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Audit interni (cl. 9.2)</h2>
|
||||
<p>Data la dimensione del team (~12 persone), gli audit sono di norma condotti dal RSGSI o da risorsa interna indipendente dall'area auditata; per oggettività sulle aree gestite dallo stesso RSGSI è possibile ricorrere a un auditor esterno.</p>
|
||||
<ol>
|
||||
<li><strong>Programmazione</strong> (input: esiti audit precedenti, importanza dei processi, esiti valutazione rischi). Si pianifica almeno un ciclo di audit interno all'anno che copra tutte le clausole e i controlli applicabili; le aree a rischio più elevato possono essere auditate più di frequente.</li>
|
||||
<li><strong>Pianificazione del singolo audit.</strong> Si definiscono obiettivi, criteri (la norma e i documenti del SGSI), ambito, date e auditor; il piano è registrato nel <strong>modulo "Audit interni"</strong>.</li>
|
||||
<li><strong>Conduzione.</strong> L'auditor verifica documentazione, configurazioni cloud, registri e prassi operative; raccoglie evidenze oggettive e annota osservazioni e non conformità.</li>
|
||||
<li><strong>Classificazione dei rilievi</strong> come Non Conformità (maggiore/minore) oppure Osservazione/Opportunità di miglioramento.</li>
|
||||
<li><strong>Rendicontazione.</strong> L'auditor redige il report nel modulo "Audit interni" (esito, rilievi, raccomandazioni), condiviso con i responsabili di area e con la Direzione.</li>
|
||||
<li><strong>Gestione dei rilievi e follow-up.</strong> Ogni Non Conformità apre una scheda nel modulo NCR/CAPA; il RSGSI ne verifica la chiusura. Esiti e stato delle azioni alimentano il successivo Riesame della Direzione.</li>
|
||||
</ol>
|
||||
|
||||
<h2>7. Riesame della direzione (cl. 9.3)</h2>
|
||||
<ol>
|
||||
<li><strong>Convocazione e raccolta input.</strong> Il RSGSI raccoglie dai moduli della piattaforma gli elementi richiesti dalla cl. 9.3.2: stato delle azioni dai riesami precedenti; cambiamenti del contesto e delle parti interessate; esiti della valutazione e del trattamento dei rischi; prestazioni di sicurezza (incidenti, non conformità, monitoraggio e misurazioni); esiti degli audit interni; raggiungimento degli obiettivi di sicurezza; feedback delle parti interessate; opportunità di miglioramento.</li>
|
||||
<li><strong>Conduzione della riunione.</strong> La Direzione esamina ciascun input e valuta idoneità, adeguatezza ed efficacia del SGSI, discutendo eventuali necessità di cambiamento.</li>
|
||||
<li><strong>Decisioni e output</strong> (cl. 9.3.3): opportunità di miglioramento, cambiamenti al SGSI (politiche, obiettivi, controlli), fabbisogni di risorse e azioni con responsabili e scadenze.</li>
|
||||
<li><strong>Verbalizzazione.</strong> Il RSGSI redige il verbale nel <strong>modulo "Riesame della Direzione"</strong>, riportando input considerati, decisioni e azioni assegnate.</li>
|
||||
<li><strong>Attuazione e monitoraggio.</strong> Le azioni derivanti sono tracciate (come schede NCR/CAPA o voci del piano di trattamento rischi) e il loro stato è verificato al riesame successivo.</li>
|
||||
</ol>
|
||||
|
||||
<h2>8. Non conformità e azioni correttive (cl. 10.1 e 10.2)</h2>
|
||||
<ol>
|
||||
<li><strong>Rilevazione e registrazione.</strong> La NC è aperta nel <strong>modulo "Non conformità / Azioni correttive (NCR/CAPA)"</strong> con descrizione, origine, data e responsabile (rilievi di audit, scostamenti dalla norma o dai documenti, incidenti, segnalazioni di clienti/fornitori, malfunzionamenti dei controlli cloud, esiti di monitoraggio).</li>
|
||||
<li><strong>Correzione immediata</strong> (cl. 10.2 a): si contiene e corregge la NC e se ne gestiscono le conseguenze (es. revoca di un accesso, ripristino di una configurazione cloud, contenimento di un incidente).</li>
|
||||
<li><strong>Valutazione della necessità di azione correttiva</strong> (cl. 10.2 b): per NC minori e isolate può bastare la correzione; per NC ricorrenti o significative si avvia un'azione correttiva.</li>
|
||||
<li><strong>Analisi delle cause</strong> (root cause): si individuano le cause profonde (es. assenza di un controllo, configurazione errata, mancanza di competenza) con tecnica adeguata (5 perché / causa-effetto).</li>
|
||||
<li><strong>Definizione e attuazione della CAPA</strong> (cl. 10.2 c/d): azioni, responsabili e scadenze registrate come azioni collegate alla scheda, e attuate.</li>
|
||||
<li><strong>Verifica dell'efficacia</strong> (cl. 10.2 d/e): il RSGSI verifica che la causa sia eliminata e che la NC non si ripresenti; aggiorna, se necessario, rischi e SoA.</li>
|
||||
<li><strong>Chiusura e comunicazione</strong> (cl. 10.2 f/g): la scheda è chiusa con evidenza dell'efficacia; le NC significative sono portate al Riesame della Direzione. Per incidenti che configurano violazioni di dati o incidenti significativi NIS2 si attivano i relativi flussi di notifica (art. 25 D.Lgs. 138/2024: pre-allarme 24h, notifica 72h, relazione finale 1 mese).</li>
|
||||
</ol>
|
||||
|
||||
<h2>9. Competenze, formazione e consapevolezza (cl. 7.2 e 7.3)</h2>
|
||||
<ol>
|
||||
<li><strong>Definizione dei fabbisogni.</strong> Per ciascun ruolo si individuano le competenze richieste (cl. 7.2 a/b) e il divario rispetto a quelle possedute, considerando le specificità <em>cloud-only</em>: sicurezza dei servizi cloud, uso sicuro dei portatili, sviluppo sicuro (SaaS e on-premise), protezione delle PII, uso responsabile delle piattaforme AI.</li>
|
||||
<li><strong>Pianificazione formativa.</strong> Il RSGSI definisce il piano annuale con priorità, destinatari e scadenze, includendo almeno: awareness generale per tutti, formazione tecnica per IT/sviluppo, sessione di governance per la Direzione, modulo sulla protezione delle PII.</li>
|
||||
<li><strong>Assegnazione ed erogazione.</strong> I corsi sono assegnati tramite il <strong>modulo "Formazione"</strong> (e-learning, aula o on-the-job).</li>
|
||||
<li><strong>Consapevolezza continua</strong> (cl. 7.3): comunicazioni periodiche su politiche, minacce attuali e responsabilità individuali; sessione di awareness obbligatoria al nuovo ingresso, prima dell'accesso ai sistemi.</li>
|
||||
<li><strong>Registrazione, verifica ed efficacia.</strong> Stato, data di completamento ed esiti di test/quiz sono registrati nel modulo "Formazione"; il RSGSI verifica tasso di completamento e adeguatezza delle competenze (es. riduzione di incidenti da errore umano); le carenze diventano azioni di miglioramento o NC.</li>
|
||||
<li><strong>Ingressi e cessazioni.</strong> All'assunzione si definiscono i requisiti di competenza e consapevolezza; alla cessazione si rammentano gli obblighi di riservatezza residui (A.6.5).</li>
|
||||
</ol>
|
||||
<h3>9.1 Consapevolezza anti-phishing (istruzione per il personale)</h3>
|
||||
<p>Il phishing è la porta d'ingresso più comune per gli attacchi; ogni persona deve saperlo riconoscere (email, SMS/smishing, telefono/vishing) e segnalarlo rapidamente. Regole essenziali: fermarsi prima di agire di fronte a messaggi che creano urgenza/paura/curiosità; verificare il mittente reale e diffidare di domini simili; non cliccare d'impulso (controllare l'URL, nel dubbio digitare l'indirizzo nel browser); non aprire allegati inattesi (eseguibili, archivi, "abilita macro"); <strong>mai inserire credenziali o codici MFA da un link ricevuto</strong>; verificare su canale diverso e noto le richieste anomale del "capo" o di fornitori (pagamenti, cambio IBAN, invio credenziali); nessuno deve farsi leggere o inoltrare un codice MFA. Prerequisito: MFA attivo su posta e servizi critici. In caso di messaggio sospetto si inoltra l'originale (senza cliccare) al Resp. IT/Sicurezza <strong>Simon Fattori</strong>; se si è già cliccato o inserito credenziali, avvisare subito Simon Fattori, cambiare la password interessata e, se possibile, scollegare il dispositivo dalla rete. Una segnalazione tempestiva può evitare un incidente; un click confessato non è una colpa. Integrare con simulazioni di phishing periodiche.</p>
|
||||
|
||||
<h2>10. Moduli della piattaforma come repository delle evidenze</h2>
|
||||
<p>Le registrazioni di processo risiedono nei rispettivi moduli della piattaforma, che fungono da repository controllato delle evidenze: <strong>Audit interni</strong> (programma e piani di audit, report e rilievi), <strong>Riesame della Direzione</strong> (verbali, input considerati, decisioni e azioni), <strong>NCR/CAPA</strong> (schede di non conformità, analisi delle cause, azioni correttive con responsabili/scadenze/verifica di efficacia), <strong>Rischi</strong> (valutazioni e piano di trattamento), <strong>Formazione</strong> (matrice competenze, piano formativo, assegnazioni e attestati di completamento). L'<strong>elenco master dei documenti</strong> con stato e versione è mantenuto dal RSGSI nella piattaforma/archivio cloud.</p>
|
||||
|
||||
<h2>11. Controlli ISO/clausole collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>cl. 7.5.1/7.5.2/7.5.3</strong>; A.5.33, A.5.37, A.8.10/A.8.11/A.8.12; requisiti ISO/IEC 27018 su conservazione, restituzione e cancellazione delle PII.</li>
|
||||
<li><strong>cl. 9.2.1/9.2.2</strong>; A.5.35 (riesame indipendente), A.5.36 (conformità); ISO 19011.</li>
|
||||
<li><strong>cl. 9.3.1/9.3.2/9.3.3</strong>; A.5.1, A.5.4; collegamento con cl. 9.2 (audit), cl. 6.1/8.2 (rischi), cl. 10.1/10.2 (miglioramento) e cl. 5 (leadership).</li>
|
||||
<li><strong>cl. 10.1 e 10.2 (a–g)</strong>; A.5.24 (preparazione gestione incidenti), A.5.27 (apprendimento dagli incidenti), A.5.28 (raccolta evidenze).</li>
|
||||
<li><strong>cl. 7.2/7.3</strong>; A.6.3 (formazione e awareness), A.6.1/A.6.2/A.6.5 (ciclo di vita del rapporto), A.8.28 (codifica sicura), A.5.17 (autenticazione), A.8.7 (protezione dai malware).</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 23 (governance e formazione della direzione), art. 24 (efficacia delle misure, igiene informatica e formazione), art. 25 (notifica incidenti).</li>
|
||||
</ul>
|
||||
|
||||
<h2>12. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Elenco master dei documenti del SGSI con stato e versione; cronologia delle versioni di ciascun documento (versione + data + autore).</li>
|
||||
<li>Programma di audit annuale, piani e report di audit con rilievi ed evidenze; evidenza di indipendenza/competenza degli auditor.</li>
|
||||
<li>Verbale di riesame della direzione con input, decisioni e azioni; pacchetto di input estratto dai moduli operativi.</li>
|
||||
<li>Schede NC con analisi delle cause e azioni correttive (responsabili, scadenze, stato, verifica di efficacia) e relativi collegamenti ad audit, rischi e incidenti.</li>
|
||||
<li>Matrice delle competenze per ruolo e piano formativo annuale; assegnazioni e attestati di completamento; evidenze di awareness di onboarding e comunicazioni periodiche.</li>
|
||||
</ul>
|
||||
|
||||
<h2>13. Riesame e versionamento</h2>
|
||||
<p>I processi di governance del SGSI e i relativi documenti sono riesaminati <strong>almeno annualmente</strong> e ad ogni cambiamento organizzativo, tecnologico o normativo rilevante (nuovi servizi/provider, incidenti gravi, nuove minacce, nuovi ruoli). Ogni nuova edizione incrementa la versione (modifiche minori → incremento minore; modifiche sostanziali → incremento maggiore). Versione corrente: <strong>2.0</strong>, approvata dalla Direzione secondo la presente procedura di controllo dei documenti.</p>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!--META|doc_type=politica_sicurezza_informazioni|title=Politica Generale per la Sicurezza delle Informazioni|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo e ambito</h2>
|
||||
<p>La presente Politica esprime l'impegno formale dell'Alta Direzione di <strong>Nuova Agile Technology srl</strong> verso la protezione di riservatezza, integrità e disponibilità delle informazioni proprie e di quelle affidate dai clienti, inclusi i dati personali. È il documento di indirizzo del Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) conforme a <strong>ISO/IEC 27001:2022</strong> e alle linee guida <strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> per i servizi cloud e i dati personali. Si applica a tutto il personale (dipendenti e collaboratori a P.IVA), a tutte le informazioni trattate, ai prodotti software in licenza d'uso installati presso i clienti e ai servizi SaaS multi-tenant erogati in cloud. L'infrastruttura è interamente cloud (<strong>Aruba</strong>, Italia; <strong>Hetzner</strong>, Germania-UE) con piattaforme AI via API; non esistono server gestiti in sede e le postazioni sono solo PC portatili cifrati.</p>
|
||||
|
||||
<h2>2. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022, 27017:2015, 27018:2019</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR)</li>
|
||||
<li>Direttiva (UE) 2022/2555 (NIS2) e D.Lgs. 138/2024</li>
|
||||
<li>Manuale SGSI, Dichiarazione di Applicabilità (SoA)</li>
|
||||
</ul>
|
||||
|
||||
<h2>3. Ruoli e responsabilità</h2>
|
||||
<p>La <strong>Presidente Silvia Garretto</strong> (Alta Direzione) approva e sostiene la Politica. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> ne cura attuazione e monitoraggio. Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> presidia le misure tecniche. Il <strong>DPO (consulente esterno, da nominare)</strong> supervisiona la conformità GDPR. Ogni membro del personale è responsabile del rispetto della Politica nell'ambito delle proprie attività.</p>
|
||||
|
||||
<h2>4. Principi e impegni</h2>
|
||||
<p>L'Alta Direzione si impegna a:</p>
|
||||
<ul>
|
||||
<li><strong>Riservatezza</strong>: garantire l'accesso alle informazioni solo a chi è autorizzato, applicando il minimo privilegio e la separazione dei tenant nel SaaS.</li>
|
||||
<li><strong>Integrità</strong>: assicurare accuratezza e completezza di informazioni e sistemi, con controlli su modifiche e sviluppo software sicuro.</li>
|
||||
<li><strong>Disponibilità</strong>: garantire l'accesso ai servizi secondo i livelli concordati, mediante backup, continuità operativa e gestione resiliente del cloud.</li>
|
||||
<li><strong>Conformità</strong>: rispettare gli obblighi legali e contrattuali, in particolare GDPR e i requisiti NIS2 ove applicabili.</li>
|
||||
<li><strong>Gestione del rischio</strong>: identificare, valutare e trattare i rischi in modo sistematico, con attenzione alla supply chain cloud e AI.</li>
|
||||
<li><strong>Consapevolezza</strong>: formare e sensibilizzare il personale sui temi di sicurezza.</li>
|
||||
<li><strong>Miglioramento continuo</strong>: riesaminare periodicamente il SGSI e adottare azioni correttive e preventive.</li>
|
||||
<li><strong>Gestione degli incidenti</strong>: rilevare, gestire e segnalare tempestivamente gli incidenti di sicurezza e le violazioni di dati personali, nel rispetto degli obblighi di notifica al CSIRT (NIS2: 24h/72h/1 mese).</li>
|
||||
</ul>
|
||||
<p>L'Azienda adotta una <strong>politica di tolleranza zero</strong> verso comportamenti che mettano deliberatamente a rischio la sicurezza delle informazioni.</p>
|
||||
|
||||
<h2>5. Obiettivi di sicurezza</h2>
|
||||
<p>Gli obiettivi misurabili del SGSI sono definiti annualmente dalla Direzione e includono, a titolo esemplificativo: copertura della formazione del personale, tempi di risposta agli incidenti, percentuale di rischi trattati entro i termini, livello di conformità dei fornitori cloud. Sono monitorati e riesaminati nel riesame di direzione.</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p>Controlli Annex A pertinenti: <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni), <strong>A.5.2</strong> (Ruoli e responsabilità), <strong>A.5.4</strong> (Responsabilità della direzione), <strong>A.5.31</strong> (Requisiti legali e contrattuali), <strong>A.5.36</strong> (Conformità alle politiche). Riferimenti NIS2 (D.Lgs. 138/2024): <strong>art.23</strong> (governance e ruolo degli organi di gestione), <strong>art.24</strong> (misure di gestione del rischio), <strong>art.25</strong> (obblighi di notifica). Le ISO costituiscono buone prassi, non obblighi di legge.</p>
|
||||
|
||||
<h2>7. Caveat NIS2</h2>
|
||||
<p>L'Azienda si autoclassifica in via <strong>preliminare</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>", pur essendo <strong>sotto le soglie dimensionali ordinarie</strong>: la qualifica va <strong>confermata da Direzione e Legale</strong>, ed eventualmente da ACN, in base a fatturato e categoria di attività. <strong>[DA VERIFICARE]</strong> in sede di registrazione presso ACN.</p>
|
||||
|
||||
<h2>8. Comunicazione e applicazione</h2>
|
||||
<p>La Politica è comunicata a tutto il personale e accessibile in qualsiasi momento. La sua violazione può comportare provvedimenti disciplinari secondo le norme contrattuali e di legge vigenti. Evidenze: documento firmato dalla Presidente, evidenza di comunicazione al personale, verbali di riesame.</p>
|
||||
|
||||
<h2>9. Riesame e versionamento</h2>
|
||||
<p>Approvata dall'Alta Direzione. Riesame almeno annuale o a fronte di modifiche significative del contesto.</p>
|
||||
@@ -0,0 +1,110 @@
|
||||
<!--META|doc_type=politica_controllo_accessi|title=Politica e Procedura di Controllo degli Accessi (incl. MFA, onboarding/offboarding)|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le regole e i passi operativi con cui <strong>Nuova Agile Technology srl</strong> autorizza, concede, modifica, riesamina e revoca gli accessi logici alle informazioni, ai sistemi cloud (Aruba, Hetzner), alle piattaforme AI, agli ambienti di sviluppo e ai servizi SaaS multi-tenant, garantendo che ogni utente disponga esclusivamente dei privilegi necessari al proprio ruolo (<em>least privilege</em>) e che ogni accesso sia protetto da <strong>autenticazione a più fattori (MFA)</strong>. Il documento copre principi, l'intero ciclo di vita dell'identità (onboarding, variazioni di ruolo, riesame periodico, offboarding) e le istruzioni operative MFA.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutti gli accessi logici di dipendenti, collaboratori a P.IVA, utenze tecniche/di servizio e — ove pertinente — di clienti e fornitori. Riguarda: account dei provider cloud (Aruba IT, Hetzner DE), repository di codice, ambienti di produzione e staging, console di amministrazione e pannelli SaaS multi-tenant, posta elettronica, strumenti di collaborazione, database, gestore di password e gestore segreti aziendali, chiavi API (incluse quelle dei provider AI) e i <strong>PC portatili cifrati</strong> in dotazione. <strong>Non esistono server in sede</strong>: non sono previsti accessi fisici a sale macchine, la cui sicurezza è ereditata dai fornitori cloud secondo il modello di responsabilità condivisa.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – Annex A: A.5.15 (Controllo degli accessi), A.5.16 (Gestione delle identità), A.5.17 (Informazioni di autenticazione), A.5.18 (Diritti di accesso), A.6.3 (Consapevolezza in fase di ingresso), A.8.1 (Dispositivi endpoint), A.8.2 (Diritti di accesso privilegiati), A.8.3 (Restrizione dell'accesso alle informazioni), A.8.5 (Autenticazione sicura).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> (gestione accessi nei servizi cloud) e <strong>27018:2019</strong> (accesso ai dati personali nel cloud).</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): art. 24 (misure di gestione del rischio).</li>
|
||||
<li>Politica per la Sicurezza delle Informazioni; Politica di Crittografia; Manuale SGSI.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva il documento, autorizza i profili di accesso privilegiato critico e le deroghe</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Approva le matrici di accesso (RBAC), coordina i riesami periodici e mantiene le evidenze</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Crea/modifica/revoca le utenze, configura l'MFA, custodisce le credenziali, esegue le verifiche tecniche</td></tr>
|
||||
<tr><td>Responsabile dell'unità richiedente</td><td><em>variabile</em></td><td>Richiede e giustifica gli accessi del proprio personale</td></tr>
|
||||
<tr><td>DPO</td><td><em>Consulente esterno</em></td><td>Consultato per gli accessi che coinvolgono dati personali dei clienti</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Principi di controllo degli accessi</h2>
|
||||
<ul>
|
||||
<li><strong>Minimo privilegio</strong>: a ogni utente sono concessi solo i diritti necessari al ruolo.</li>
|
||||
<li><strong>Need-to-know</strong>: l'accesso alle informazioni è limitato a chi ne ha effettiva necessità.</li>
|
||||
<li><strong>Segregazione dei compiti</strong>: ove possibile, separazione tra chi sviluppa, chi rilascia in produzione e chi amministra.</li>
|
||||
<li><strong>Default deny</strong>: in assenza di autorizzazione esplicita, l'accesso è negato.</li>
|
||||
<li><strong>Account nominativi</strong>: nessuna condivisione di credenziali; ogni account è personale.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Autenticazione e MFA</h2>
|
||||
<p>L'<strong>MFA è obbligatoria</strong> per ogni servizio critico: console cloud Aruba/Hetzner, repository di codice, accessi amministrativi al SaaS, account di posta, SSO, piattaforme AI, password manager e gestore segreti. La sola password non è mai sufficiente. Le password devono essere robuste secondo standard aziendale ed è obbligatorio l'uso di un <strong>password manager</strong> approvato.</p>
|
||||
<h3>6.1 Istruzione operativa MFA</h3>
|
||||
<ol>
|
||||
<li><strong>Scegli il secondo fattore corretto.</strong> Ordine di preferenza: (1) chiave di sicurezza FIDO2/passkey, (2) app di autenticazione (codici TOTP o notifiche push), (3) come ultima scelta SMS. <strong>Evita gli SMS</strong> dove esiste un'alternativa.</li>
|
||||
<li><strong>Attiva l'MFA su ogni servizio critico.</strong> Impostazioni di sicurezza dell'account → "Autenticazione a due fattori / MFA" → abilita, servizio per servizio (posta, repository, console Aruba e Hetzner, piattaforme AI, password manager).</li>
|
||||
<li><strong>Registra il dispositivo.</strong> Inquadra il QR code con l'app di autenticazione o registra la chiave FIDO2; conferma con il primo codice generato.</li>
|
||||
<li><strong>Salva i codici di recupero</strong> monouso <strong>solo</strong> nel password manager aziendale, mai in chiaro su file, email o foglietti.</li>
|
||||
<li><strong>Imposta un secondo metodo</strong> (seconda passkey o chiave fisica di backup) per non restare bloccato in caso di smarrimento dello smartphone; sui servizi più critici (posta e console cloud) devono risultare registrati almeno due metodi.</li>
|
||||
<li><strong>Proteggi il password manager</strong>: master password lunga, unica, mai riutilizzata, con MFA attivo.</li>
|
||||
<li><strong>Account/segreti di servizio</strong> nel gestore segreti aziendale, mai scambiati via chat o email.</li>
|
||||
</ol>
|
||||
<h3>6.2 Verifiche di esito MFA</h3>
|
||||
<ul>
|
||||
<li>Un login di prova deve richiedere il secondo fattore; nessun servizio critico accessibile con la sola password.</li>
|
||||
<li>I codici di recupero sono presenti nel password manager per ogni servizio.</li>
|
||||
</ul>
|
||||
<p>In caso di smarrimento smartphone, mancata ricezione codici, esaurimento dei codici di recupero o sospetto accesso non autorizzato, contattare <strong>subito</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong> per il reset controllato. È <strong>vietato disattivare l'MFA</strong> per "comodità".</p>
|
||||
|
||||
<h2>7. Accessi privilegiati e chiavi API</h2>
|
||||
<ul>
|
||||
<li>Gli account amministrativi sono ridotti al minimo, nominativi, monitorati e soggetti a logging e revisione.</li>
|
||||
<li>Le <strong>chiavi API</strong> (cloud, AI, integrazioni) sono custodite in un vault cifrato, mai inserite in chiaro nel codice o committate nei repository, e ruotate periodicamente o in caso di sospetta compromissione.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Accesso nel SaaS multi-tenant</h2>
|
||||
<ul>
|
||||
<li>Isolamento logico dei tenant: ogni cliente accede esclusivamente ai propri dati.</li>
|
||||
<li>I dati personali dei clienti sono accessibili al personale dell'Azienda solo per finalità di erogazione e supporto, con tracciabilità (rif. ISO 27018).</li>
|
||||
</ul>
|
||||
|
||||
<h2>9. Ciclo di vita dell'identità</h2>
|
||||
<h3>9.1 Onboarding (ingresso di un nuovo utente)</h3>
|
||||
<ol>
|
||||
<li>Il responsabile (o la Direzione) invia a Simon Fattori la richiesta formale con ruolo, profilo di accesso, data di inizio (ed eventuale fine, per i collaboratori esterni).</li>
|
||||
<li>Simon Fattori crea l'identità (account aziendale: posta + SSO se presente) sulle sole piattaforme necessarie al ruolo, applicando il <em>least privilege</em> e profili predefiniti per mansione (RBAC); per i collaboratori esterni limita l'accesso ai soli progetti pertinenti, se possibile con scadenza.</li>
|
||||
<li>Si attiva obbligatoriamente l'<strong>MFA su tutte le utenze</strong> e si forza il cambio password al primo accesso. Senza MFA l'accesso non è abilitato.</li>
|
||||
<li>Il PC portatile viene consegnato già <strong>cifrato a disco intero</strong> (configurato secondo l'Istruzione Hardening: antimalware, blocco schermo) e registrato nell'inventario; le credenziali sono custodite solo nel password manager/gestore segreti, mai via email o chat.</li>
|
||||
<li>L'utente sottoscrive la Politica d'uso accettabile, riceve la formazione di sicurezza di base (MFA, phishing, uso AI, hardening) e si registra l'avvenuta consapevolezza.</li>
|
||||
<li>Si annota nel registro accessi data, ruolo e servizi concessi.</li>
|
||||
</ol>
|
||||
<h3>9.2 Variazione di ruolo</h3>
|
||||
<ol>
|
||||
<li>A ogni cambio di mansione il responsabile richiede l'aggiornamento del profilo.</li>
|
||||
<li>Simon Fattori adegua i privilegi <strong>rimuovendo quelli non più pertinenti prima di aggiungere i nuovi</strong> (no accumulo di permessi).</li>
|
||||
</ol>
|
||||
<h3>9.3 Riesame periodico degli accessi</h3>
|
||||
<ol>
|
||||
<li>Con cadenza <strong>almeno semestrale</strong>, Massimo Tagliavini e Simon Fattori riesaminano tutte le utenze, con particolare attenzione agli <strong>accessi privilegiati</strong> e alle utenze di servizio, confrontando gli accessi attivi con il personale in forza.</li>
|
||||
<li>Gli accessi non più giustificati vengono revocati; gli scostamenti sono registrati come <strong>non conformità</strong> nel modulo dedicato e gli esiti del riesame sono conservati.</li>
|
||||
</ol>
|
||||
<h3>9.4 Offboarding (cessazione)</h3>
|
||||
<ol>
|
||||
<li>Alla data di cessazione (o prima, se richiesto), Simon Fattori <strong>disabilita l'account principale e tutte le utenze</strong> e revoca le sessioni attive: non ci si limita a cambiare la password.</li>
|
||||
<li>Si rimuove l'utente da posta, repository, console cloud, piattaforme AI, password manager, gestore segreti e MDM, revocando chiavi API e token.</li>
|
||||
<li>Si <strong>ruotano i segreti condivisi</strong> (password, chiavi API, token) di cui l'utente era a conoscenza — specialmente per i collaboratori esterni.</li>
|
||||
<li>Si recupera il PC portatile, se ne verifica la cifratura e si esegue il wipe sicuro prima della riassegnazione; per dispositivi personali si rimuovono dati e accessi aziendali.</li>
|
||||
<li>Si riassegnano repository, documenti e caselle condivise a un referente designato.</li>
|
||||
<li>Si registra l'avvenuto offboarding (data di revoca e segreti ruotati). In caso di uscita conflittuale, priorità assoluta alla revoca immediata degli accessi.</li>
|
||||
</ol>
|
||||
|
||||
<h2>10. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p>Il documento attua i controlli ISO/IEC 27001:2022 <strong>A.5.15</strong> (Controllo degli accessi), <strong>A.5.16</strong> (Gestione delle identità), <strong>A.5.17</strong> (Informazioni di autenticazione), <strong>A.5.18</strong> (Diritti di accesso), <strong>A.6.3</strong> (Consapevolezza in fase di ingresso), <strong>A.8.1</strong> (Dispositivi endpoint), <strong>A.8.2</strong> (Diritti di accesso privilegiati), <strong>A.8.3</strong> (Restrizione dell'accesso alle informazioni), <strong>A.8.5</strong> (Autenticazione sicura), oltre ai controlli cloud di ISO/IEC 27017/27018. Risponde alle <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> (NIS2) in materia di controllo degli accessi, autenticazione a più fattori e igiene informatica. Le evidenze sono richiamabili anche per gli obblighi di supply chain verso i clienti NIS2.</p>
|
||||
|
||||
<h2>11. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Matrice dei ruoli e dei privilegi (RBAC), registro delle richieste di accesso e relative autorizzazioni;</li>
|
||||
<li>Report dei riesami periodici (semestrali) degli accessi;</li>
|
||||
<li>Registrazioni di onboarding/offboarding e di ritiro dei dispositivi;</li>
|
||||
<li>Configurazione MFA e log di accesso conservati dalle piattaforme cloud;</li>
|
||||
<li>Log degli accessi privilegiati e inventario delle chiavi API.</li>
|
||||
</ul>
|
||||
|
||||
<h2>12. Riesame e versionamento</h2>
|
||||
<p>Approvato dal Responsabile SGSI e dall'Alta Direzione (Presidente). Revisione almeno annuale o a fronte di cambiamenti significativi delle piattaforme o dei ruoli.</p>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!--META|doc_type=politica_crittografia|title=Politica di Crittografia e Gestione delle Chiavi (incl. chiavi cloud)|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire i criteri con cui <strong>Nuova Agile Technology srl</strong> impiega la crittografia per proteggere riservatezza e integrità delle informazioni — in particolare i dati personali dei clienti trattati nel SaaS — e per gestire in modo sicuro chiavi crittografiche e segreti applicativi lungo l'intero ciclo di vita. Include la regola specifica per la <strong>custodia e il backup delle chiavi di accesso al cloud (key escrow controllato)</strong>, affinché lo smarrimento dell'unico dispositivo non causi mai la perdita di accesso ai servizi, senza creare nuovi rischi di esposizione.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica ai dati a riposo nei servizi cloud (Aruba, Hetzner), ai dati in transito tra utenti, servizi e provider AI, ai PC portatili del personale, ai backup, ai segreti applicativi (chiavi API cloud e AI, credenziali di database, token, chiavi SSH, certificati TLS, codici di recupero MFA) e ai prodotti in licenza installati presso i clienti, per la parte di configurazione crittografica raccomandata.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 Annex A: A.8.24 (crittografia), A.8.5 (autenticazione sicura), A.5.33 (protezione delle registrazioni), A.5.17 (informazioni di autenticazione), A.8.13 (backup), A.5.23 (sicurezza nei servizi cloud).</li>
|
||||
<li>ISO/IEC 27017:2015 e 27018:2019 (crittografia dei dati personali nel cloud).</li>
|
||||
<li>GDPR art. 32 (misure tecniche, tra cui cifratura); Politica di Controllo degli Accessi.</li>
|
||||
<li>NIS2 — D.Lgs. 138/2024, art. 24 (misure di gestione del rischio, incluse politiche e procedure relative all'uso della crittografia).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>Il <strong>Responsabile IT e Sicurezza (Simon Fattori)</strong> definisce e gestisce algoritmi, chiavi, vault dei segreti, accessi e rotazione; è il primo autorizzatore al recupero delle chiavi critiche. Il <strong>Responsabile SGSI (Massimo Tagliavini)</strong> verifica la conformità della Politica ed è il secondo autorizzatore al recupero. Il <strong>DPO (consulente esterno)</strong> è consultato per la cifratura dei dati personali. L'<strong>Alta Direzione (Silvia Garretto)</strong> approva la Politica. I <strong>collaboratori</strong> registrano e proteggono le proprie credenziali secondo le regole seguenti.</p>
|
||||
|
||||
<h2>5. Regole di crittografia</h2>
|
||||
<h3>5.1 Dati in transito</h3>
|
||||
<ul>
|
||||
<li>Tutte le comunicazioni esterne avvengono tramite <strong>TLS 1.2 o superiore</strong> (preferibilmente TLS 1.3); protocolli e cifrature obsolete sono disabilitati.</li>
|
||||
<li>Le connessioni tra applicazione e database e tra microservizi adottano canali cifrati ove tecnicamente possibile.</li>
|
||||
<li>Le chiamate alle API dei provider AI e cloud avvengono esclusivamente su canali cifrati.</li>
|
||||
</ul>
|
||||
<h3>5.2 Dati a riposo</h3>
|
||||
<ul>
|
||||
<li>I dati a riposo nei servizi cloud sono cifrati; database e volumi che contengono dati personali utilizzano cifratura (es. AES-256) secondo le funzionalità del provider.</li>
|
||||
<li>I <strong>PC portatili</strong> del personale hanno la <strong>cifratura completa del disco</strong> attiva (BitLocker/FileVault/LUKS).</li>
|
||||
<li>I <strong>backup</strong> sono cifrati e conservati in modo sicuro.</li>
|
||||
</ul>
|
||||
<h3>5.3 Algoritmi ammessi</h3>
|
||||
<ul>
|
||||
<li>Cifratura simmetrica: <strong>AES-256</strong> (modalità autenticate come GCM).</li>
|
||||
<li>Hashing password: funzioni dedicate e resistenti (es. <strong>bcrypt/Argon2</strong>); vietato MD5/SHA-1 per scopi di sicurezza.</li>
|
||||
<li>Firma/integrità: <strong>HMAC-SHA-256</strong> o equivalente; certificati basati su algoritmi robusti.</li>
|
||||
</ul>
|
||||
<h3>5.4 Provider AI e dati</h3>
|
||||
<p>Le informazioni inviate alle piattaforme AI sono trasmesse su canali cifrati e, ove possibile, <strong>minimizzate/anonimizzate</strong> per evitare l'esposizione di dati personali o riservati non necessari all'elaborazione.</p>
|
||||
|
||||
<h2>6. Gestione delle chiavi e dei segreti</h2>
|
||||
<h3>6.1 Principi generali</h3>
|
||||
<ul>
|
||||
<li><strong>Custodia centralizzata</strong>: i segreti (chiavi API, credenziali, token, chiavi SSH, codici di recupero MFA) vivono in un <strong>vault cifrato / password manager aziendale</strong>, mai in chiaro nel codice sorgente, nei file di configurazione versionati, nei log, nelle email, chat o note personali.</li>
|
||||
<li><strong>Sempre cifrate, mai sul portatile</strong>: nessuna credenziale in chiaro su portatili, repository o supporti locali non protetti.</li>
|
||||
<li><strong>Minimo privilegio e tracciabilità</strong>: l'accesso alle chiavi segue il principio del minimo privilegio ed è registrato (audit log).</li>
|
||||
<li><strong>Rotazione</strong>: rotazione periodica e rotazione immediata in caso di sospetta compromissione, leak o cessazione di personale con accesso.</li>
|
||||
<li><strong>Certificati TLS</strong>: monitorati per la scadenza e rinnovati tempestivamente.</li>
|
||||
</ul>
|
||||
<h3>6.2 Custodia e backup delle chiavi di accesso al cloud (key escrow controllato)</h3>
|
||||
<p>Le chiavi e credenziali di accesso ai servizi cloud (Aruba, Hetzner, piattaforme AI, repository, console di amministrazione) seguono regole rafforzate affinché la perdita di un dispositivo non comporti mai la perdita di accesso:</p>
|
||||
<ul>
|
||||
<li><strong>Copia di sicurezza obbligatoria</strong>: ogni chiave critica ha una copia di backup cifrata; perdere un dispositivo non deve significare perdere l'accesso.</li>
|
||||
<li><strong>Backup dei codici MFA</strong>: i codici di recupero MFA sono salvati nella cassaforte digitale cifrata, mai sullo stesso dispositivo che genera l'OTP.</li>
|
||||
<li><strong>Backup periodico verificato</strong>: la cassaforte digitale è inclusa nei backup cifrati; si verifica periodicamente l'integrità e la ripristinabilità delle copie.</li>
|
||||
<li><strong>Recupero a doppia autorizzazione</strong>: il recupero o l'estrazione di chiavi critiche richiede l'approvazione di <strong>due persone</strong> (Responsabile IT + RSGSI), per evitare abusi.</li>
|
||||
<li><strong>Rotazione post-incidente</strong>: dopo furto, smarrimento o sospetta compromissione si rigenerano le credenziali coinvolte e si aggiorna la cassaforte.</li>
|
||||
<li><strong>Dismissione</strong>: alla cessazione del rapporto si revocano gli accessi del collaboratore e si trasferiscono/ruotano le chiavi di sua competenza.</li>
|
||||
</ul>
|
||||
<h3>6.3 Divieti</h3>
|
||||
<ul>
|
||||
<li>Niente credenziali in chiaro su portatile, repository, email, chat o note personali.</li>
|
||||
<li>Niente condivisione di password fuori dal gestore di segreti.</li>
|
||||
<li>Niente backup delle chiavi su cloud personali o supporti non cifrati.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p><strong>A.8.24</strong> (uso della crittografia), <strong>A.8.5</strong> (autenticazione sicura), <strong>A.5.33</strong> (protezione delle registrazioni), <strong>A.5.17</strong> (informazioni di autenticazione), <strong>A.8.13</strong> (backup), <strong>A.5.23</strong> (sicurezza nei servizi cloud). Riferimenti cloud: ISO/IEC 27017/27018. Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, incluse crittografia, controllo accessi e continuità). Le ISO sono buone prassi, non obblighi di legge.</p>
|
||||
|
||||
<h2>8. Registrazioni ed evidenze</h2>
|
||||
<p>Inventario delle chiavi e dei certificati, log di rotazione, configurazioni TLS, evidenza della cifratura disco sui PC portatili, registro degli accessi al vault, log delle autorizzazioni doppie al recupero delle chiavi cloud.</p>
|
||||
|
||||
<h2>9. Riesame e versionamento</h2>
|
||||
<p>Approvata dal Responsabile SGSI. Documento vivo, riesaminato almeno annualmente o a fronte di nuove vulnerabilità crittografiche, incidenti o cambiamenti rilevanti. Versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,124 @@
|
||||
<!--META|doc_type=politica_uso_dispositivi|title=Politica di Uso Accettabile, Dispositivi e Lavoro da Remoto|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire, in un unico documento "persone e strumenti", le regole con cui <strong>Nuova Agile Technology srl</strong> classifica le informazioni e usa in sicurezza dispositivi, account e servizi aziendali. L'azienda opera con modello <strong>Work-from-Anywhere senza sede fissa né perimetro fisico</strong> e interamente in cloud: il portatile è il principale punto di esposizione e va trattato come asset critico. La Politica copre classificazione delle informazioni, uso accettabile degli strumenti, dispositivi dedicati e separazione privato/lavoro, lavoro da remoto, posta e servizi aziendali, hardening degli endpoint e uso sicuro dell'AI.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutto il personale (dipendenti e collaboratori esterni a P.IVA) che utilizza dispositivi, account e servizi aziendali (cloud Aruba/Hetzner, repository di codice, posta, strumenti di collaborazione, gestionali, piattaforme AI), ovunque si trovi e con qualsiasi rete. Riguarda tutte le informazioni gestite dall'Azienda — codice sorgente, documentazione, dati dei servizi SaaS inclusi i dati personali dei clienti, credenziali e segreti, documenti amministrativi e contrattuali — indipendentemente da formato e supporto. Non essendoci server in sede, l'attenzione è sugli <strong>endpoint mobili</strong> e sull'uso responsabile dei servizi cloud.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 Annex A: A.5.10, A.5.12, A.5.13, A.5.14, A.5.17, A.6.7, A.7.9, A.8.1, A.8.5, A.8.7, A.8.9, A.8.10, A.8.12, A.8.24, A.5.23.</li>
|
||||
<li>ISO/IEC 27018:2019 (protezione dei dati personali nel cloud); ISO/IEC 27017:2015 (servizi cloud).</li>
|
||||
<li>GDPR; Direttiva NIS2 / D.Lgs. 138/2024.</li>
|
||||
<li>Politica per la Sicurezza delle Informazioni; Politica di Controllo degli Accessi; Politica di Crittografia e Gestione delle Chiavi.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>Il <strong>Responsabile IT e Sicurezza (Simon Fattori)</strong> configura e mantiene gli endpoint sicuri (cifratura, aggiornamenti, antimalware, firewall), gestisce MDM, MFA, SSO, account e revoche, e implementa le misure tecniche per livello di classificazione. Il <strong>Responsabile SGSI (Massimo Tagliavini)</strong> mantiene lo schema di classificazione e vigila sul rispetto della Politica. Il <strong>DPO (consulente esterno)</strong> presidia il corretto trattamento dei dati personali. L'<strong>Alta Direzione (Silvia Garretto)</strong> approva la Politica. Ogni utente è personalmente responsabile dell'uso corretto degli strumenti assegnati e, come proprietario o utilizzatore dell'informazione, della sua corretta classificazione e gestione.</p>
|
||||
|
||||
<h2>5. Classificazione delle informazioni</h2>
|
||||
<h3>5.1 Schema di classificazione</h3>
|
||||
<table>
|
||||
<tr><th>Livello</th><th>Descrizione</th><th>Esempi</th></tr>
|
||||
<tr><td><strong>Pubblico</strong></td><td>Destinato alla diffusione, nessun impatto se divulgato.</td><td>Materiale marketing, documentazione pubblica del prodotto</td></tr>
|
||||
<tr><td><strong>Interno</strong></td><td>Uso interno; impatto limitato in caso di divulgazione non autorizzata.</td><td>Procedure interne, comunicazioni di team</td></tr>
|
||||
<tr><td><strong>Riservato</strong></td><td>Sensibile aziendale o di terzi; impatto significativo se divulgato.</td><td>Codice sorgente proprietario, contratti, dati clienti non personali</td></tr>
|
||||
<tr><td><strong>Strettamente riservato</strong></td><td>Massima sensibilità; impatto grave (legale, reputazionale, sanzionatorio).</td><td>Dati personali dei clienti nel SaaS, credenziali, chiavi API, segreti</td></tr>
|
||||
</table>
|
||||
<h3>5.2 Etichettatura e misure minime per livello</h3>
|
||||
<p>Documenti e repository riportano, ove pertinente, il livello di classificazione; i dati personali sono trattati come <strong>Strettamente riservati</strong> per impostazione predefinita.</p>
|
||||
<ul>
|
||||
<li><strong>Pubblico</strong>: nessuna restrizione di accesso particolare; verifica di accuratezza prima della pubblicazione.</li>
|
||||
<li><strong>Interno</strong>: accesso limitato al personale; non condividere all'esterno senza autorizzazione.</li>
|
||||
<li><strong>Riservato</strong>: accesso su base need-to-know; trasmissione cifrata; conservazione nei sistemi aziendali autorizzati; divieto di copia su supporti non controllati.</li>
|
||||
<li><strong>Strettamente riservato</strong>: cifratura obbligatoria a riposo e in transito; accesso con MFA e minimo privilegio; tracciabilità degli accessi; divieto di trasmissione a piattaforme AI senza minimizzazione/anonimizzazione; nessuna conservazione su supporti personali.</li>
|
||||
</ul>
|
||||
<h3>5.3 Dati personali nel SaaS, conservazione e cancellazione sicura</h3>
|
||||
<ul>
|
||||
<li>Trattamento secondo le istruzioni documentate dei clienti (in qualità di responsabile del trattamento), con isolamento per tenant; minimizzazione e limitazione della conservazione; supporto ai diritti degli interessati e alle richieste di cancellazione (ISO 27018 / GDPR).</li>
|
||||
<li>I periodi di conservazione sono definiti per categoria e in base a obblighi legali e contrattuali.</li>
|
||||
<li>La cancellazione avviene in modo sicuro (cancellazione logica irreversibile nei sistemi cloud, distruzione delle copie); per i PC portatili la dismissione prevede wipe sicuro o riformattazione con disco già cifrato.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Uso accettabile degli strumenti</h2>
|
||||
<ul>
|
||||
<li>Gli strumenti aziendali sono destinati prevalentemente a finalità professionali; un uso personale limitato e ragionevole è tollerato se non compromette sicurezza e produttività.</li>
|
||||
<li>È <strong>vietato</strong>: installare software non autorizzato o da fonti non attendibili; disattivare le misure di sicurezza (cifratura, antimalware, aggiornamenti); condividere credenziali; aggirare i controlli di accesso; trattare dati aziendali su servizi personali non approvati.</li>
|
||||
<li>Condividere informazioni riservate solo con destinatari autorizzati e tramite canali e strumenti aziendali approvati; evitare app di messaggistica personali per documenti di lavoro.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Dispositivi aziendali e separazione privato/lavoro</h2>
|
||||
<ul>
|
||||
<li><strong>Un portatile e uno smartphone aziendali per persona</strong>, dedicati e gestiti centralmente tramite <strong>MDM</strong>; l'accesso a sistemi e dati avviene esclusivamente da questi dispositivi, mai da dispositivi privati.</li>
|
||||
<li><strong>Uso solo professionale</strong>: niente uso personale, niente account privati, niente app fuori dal catalogo approvato; privilegi di amministratore limitati e concessi solo da IT.</li>
|
||||
<li><strong>Cifratura di default</strong>: full-disk encryption su portatili (BitLocker/FileVault/LUKS), smartphone cifrato con blocco schermo obbligatorio (PIN/biometria).</li>
|
||||
<li><strong>Secure-by-default</strong>: aggiornamenti automatici di sistema e applicazioni, antimalware attivo e aggiornato, firewall locale attivo, blocco automatico dello schermo dopo inattività, servizi non necessari disabilitati.</li>
|
||||
<li><strong>Nessuna credenziale in chiaro sul dispositivo</strong>: password e chiavi vivono nel password manager aziendale (vedi Politica di Crittografia e Gestione delle Chiavi).</li>
|
||||
<li><strong>MDM e controllo remoto</strong>: IT può applicare policy, distribuire aggiornamenti e, in caso di furto/smarrimento, eseguire blocco o cancellazione remota (wipe).</li>
|
||||
<li><strong>Separazione privato/lavoro</strong>: i dati personali del collaboratore non risiedono sui dispositivi aziendali e i dati aziendali non risiedono sui dispositivi privati; niente sincronizzazione con cloud personali. Così, in caso di wipe remoto, non si perdono dati personali perché non devono esserci.</li>
|
||||
<li><strong>Restituzione e fine rapporto</strong>: alla cessazione o riassegnazione il dispositivo è restituito, ripulito (wipe) e riconfigurato; gli accessi associati sono revocati e le chiavi ruotate.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Hardening del PC portatile</h2>
|
||||
<p>Passi minimi e obbligatori per configurare ogni portatile aziendale, indipendentemente dal sistema operativo:</p>
|
||||
<ol>
|
||||
<li><strong>Cifratura del disco (obbligatoria)</strong> prima di salvare qualsiasi dato (BitLocker con TPM+PIN / FileVault / LUKS); chiave di recupero solo nel password manager aziendale.</li>
|
||||
<li><strong>Account e privilegi</strong>: account standard per il lavoro quotidiano (non navigare né leggere email da account amministratore); account ospite/predefiniti rinominati o disabilitati.</li>
|
||||
<li><strong>Blocco schermo</strong> automatico dopo massimo 5 minuti di inattività con password/biometria allo sblocco; blocco manuale lasciando la postazione.</li>
|
||||
<li><strong>Firmware/UEFI</strong>: password al firmware ove possibile, Secure Boot abilitato, avvio da USB disabilitato se non necessario.</li>
|
||||
<li><strong>Firewall e servizi</strong>: firewall locale attivo; condivisione file/stampanti, desktop remoto e servizi non usati disattivati.</li>
|
||||
<li><strong>Antimalware</strong> attivo con protezione in tempo reale e aggiornamenti automatici.</li>
|
||||
<li><strong>Aggiornamenti</strong> automatici di sistema e applicazioni abilitati.</li>
|
||||
<li><strong>Backup</strong>: lavoro su repository e storage cloud aziendali (codice versionato in git, documenti sui drive aziendali), non solo in locale; si evita l'accumulo di dati riservati in locale e l'uso di supporti rimovibili non cifrati.</li>
|
||||
<li><strong>Software</strong>: solo da fonti ufficiali e necessario al lavoro; rimosse applicazioni inutili ed estensioni browser non indispensabili.</li>
|
||||
<li><strong>MDM</strong>: non rimuovere l'agente né eludere le policy applicate.</li>
|
||||
</ol>
|
||||
<p><strong>Verifiche di esito</strong>: disco cifrato con chiave nel password manager; blocco schermo ≤ 5 minuti; firewall e antimalware attivi con ultimo aggiornamento entro 7 giorni; nessun account amministratore usato per attività quotidiane.</p>
|
||||
|
||||
<h2>9. Lavoro da remoto e mobilità</h2>
|
||||
<ul>
|
||||
<li><strong>Solo dispositivi aziendali</strong> e accesso sempre con <strong>MFA e SSO</strong>; le sessioni hanno timeout automatico.</li>
|
||||
<li><strong>Reti non fidate</strong>: su Wi-Fi pubblici o di terzi usare sempre connessioni cifrate (HTTPS/TLS, VPN aziendale ove prevista); mai disattivare la cifratura per "far funzionare" un servizio.</li>
|
||||
<li><strong>Privacy fisica dello schermo</strong>: in luoghi pubblici evitare la visibilità (shoulder surfing), usare filtri privacy quando possibile, bloccare lo schermo allontanandosi anche per pochi minuti.</li>
|
||||
<li><strong>Asset fuori sede</strong>: dispositivi mai lasciati incustoditi in auto, mezzi o spazi pubblici; in viaggio sempre con sé o in luogo sicuro.</li>
|
||||
<li><strong>Nessun dato sensibile in chiaro fuori dai sistemi gestiti</strong>: niente download su supporti non cifrati, stampe non necessarie o copie su servizi personali.</li>
|
||||
</ul>
|
||||
|
||||
<h2>10. Posta elettronica e servizi aziendali</h2>
|
||||
<ul>
|
||||
<li><strong>Account aziendali, non personali</strong>: il lavoro si svolge solo con caselle e account forniti e amministrati dalla società; vietato usare account personali per attività lavorative.</li>
|
||||
<li><strong>Controllo della società</strong>: l'azienda amministra creazione, configurazione, sospensione e cancellazione degli account (admin centralizzato, SSO), nel rispetto della normativa privacy e con il coinvolgimento del DPO per gli aspetti di protezione dei dati personali.</li>
|
||||
<li><strong>Niente inoltro a caselle personali</strong>: vietato l'auto-inoltro o l'inoltro sistematico di email di lavoro verso indirizzi personali o esterni non autorizzati.</li>
|
||||
<li><strong>MFA obbligatoria</strong> su email e su tutti i servizi; password robuste gestite nel password manager aziendale; <strong>SSO ove possibile</strong> per centralizzare controllo e revoche.</li>
|
||||
<li><strong>Ciclo di vita degli account</strong>: onboarding con privilegi minimi necessari (least privilege), diritti rivisti periodicamente secondo il ruolo, offboarding con disattivazione e revoca tempestiva alla cessazione del rapporto (la casella resta sotto controllo della società).</li>
|
||||
</ul>
|
||||
|
||||
<h2>11. Posta, phishing e segnalazione degli eventi</h2>
|
||||
<ul>
|
||||
<li>Prudenza con allegati e link sospetti; verifica dell'identità del mittente prima di azioni sensibili (es. cambi di pagamento, invio credenziali).</li>
|
||||
<li>Segnalazione tempestiva al Responsabile IT/SGSI di tentativi di phishing, social engineering, anomalie o sospette compromissioni.</li>
|
||||
<li>Smarrimento o furto di un dispositivo o accesso anomalo va segnalato <strong>immediatamente</strong> al Responsabile IT: IT procede con blocco/wipe remoto via MDM e revoca/rotazione delle credenziali coinvolte. Le chiavi di accesso al cloud restano recuperabili grazie al key escrow controllato (vedi Politica di Crittografia e Gestione delle Chiavi), quindi lo smarrimento del dispositivo non causa perdita di accesso.</li>
|
||||
<li>La segnalazione consente la valutazione e, se necessario, la notifica secondo gli obblighi NIS2 (pre-allarme entro 24 ore, notifica entro 72 ore, relazione finale entro 1 mese al CSIRT Italia/ACN, ove l'Azienda risulti soggetto obbligato, art. 25 D.Lgs. 138/2024).</li>
|
||||
</ul>
|
||||
|
||||
<h2>12. Uso sicuro delle piattaforme AI</h2>
|
||||
<ul>
|
||||
<li><strong>Solo strumenti approvati</strong> dal Resp. IT, protetti da MFA; eventuali accordi/DPA con il fornitore verificati da Resp. IT/DPO.</li>
|
||||
<li><strong>Mai dati personali dei clienti</strong> nei prompt (nomi, email, contatti, dati contenuti nel SaaS) salvo accordo/DPA adeguato e minimizzazione.</li>
|
||||
<li><strong>Mai segreti</strong>: password, chiavi API, token, certificati, stringhe di connessione o configurazioni di produzione restano nel gestore segreti, non nei prompt.</li>
|
||||
<li><strong>Minimizza e anonimizza</strong>: sostituisci i dati identificativi con segnaposto (es. "CLIENTE_X"); condividi solo il minimo necessario.</li>
|
||||
<li><strong>Codice sorgente</strong>: non caricare interi repository proprietari o codice contenente segreti; estrai solo lo snippet rilevante e ripulito.</li>
|
||||
<li><strong>Verifica gli output</strong>: tratta le risposte come bozze da verificare per correttezza, sicurezza del codice e assenza di riferimenti inventati prima dell'uso in produzione o verso i clienti.</li>
|
||||
<li><strong>Impostazioni privacy</strong>: dove possibile, disabilita l'uso dei tuoi dati per l'addestramento e usa i piani/impostazioni aziendali concordati.</li>
|
||||
<li><strong>Nel dubbio non inviare</strong> e chiedi prima al Resp. IT. Se hai inviato per errore dati personali o segreti, contatta subito il Resp. IT (e il DPO per i dati personali): può configurare un incidente da valutare.</li>
|
||||
</ul>
|
||||
|
||||
<h2>13. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p><strong>A.5.10</strong> (uso accettabile), <strong>A.5.12/A.5.13/A.5.14</strong> (classificazione, etichettatura, trasferimento delle informazioni), <strong>A.5.17</strong> (informazioni di autenticazione), <strong>A.6.7</strong> (lavoro da remoto), <strong>A.7.9</strong> (sicurezza degli asset fuori sede), <strong>A.8.1</strong> (dispositivi endpoint), <strong>A.8.5</strong> (autenticazione sicura), <strong>A.8.7</strong> (protezione dai malware), <strong>A.8.9</strong> (gestione della configurazione), <strong>A.8.10/A.8.12</strong> (cancellazione delle informazioni, prevenzione della fuga di dati), <strong>A.8.24</strong> (crittografia), <strong>A.5.23</strong> (sicurezza nei servizi cloud). Cloud: ISO/IEC 27017/27018. Riferimenti NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, tra cui igiene informatica di base, crittografia, controllo accessi e formazione) e <strong>art.25</strong> (obblighi di notifica degli incidenti). Le ISO sono buone prassi, non obblighi di legge.</p>
|
||||
|
||||
<h2>14. Registrazioni ed evidenze</h2>
|
||||
<p>Accettazione della Politica da parte del personale; schema di classificazione e inventario degli asset informativi con livello; inventario degli endpoint con stato di cifratura e aggiornamento; registro delle segnalazioni di eventi/incidenti; registro dei trattamenti (GDPR) ed evidenze di cancellazione sicura.</p>
|
||||
|
||||
<h2>15. Riesame e versionamento</h2>
|
||||
<p>Approvata dall'Alta Direzione. Documento vivo, riesaminato almeno annualmente o a fronte di nuove minacce rilevanti, cambiamenti del parco dispositivi o adozione di nuovi strumenti (incl. AI). Versione tracciata nella piattaforma SGSI.</p>
|
||||
@@ -0,0 +1,91 @@
|
||||
<!--META|doc_type=politica_cloud_privacy|title=Politica di Sicurezza del Cloud e Protezione dei Dati (ISO 27017/27018, GDPR)|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> utilizza in sicurezza i servizi cloud su cui poggia interamente la propria operatività e protegge i dati personali (PII) che vi tratta per conto dei propri clienti. Poiché l'infrastruttura aziendale è <strong>integralmente in cloud</strong> (Aruba S.p.A. – Italia; Hetzner – Germania, UE) e in sede non esiste alcun server, la corretta configurazione e gestione dei servizi cloud è essenziale per la sicurezza delle informazioni dell'azienda e dei clienti. La Politica recepisce i controlli cloud-specifici della <strong>ISO/IEC 27017:2015</strong> (chiarendo il <strong>modello di responsabilità condivisa</strong> tra azienda e fornitori) e i controlli della <strong>ISO/IEC 27018:2019</strong> per la tutela dei PII nei cloud pubblici, in coerenza con il <strong>Regolamento (UE) 2016/679 (GDPR)</strong>. Nel trattare i PII via <strong>SaaS multi-tenant</strong>, l'azienda agisce di norma come <strong>responsabile del trattamento</strong> per conto del cliente (titolare).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutti i servizi cloud utilizzati per: l'erogazione del SaaS multi-tenant; l'ospitalità di ambienti di sviluppo, test e strumenti interni; l'archiviazione di dati aziendali e dei clienti; i servizi di intelligenza artificiale fruiti via API (es. LLM Anthropic). Copre tutti i trattamenti di PII di clienti e interessati finali svolti tramite l'infrastruttura cloud (Aruba IT, Hetzner DE) e i servizi accessori, per l'intero ciclo di vita del dato: raccolta, archiviazione, elaborazione, trasferimento, conservazione e cancellazione. Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) che configura, amministra o accede a tali servizi — esclusivamente tramite <strong>PC portatili cifrati</strong> — e ai sub-fornitori che concorrono al trattamento.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27017:2015 – controlli cloud-specifici (CLD.6.3, CLD.8.1, CLD.9.5, CLD.12.1, CLD.12.4, CLD.13.1) e modello di responsabilità condivisa.</li>
|
||||
<li>ISO/IEC 27018:2019 – codice di condotta per la protezione dei PII nei cloud pubblici.</li>
|
||||
<li>ISO/IEC 27001:2022 – Annex A, in particolare A.5.23 (uso di servizi cloud), A.5.34 (privacy e protezione dei PII), A.8.9, A.8.10, A.8.11, A.8.12, A.8.13, A.8.15, A.8.24.</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR) – artt. 5, 28, 30, 32, 33, 44-49.</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio) e art. 25 (notifica incidenti).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e l'adozione/dismissione di servizi cloud strategici; garantisce le risorse per la conformità privacy.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, coordina la valutazione dei rischi cloud, integra i requisiti privacy nel SGSI e gestisce i riesami.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Configura e amministra i servizi cloud secondo la responsabilità condivisa; attua le misure tecniche (accessi, cifratura, log, hardening, cancellazione).</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Sorveglia la conformità GDPR/27018, supporta i DPA, le richieste degli interessati, i trasferimenti di dati personali e la gestione dei data breach.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Modello di responsabilità condivisa</h2>
|
||||
<p>Per ogni servizio cloud è documentata la ripartizione delle responsabilità di sicurezza tra <strong>fornitore</strong> (sicurezza fisica dei data center, hypervisor, rete sottostante, disponibilità dell'infrastruttura) e <strong>azienda cliente</strong> (configurazione dei servizi, gestione degli account e degli accessi, cifratura dei dati a riposo e in transito, gestione delle chiavi, backup applicativi, logging). La consapevolezza di "ciò che il fornitore fa per noi e ciò che dobbiamo fare noi" è la base della Politica (CLD.6.3).</p>
|
||||
|
||||
<h2>6. Selezione, collocazione e trasferimenti</h2>
|
||||
<ul>
|
||||
<li>Si privilegiano fornitori cloud con certificazioni di sicurezza riconosciute e data center nell'UE/SEE (Aruba in Italia, Hetzner in Germania) per favorire la conformità GDPR; i PII sono ospitati di preferenza su tali data center.</li>
|
||||
<li>Eventuali servizi e trasferimenti extra-UE (es. API AI) sono adottati solo con garanzie adeguate ai sensi degli artt. 44-49 GDPR, DPA idonei e documentazione del trasferimento.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Ruolo di responsabile, trasparenza e sub-fornitori</h2>
|
||||
<ul>
|
||||
<li>L'azienda tratta i PII dei clienti <strong>solo su istruzione documentata del titolare</strong> (art. 28 GDPR; principio 27018) e per le finalità del servizio, senza usi propri non autorizzati.</li>
|
||||
<li>Con ogni cliente che affida PII è stipulato un <strong>DPA ex art. 28 GDPR</strong> che disciplina finalità, durata, misure di sicurezza, sub-responsabili e assistenza al titolare.</li>
|
||||
<li>I sub-fornitori che trattano PII (provider cloud, eventuali servizi AI) sono dichiarati al cliente e vincolati a obblighi di protezione equivalenti; i cambi rilevanti di sub-fornitore sono comunicati al titolare secondo gli accordi.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Misure di sicurezza tecniche</h2>
|
||||
<ul>
|
||||
<li>Cifratura dei dati e dei PII in transito (TLS) e a riposo; gestione sicura delle chiavi (A.8.24).</li>
|
||||
<li>Accessi amministrativi nominali, autenticazione forte (MFA), principio del minimo privilegio, separazione dei ruoli e revisione periodica delle utenze, con revoca tempestiva al termine del rapporto; tracciamento degli accessi ai PII. L'accesso avviene solo da PC portatili cifrati e aggiornati.</li>
|
||||
<li>Isolamento e segregazione dei dati tra tenant nel SaaS multi-tenant (CLD.9.5); gli ambienti di test non utilizzano PII reali (dati anonimizzati/sintetici).</li>
|
||||
<li>Configurazioni sicure (hardening) e disabilitazione dei servizi non necessari.</li>
|
||||
</ul>
|
||||
|
||||
<h2>9. Logging, monitoraggio e gestione operativa</h2>
|
||||
<ul>
|
||||
<li>Abilitazione dei log di sicurezza, degli accessi amministrativi e delle operazioni sui PII resi disponibili dai fornitori; conservazione e revisione periodica (CLD.12.1, CLD.12.4).</li>
|
||||
<li>Monitoraggio della disponibilità dei servizi e degli avvisi di sicurezza dei fornitori cloud.</li>
|
||||
<li>Gestione coordinata dei cambiamenti che impattano i servizi cloud.</li>
|
||||
</ul>
|
||||
|
||||
<h2>10. Diritti degli interessati, continuità, conservazione e cancellazione</h2>
|
||||
<ul>
|
||||
<li>L'azienda assiste il titolare nel dare seguito alle richieste degli interessati (accesso, rettifica, cancellazione, portabilità, opposizione), fornendo gli strumenti tecnici per individuare ed estrarre/cancellare i dati nel SaaS.</li>
|
||||
<li>Backup e ripristino sono gestiti coerentemente con la Politica di Continuità, tenendo conto della responsabilità condivisa.</li>
|
||||
<li>I PII sono conservati per il tempo previsto dal contratto/dalla finalità; alla cessazione di un servizio o del rapporto si garantisce l'esportazione dei dati e la loro cancellazione sicura, anche presso i provider cloud (CLD.8.1).</li>
|
||||
</ul>
|
||||
|
||||
<h2>11. Gestione incidenti e violazioni di dati personali</h2>
|
||||
<p>Gli incidenti che coinvolgono i servizi cloud sono gestiti secondo il processo di gestione incidenti del SGSI. In caso di data breach, l'azienda informa <strong>senza ingiustificato ritardo</strong> il titolare (art. 33 GDPR), supportandolo nelle valutazioni e notifiche. Se l'evento è anche un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024); il coordinamento tra notifica privacy e notifica NIS2 è gestito dal DPO con il RSGSI.</p>
|
||||
|
||||
<h2>12. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>CLD.6.3</strong> – Ripartizione delle responsabilità tra cliente e fornitore cloud.</li>
|
||||
<li><strong>CLD.8.1</strong> – Rimozione/restituzione degli asset cliente al termine del servizio.</li>
|
||||
<li><strong>CLD.9.5</strong> – Segregazione negli ambienti virtuali multi-tenant.</li>
|
||||
<li><strong>CLD.12.1 / CLD.12.4</strong> – Operatività e logging dei servizi cloud.</li>
|
||||
<li><strong>CLD.13.1</strong> – Gestione della sicurezza di rete nel cloud.</li>
|
||||
<li>Controlli <strong>ISO/IEC 27018</strong>: consenso e scopo, trasparenza sui sub-fornitori, cancellazione sicura, notifica accessi, restrizione d'uso dei PII, tracciamento.</li>
|
||||
<li><strong>A.5.23</strong> – Sicurezza nell'uso di servizi cloud; <strong>A.5.34</strong> – privacy e protezione dei PII.</li>
|
||||
<li><strong>A.8.10 / A.8.11</strong> – Cancellazione e mascheramento dei dati; <strong>A.8.12</strong> – prevenzione della fuga di dati; <strong>A.8.24</strong> – uso della crittografia.</li>
|
||||
<li><strong>GDPR</strong>: artt. 5, 28, 30, 32, 33, 44-49.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 24 (misure di gestione del rischio), art. 25 (notifica incidenti).</li>
|
||||
</ul>
|
||||
|
||||
<h2>13. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Matrice di responsabilità condivisa per ciascun servizio cloud; inventario dei servizi cloud e relative configurazioni di sicurezza.</li>
|
||||
<li>DPA con i clienti e con i sub-responsabili; elenco dei sub-fornitori; registro dei trattamenti per conto del titolare (supporto art. 30 GDPR).</li>
|
||||
<li>Registro degli accessi amministrativi e ai PII; log di sicurezza conservati; registro delle richieste degli interessati.</li>
|
||||
<li>Registro dei data breach e relative comunicazioni al titolare; evidenze dei riesami periodici di configurazioni e accessi.</li>
|
||||
</ul>
|
||||
|
||||
<h2>14. Riesame e versionamento</h2>
|
||||
<p>La Politica è riesaminata almeno annualmente o a fronte dell'adozione di nuovi servizi cloud, nuovi trattamenti, cambi di sub-fornitori, modifiche architetturali, aggiornamenti normativi o incidenti rilevanti. Versione corrente: <strong>2.0</strong>, approvata dalla Presidente con il parere del DPO. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,89 @@
|
||||
<!--META|doc_type=politica_fornitori|title=Politica e Procedura di Gestione dei Fornitori e dei Servizi Cloud|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire principi, regole e passi operativi con cui <strong>Nuova Agile Technology srl</strong> seleziona, qualifica, contrattualizza, sorveglia e dismette i propri fornitori, con particolare attenzione a quelli rilevanti per la sicurezza delle informazioni: infrastruttura cloud (<strong>Aruba</strong> IT, <strong>Hetzner</strong> DE), servizi di <strong>intelligenza artificiale</strong> via API (es. LLM Anthropic), sviluppo e manutenzione software. Lo scopo è mantenere il livello di sicurezza richiesto dal SGSI anche quando attività o dati sono affidati a terze parti, in modo ripetibile e tracciabile, e propagare lungo la supply chain gli obblighi di sicurezza previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutti i fornitori, sub-fornitori e prestatori di servizi, nuovi ed esistenti, che: (a) trattano, ospitano o accedono a informazioni aziendali o dei clienti; (b) erogano infrastruttura cloud (Aruba – Italia; Hetzner – Germania, UE); (c) forniscono servizi AI tramite API; (d) contribuiscono allo sviluppo, manutenzione o assistenza dei prodotti, sia in licenza on-premise sia SaaS multi-tenant. Coinvolge tutto il personale che richiede, valuta e gestisce i fornitori (9 dipendenti, 2 collaboratori esterni a P.IVA).</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – Annex A: A.5.19, A.5.20, A.5.21, A.5.22, A.5.23.</li>
|
||||
<li>ISO/IEC 27017:2015 (CLD.6.3, CLD.8.1; modello di responsabilità condivisa) e ISO/IEC 27018:2019 (tutela delle PII nel cloud).</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (sicurezza della supply chain) e art. 25 (notifica incidenti).</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR), art. 28 (responsabili del trattamento).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva il documento, autorizza l'ingaggio dei fornitori critici e accetta i rischi residui.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene il documento, classifica i fornitori, gestisce il registro, coordina valutazioni e riesami periodici.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Valuta tecnicamente i fornitori, verifica certificazioni e misure, definisce la matrice di responsabilità condivisa, monitora SLA e incidenti.</td></tr>
|
||||
<tr><td>DPO (esterno)</td><td>Valida gli aspetti privacy, i DPA e i trasferimenti di dati personali.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Principi</h2>
|
||||
<ul>
|
||||
<li><strong>Classificazione per criticità</strong>: ogni fornitore è classificato in funzione del dato e del servizio — <strong>critico</strong> (cloud, AI, accesso a PII di clienti), <strong>rilevante</strong> (sviluppo/manutenzione), <strong>ordinario</strong> (servizi non legati alle informazioni). La classe determina la profondità della due diligence.</li>
|
||||
<li><strong>Dati nell'UE</strong>: si privilegiano fornitori con certificazioni riconosciute e data center nell'UE/SEE (Aruba in Italia, Hetzner in Germania); per trasferimenti extra-UE si verificano garanzie adeguate.</li>
|
||||
<li><strong>Propagazione lungo la supply chain</strong>: essendo l'azienda fornitore di clienti soggetti a NIS2, i requisiti di sicurezza ricevuti dai clienti vengono recepiti e ribaltati sui propri sub-fornitori, così che le misure scendano coerentemente lungo l'intera catena (art. 24 D.Lgs. 138/2024).</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Processo</h2>
|
||||
<h3>6.1 Richiesta e classificazione</h3>
|
||||
<ul>
|
||||
<li>Il richiedente apre una richiesta indicando servizio, dati coinvolti e finalità.</li>
|
||||
<li>Il RSGSI assegna la classe di criticità (critico / rilevante / ordinario).</li>
|
||||
</ul>
|
||||
<h3>6.2 Due diligence e valutazione di sicurezza</h3>
|
||||
<ul>
|
||||
<li>Per i fornitori critici/rilevanti il Resp. IT raccoglie evidenze: certificazioni (ISO 27001/27017/27018, SOC 2), localizzazione dei data center, misure tecniche/organizzative, gestione incidenti.</li>
|
||||
<li>Si compila una scheda di valutazione con esito (idoneo / idoneo con prescrizioni / non idoneo).</li>
|
||||
<li>Per i servizi cloud si definisce la <strong>matrice di responsabilità condivisa</strong> (cosa fa il fornitore, cosa fa l'azienda).</li>
|
||||
</ul>
|
||||
<h3>6.3 Contrattualizzazione (clausole e DPA)</h3>
|
||||
<ul>
|
||||
<li>Il contratto include clausole di sicurezza: riservatezza, SLA, obbligo di notifica incidenti, diritto a evidenze/audit, gestione del fine rapporto e restituzione/cancellazione dei dati.</li>
|
||||
<li>Quando sono trattati dati personali si stipula un <strong>DPA ex art. 28 GDPR</strong>, verificando i sub-responsabili e le clausole 27018.</li>
|
||||
<li>Si richiede al fornitore la notifica tempestiva degli incidenti, coerente con i tempi NIS2 (pre-allarme 24h, notifica 72h, relazione finale 1 mese) quando l'azienda è soggetto obbligato.</li>
|
||||
<li>L'attivazione dei fornitori critici richiede l'autorizzazione della Presidente.</li>
|
||||
</ul>
|
||||
<h3>6.4 Attivazione e configurazione sicura</h3>
|
||||
<ul>
|
||||
<li>Accessi nominali con MFA e minimo privilegio, cifratura e logging.</li>
|
||||
<li>Il fornitore è inserito nel <strong>registro fornitori</strong> con classificazione, scadenze contrattuali e referenti.</li>
|
||||
</ul>
|
||||
<h3>6.5 Monitoraggio e riesame periodico</h3>
|
||||
<ul>
|
||||
<li>Almeno annualmente per i fornitori critici: verifica SLA, rinnovo evidenze e certificazioni, revisione accessi, analisi di eventuali incidenti.</li>
|
||||
<li>Valutazione degli avvisi di sicurezza dei fornitori cloud/AI e del loro impatto; gli esiti sono registrati.</li>
|
||||
<li>Le non conformità generano azioni correttive tracciate; in caso di non conformità grave o incidente, si valuta la sostituzione del fornitore.</li>
|
||||
</ul>
|
||||
<h3>6.6 Gestione incidenti del fornitore</h3>
|
||||
<p>In caso di incidente comunicato dal fornitore o rilevato dall'azienda si attiva il processo di gestione incidenti del SGSI; se l'evento è un incidente NIS2 significativo e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese; art. 25 D.Lgs. 138/2024). Gli incidenti che coinvolgono PII sono comunicati al DPO.</p>
|
||||
<h3>6.7 Dismissione e fine rapporto</h3>
|
||||
<ul>
|
||||
<li>Revoca degli accessi, recupero/esportazione dei dati e verifica della cancellazione sicura presso il fornitore (CLD.8.1).</li>
|
||||
<li>Aggiornamento del registro fornitori e valutazione dell'impatto sulla continuità.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>A.5.19 / A.5.20</strong> – Sicurezza nei rapporti e negli accordi con i fornitori.</li>
|
||||
<li><strong>A.5.21</strong> – Sicurezza nella catena di fornitura ICT.</li>
|
||||
<li><strong>A.5.22</strong> – Monitoraggio, riesame e gestione dei cambiamenti dei servizi dei fornitori.</li>
|
||||
<li><strong>A.5.23</strong> – Sicurezza nell'uso di servizi cloud; <strong>CLD.6.3 / CLD.8.1</strong> (ISO 27017); ISO 27018 per le PII.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 24 (sicurezza della supply chain); art. 25 (notifica incidenti).</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Registro dei fornitori (classificazione di criticità, scadenze, referenti, stato di qualifica).</li>
|
||||
<li>Schede di valutazione di sicurezza e matrici di responsabilità condivisa.</li>
|
||||
<li>Contratti, DPA e clausole di sicurezza archiviati.</li>
|
||||
<li>Verbali dei riesami periodici, registro azioni correttive e registro incidenti dei fornitori.</li>
|
||||
</ul>
|
||||
|
||||
<h2>9. Riesame e versionamento</h2>
|
||||
<p>Il documento è riesaminato almeno annualmente o a fronte di cambiamenti significativi (nuovi fornitori critici, incidenti rilevanti, evoluzioni normative). Approvato dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI con numero di versione, data e responsabile dell'aggiornamento (RSGSI).</p>
|
||||
@@ -0,0 +1,48 @@
|
||||
<!--META|doc_type=procedura_gestione_rischio|title=Procedura di Gestione del Rischio (valutazione e trattamento)|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le modalità con cui <strong>Nuova Agile Technology srl</strong> identifica, analizza, valuta e tratta i rischi per la sicurezza delle informazioni, in modo sistematico e ripetibile. Attua i requisiti ISO/IEC 27001:2022 cl. 6.1.2 (valutazione), 6.1.3 (trattamento), 8.2 e 8.3 (esecuzione) e supporta la determinazione delle misure ex art. 24 D.Lgs. 138/2024 (NIS2) per i soggetti rientranti.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutte le informazioni e agli asset aziendali: codice sorgente e prodotti on-premise, piattaforma SaaS in cloud, dati personali dei clienti, infrastruttura cloud (Aruba IT, Hetzner DE, piattaforme AI) e PC portatili. Non essendoci server in sede, l'analisi considera i rischi di un modello <em>cloud-only</em> (responsabilità condivisa; controlli ISO/IEC 27017 e 27018 per le PII).</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 6.1.2, 6.1.3, 8.2, 8.3; controlli A.5.7, A.5.9, A.8.8.</li>
|
||||
<li>ISO/IEC 27005 (stima del rischio) e ISO 31000.</li>
|
||||
<li>ISO/IEC 27017 §CLD.6.3 e 27018 (rischi cloud e PII).</li>
|
||||
<li>D.Lgs. 138/2024 art. 24; Determinazione ACN 164179/2025 [DA VERIFICARE riferimento applicabile].</li>
|
||||
<li>SoA, Politica del SGSI, Procedura NC/Azioni Correttive.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<ul>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: approva criteri e propensione al rischio (risk appetite) e il piano di trattamento; accetta formalmente i rischi residui.</li>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: conduce e coordina la valutazione, mantiene il registro dei rischi, propone il piano di trattamento, monitora le scadenze.</li>
|
||||
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: fornisce l'inventario asset e le informazioni su minacce/vulnerabilità, implementa i controlli tecnici.</li>
|
||||
<li><strong>DPO esterno</strong>: consultato per i rischi sui dati personali (PII nel SaaS).</li>
|
||||
</ul>
|
||||
|
||||
<h2>5. Flusso passo-passo</h2>
|
||||
<ol>
|
||||
<li><strong>Contesto e criteri</strong> (input: SoA, inventario asset). RSGSI e Direzione fissano scala di probabilità (1–5) e impatto (1–5), soglia di accettabilità e criteri di significatività.</li>
|
||||
<li><strong>Identificazione</strong> (input: asset, minacce, vulnerabilità). Per ciascun asset si individuano le minacce plausibili (es. compromissione credenziali cloud, data breach SaaS, perdita di un portatile, indisponibilità del provider) e si registra ogni rischio nel <em>modulo "Rischi"</em>.</li>
|
||||
<li><strong>Analisi e ponderazione</strong>. Rischio = Probabilità × Impatto secondo la matrice 5×5 (coerente con ISO/IEC 27005); il punteggio determina la classe (basso/medio/alto/critico).</li>
|
||||
<li><strong>Valutazione</strong>: confronto con la soglia. I rischi sopra soglia richiedono trattamento; quelli sotto soglia sono candidati all'accettazione.</li>
|
||||
<li><strong>Trattamento</strong>. Per ogni rischio sopra soglia si sceglie l'opzione: <em>mitigazione</em> (controlli Annex A), <em>trasferimento</em> (clausole contrattuali col provider, assicurazione), <em>evitamento</em> o <em>accettazione</em>. I controlli scelti sono registrati come trattamenti e riconciliati con la SoA.</li>
|
||||
<li><strong>Approvazione e accettazione del rischio residuo</strong>: la Direzione approva il piano e accetta formalmente i rischi residui (cl. 6.1.3 e).</li>
|
||||
<li><strong>Attuazione e monitoraggio</strong>: il Resp. IT implementa i controlli; il RSGSI traccia stato e scadenze. Le carenze rilevate diventano Non Conformità.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO/clausole collegati</h2>
|
||||
<p>cl. 6.1.2/6.1.3 (processo e piano), cl. 8.2/8.3 (esecuzione e SoA aggiornata); A.5.7 (threat intelligence), A.5.9 (inventario asset), A.8.8 (gestione vulnerabilità tecniche); per il cloud A.5.19–A.5.23 e i controlli estesi ISO/IEC 27017 e 27018 sulle PII.</p>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li><strong>Registro dei rischi e trattamenti</strong>: modulo "Rischi" (rischio inerente, controlli, rischio residuo, owner, scadenze).</li>
|
||||
<li><strong>Piano di trattamento e accettazione rischi residui</strong>: verbale di approvazione della Direzione, archiviato come informazione documentata.</li>
|
||||
<li><strong>SoA aggiornata</strong> (modulo SGSI/SoA).</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La valutazione è riesaminata <strong>almeno una volta l'anno</strong> e a ogni cambiamento significativo (nuovo prodotto/servizio, nuovo provider, incidente rilevante, modifica normativa). Gli esiti alimentano il Riesame della Direzione. La procedura è versionata secondo la Procedura di Controllo dei Documenti.</p>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!--META|doc_type=procedura_incident|title=Procedura di Gestione degli Incidenti e Notifica NIS2|status=approved|version=2.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le modalità con cui <strong>Nuova Agile Technology srl</strong> (l'"Azienda") rileva, classifica, gestisce e — quando dovuto — notifica gli incidenti di sicurezza delle informazioni, assicurando risposta tempestiva, riduzione degli impatti e rispetto degli obblighi di notifica NIS2. Disciplina anche la <strong>comunicazione ai clienti impattati</strong> nel ruolo dell'Azienda quale fornitore di soggetti NIS2.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutti gli incidenti che riguardano riservatezza, integrità o disponibilità di informazioni e servizi: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), <strong>SaaS</strong> multi-tenant, <strong>endpoint</strong> (PC portatili cifrati), <strong>piattaforme AI</strong> via API e i prodotti in licenza on-premise per la parte di responsabilità dell'Azienda. Non riguarda sale server in sede, <strong>assenti</strong> per l'architettura cloud-only.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.5.24, A.5.25, A.5.26, A.5.27, A.5.28 (gestione degli incidenti) e A.5.7 (threat intelligence).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione degli incidenti nei servizi cloud e sui dati personali.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): <strong>art. 23 (governance)</strong>, <strong>art. 24 (misure di gestione del rischio)</strong>, <strong>art. 25 (obblighi di notifica degli incidenti)</strong>.</li>
|
||||
<li><strong>Determinazione ACN n. 164179/2025</strong> e Determinazioni vigenti per criteri di significatività, tassonomia e modalità di notifica al CSIRT Italia tramite la piattaforma ACN.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Decide la notifica alle Autorità e la comunicazione esterna; informa gli organi di gestione (art. 23).</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Coordina la gestione dell'incidente, cura le registrazioni e la relazione finale.</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Contiene, eradica e ripristina; raccoglie le evidenze tecniche.</td></tr>
|
||||
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta se vi è violazione di dati personali e gli obblighi GDPR verso il Garante.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso passo-passo</h2>
|
||||
<ol>
|
||||
<li><strong>Rilevazione e segnalazione.</strong> Chiunque rilevi un evento sospetto lo segnala immediatamente a Simon Fattori. L'evento è registrato nel <strong>modulo "Incidenti"</strong> con data e ora di conoscenza.</li>
|
||||
<li><strong>Triage e classificazione.</strong> Simon Fattori, con Massimo Tagliavini, valuta natura, origine e impatto (riservatezza/integrità/disponibilità) e assegna una severità. Si verifica se l'incidente è <strong>"significativo"</strong> secondo i criteri di legge e delle Determinazioni ACN (impatto operativo grave, numero di utenti coinvolti, durata, danno economico/reputazionale, effetti transfrontalieri). In caso di dubbio: <strong>[DA VERIFICARE]</strong> con la Direzione e il supporto consulenziale.</li>
|
||||
<li><strong>Contenimento, eradicazione, ripristino.</strong> Simon Fattori isola sistemi/account compromessi, rimuove la causa e ripristina il servizio anche tramite backup, tracciando i tempi delle fasi.</li>
|
||||
<li><strong>Notifica al CSIRT Italia (ACN)</strong> — se l'incidente è significativo, secondo le tempistiche dell'<strong>art. 25 del D.Lgs. 138/2024</strong>:
|
||||
<ul>
|
||||
<li><strong>Pre-allarme (early warning) entro 24 ore</strong> dalla conoscenza dell'incidente significativo;</li>
|
||||
<li><strong>Notifica completa entro 72 ore</strong> dalla conoscenza;</li>
|
||||
<li><strong>Relazione finale entro 1 mese</strong> dalla notifica completa (e relazioni intermedie se richieste).</li>
|
||||
</ul>
|
||||
La notifica avviene tramite la piattaforma ACN; decide e autorizza la Direzione.</li>
|
||||
<li><strong>Valutazione dati personali.</strong> In caso di violazione di dati personali, il DPO valuta gli obblighi GDPR (eventuale notifica al Garante entro 72 ore e comunicazione agli interessati).</li>
|
||||
<li><strong>Comunicazione ai clienti impattati.</strong> Quale <strong>fornitore di clienti NIS2</strong>, l'Azienda informa <strong>tempestivamente</strong> i clienti i cui servizi/dati sono coinvolti, secondo gli <strong>obblighi contrattuali di supply chain</strong>, fornendo gli elementi utili ai loro adempimenti di notifica.</li>
|
||||
<li><strong>Chiusura e lezioni apprese.</strong> A risoluzione si redige la relazione finale, si aprono eventuali <strong>non conformità</strong> e azioni correttive e si aggiorna il <strong>modulo "Rischi"</strong> se emergono nuovi rischi.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO e riferimenti NIS2 (tempistiche)</h2>
|
||||
<p>La procedura attua i controlli <strong>A.5.24–A.5.28</strong> e <strong>A.5.7</strong> di ISO/IEC 27001:2022, integrati dai controlli cloud di ISO/IEC 27017/27018, e dà attuazione agli <strong>artt. 23, 24 e 25 del D.Lgs. 138/2024</strong>. Le <strong>tempistiche esatte di notifica</strong> al CSIRT Italia sono: <strong>pre-allarme (early warning) 24 ore</strong>, <strong>notifica completa 72 ore</strong>, <strong>relazione finale 1 mese</strong>. I criteri di significatività seguono la <strong>Determinazione ACN n. 164179/2025</strong> e le Determinazioni vigenti.</p>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Scheda incidente nel modulo "Incidenti" (cronologia, severità, classificazione);</li>
|
||||
<li>Evidenze tecniche raccolte (log, immagini, comunicazioni), preservate ai sensi di A.5.28;</li>
|
||||
<li>Ricevute di notifica al CSIRT/ACN e relazione finale;</li>
|
||||
<li>Comunicazioni ai clienti e, se del caso, al DPO/Garante;</li>
|
||||
<li>Non conformità e azioni correttive collegate.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale, dopo ogni incidente significativo o a fronte di aggiornamenti delle Determinazioni ACN. Versionata secondo la Procedura di Controllo dei Documenti.</p>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!--META|doc_type=manuale_sgsi|title=Manuale del Sistema di Gestione per la Sicurezza delle Informazioni (SGSI)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Il presente Manuale descrive il Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) di <strong>Nuova Agile Technology srl</strong> (di seguito "l'Organizzazione" o "l'Azienda"), progettato e mantenuto in conformità alla norma <strong>ISO/IEC 27001:2022</strong>, integrata dalle linee guida <strong>ISO/IEC 27017:2015</strong> (controlli di sicurezza per i servizi cloud) e <strong>ISO/IEC 27018:2019</strong> (protezione dei dati personali nel cloud pubblico). Il Manuale costituisce il documento di vertice della struttura documentale del SGSI e ne illustra contesto, leadership, ambito, approccio al rischio e processo di miglioramento continuo.</p>
|
||||
|
||||
<h2>2. Ambito del SGSI</h2>
|
||||
<p>Il SGSI si applica a tutti i processi di progettazione, sviluppo, erogazione e supporto dei servizi e prodotti software dell'Azienda, alle informazioni trattate (proprie e dei clienti, inclusi i dati personali) e a tutto il personale (dipendenti e collaboratori). Le due linee di business coperte sono:</p>
|
||||
<ul>
|
||||
<li><strong>Prodotti software in licenza d'uso</strong> installati on-premise sui server <em>dei clienti</em>;</li>
|
||||
<li><strong>Servizi SaaS multi-tenant</strong> erogati in cloud.</li>
|
||||
</ul>
|
||||
<p>L'infrastruttura è <strong>interamente in cloud</strong> presso <strong>Aruba S.p.A.</strong> (data center in Italia) e <strong>Hetzner Online GmbH</strong> (Germania, UE), con utilizzo di piattaforme di intelligenza artificiale tramite API (es. Anthropic). <strong>L'Azienda non gestisce alcun server in sede</strong>: le postazioni di lavoro sono esclusivamente <strong>PC portatili cifrati</strong>. La sicurezza fisica dei data center è ereditata dai fornitori cloud secondo il modello di <em>responsabilità condivisa</em> (vedi A.5.23).</p>
|
||||
|
||||
<h2>3. Contesto dell'Organizzazione</h2>
|
||||
<p>Nuova Agile Technology srl è una software house italiana composta da circa 12 persone (9 dipendenti, 2 collaboratori esterni a P.IVA e la Presidente). I principali fattori di contesto interni ed esterni sono: dipendenza da fornitori cloud e da provider AI, requisiti contrattuali di sicurezza imposti dai clienti (supply chain), obblighi GDPR sui dati personali trattati nel SaaS e il quadro normativo NIS2. Le parti interessate rilevanti includono clienti, fornitori cloud/AI (sub-responsabili), personale, Autorità di controllo (Garante Privacy, ACN) e investitori.</p>
|
||||
|
||||
<h2>4. Ruoli e responsabilità (organigramma SGSI)</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità principali</th></tr>
|
||||
<tr><td>Alta Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Leadership, approvazione politiche, assegnazione risorse, riesame di direzione</td></tr>
|
||||
<tr><td>Responsabile SGSI (RSGSI)</td><td><strong>Massimo Tagliavini</strong></td><td>Gestione operativa del SGSI, risk management, audit interni, documentazione</td></tr>
|
||||
<tr><td>Responsabile IT e Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Sicurezza tecnica, gestione cloud, controllo accessi, gestione incidenti</td></tr>
|
||||
<tr><td>Referente Protezione Dati (DPO)</td><td><em>Consulente esterno (da nominare)</em></td><td>Conformità GDPR, pareri sul trattamento dei dati personali</td></tr>
|
||||
</table>
|
||||
<p>L'organigramma di dettaglio e la matrice RACI sono mantenuti nel registro dei ruoli del SGSI.</p>
|
||||
|
||||
<h2>5. Leadership e impegno della Direzione</h2>
|
||||
<p>L'Alta Direzione, nella persona della Presidente, garantisce l'impegno verso il SGSI mediante: definizione e approvazione della Politica per la Sicurezza delle Informazioni; integrazione del SGSI nei processi aziendali; messa a disposizione delle risorse; promozione del miglioramento continuo; comunicazione dell'importanza della sicurezza al personale. Tali impegni soddisfano i requisiti della clausola 5 di ISO/IEC 27001 e i controlli <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni) e <strong>A.5.4</strong> (Responsabilità della direzione).</p>
|
||||
|
||||
<h2>6. Struttura documentale del SGSI</h2>
|
||||
<p>La documentazione è organizzata su livelli gerarchici: (1) Manuale SGSI e Politica generale; (2) Politiche tematiche (controllo accessi, crittografia, classificazione, uso accettabile, ecc.); (3) Procedure operative; (4) Registrazioni ed evidenze. Tutti i documenti sono soggetti a controllo di versione, approvazione e revisione periodica (A.5.37).</p>
|
||||
|
||||
<h2>7. Approccio al rischio</h2>
|
||||
<p>Il SGSI adotta un processo di valutazione e trattamento del rischio basato su ISO/IEC 27005, che prevede identificazione degli asset informativi e dei relativi rischi, analisi di probabilità e impatto, definizione del rischio accettabile e selezione delle opzioni di trattamento (mitigazione, trasferimento, accettazione, eliminazione). Particolare attenzione è posta ai rischi della <strong>supply chain cloud/AI</strong> e ai rischi sui dati personali. Gli esiti sono documentati nel Registro dei Rischi e riferiti alla <strong>Dichiarazione di Applicabilità (SoA)</strong>.</p>
|
||||
|
||||
<h2>8. Dichiarazione di Applicabilità (SoA)</h2>
|
||||
<p>La SoA elenca i controlli dell'<strong>Annex A di ISO/IEC 27001:2022</strong> (93 controlli su 4 temi: organizzativi, persone, fisici, tecnologici), integrati dai controlli aggiuntivi di <strong>ISO/IEC 27017</strong> e <strong>27018</strong> per i servizi cloud e i dati personali. Per ciascun controllo sono indicati applicabilità, stato di implementazione e giustificazione delle esclusioni.</p>
|
||||
|
||||
<h2>9. CAVEAT sulla classificazione NIS2</h2>
|
||||
<p>L'Azienda si autoclassifica <strong>in via PRELIMINARE</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>". Tuttavia l'Azienda è <strong>SOTTO le soglie dimensionali ordinarie</strong> (≥50 addetti oppure fatturato/bilancio >10 M€): la qualifica deve essere <strong>CONFERMATA dalla Direzione e dal Legale</strong>, ed eventualmente da ACN, in funzione del fatturato e della categoria di attività (provider di servizi cloud). L'adozione del SGSI è guidata anche dai <strong>requisiti di supply chain dei clienti</strong>. Il quadro di riferimento è la Direttiva (UE) 2022/2555, recepita in Italia dal <strong>D.Lgs. 138/2024</strong>: art.23 (governance), art.24 (misure di gestione del rischio, equivalenti all'Art.21 della Direttiva), art.25 (obblighi di notifica). Le misure di dettaglio sono definite dalle Determinazioni ACN. <strong>Le norme ISO citate sono buone prassi, non obblighi di legge.</strong></p>
|
||||
|
||||
<h2>10. Miglioramento continuo</h2>
|
||||
<p>Il SGSI segue il ciclo PDCA. Sono previsti audit interni programmati, riesami di direzione almeno annuali, gestione delle non conformità e azioni correttive (NCR/CAPA), e monitoraggio di indicatori di prestazione. Gli esiti alimentano il piano di miglioramento.</p>
|
||||
|
||||
<h2>11. Registrazioni ed evidenze</h2>
|
||||
<p>Verbali di riesame di direzione, registro dei rischi, SoA, rapporti di audit, registro delle non conformità, log di sicurezza e registro degli incidenti.</p>
|
||||
|
||||
<h2>12. Riesame e versionamento</h2>
|
||||
<p>Documento approvato dalla Presidente. Revisione almeno annuale o a fronte di cambiamenti significativi. Versione 1.0.</p>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--META|doc_type=politica_sgsi|title=Politica per la Sicurezza delle Informazioni|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica esprime l'impegno formale dell'Alta Direzione di <strong>Nuova Agile Technology srl</strong> verso la protezione della riservatezza, integrità e disponibilità delle informazioni proprie e di quelle affidate dai clienti, inclusi i dati personali. Costituisce il documento di indirizzo del Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) conforme a <strong>ISO/IEC 27001:2022</strong> e alle linee guida <strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> per i servizi cloud e i dati personali.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutto il personale (dipendenti e collaboratori a P.IVA), a tutte le informazioni trattate, ai prodotti software in licenza d'uso installati presso i clienti e ai servizi SaaS multi-tenant erogati in cloud. L'infrastruttura è interamente cloud (<strong>Aruba</strong>, Italia; <strong>Hetzner</strong>, Germania-UE) con piattaforme AI via API; non esistono server gestiti in sede e le postazioni sono solo PC portatili cifrati.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022, 27017:2015, 27018:2019</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR)</li>
|
||||
<li>Direttiva (UE) 2022/2555 (NIS2) e D.Lgs. 138/2024</li>
|
||||
<li>Manuale SGSI, Dichiarazione di Applicabilità (SoA)</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>La <strong>Presidente Silvia Garretto</strong> (Alta Direzione) approva e sostiene la Politica. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> ne cura l'attuazione e il monitoraggio. Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> presidia le misure tecniche. Il <strong>Referente Protezione Dati (DPO, consulente esterno da nominare)</strong> supervisiona la conformità GDPR. Ogni membro del personale è responsabile del rispetto della Politica nell'ambito delle proprie attività.</p>
|
||||
|
||||
<h2>5. Principi e impegni</h2>
|
||||
<p>L'Alta Direzione si impegna a:</p>
|
||||
<ul>
|
||||
<li><strong>Riservatezza</strong>: garantire che le informazioni siano accessibili solo a chi è autorizzato, applicando il principio del minimo privilegio e la separazione dei tenant nel SaaS.</li>
|
||||
<li><strong>Integrità</strong>: assicurare l'accuratezza e la completezza delle informazioni e dei sistemi, con controlli su modifiche e sviluppo software sicuro.</li>
|
||||
<li><strong>Disponibilità</strong>: garantire l'accesso ai servizi secondo i livelli concordati, mediante backup, continuità operativa e gestione resiliente del cloud.</li>
|
||||
<li><strong>Conformità</strong>: rispettare gli obblighi legali e contrattuali, in particolare GDPR e i requisiti NIS2 ove applicabili.</li>
|
||||
<li><strong>Gestione del rischio</strong>: identificare, valutare e trattare i rischi in modo sistematico, con attenzione alla supply chain cloud e AI.</li>
|
||||
<li><strong>Consapevolezza</strong>: formare e sensibilizzare il personale sui temi di sicurezza.</li>
|
||||
<li><strong>Miglioramento continuo</strong>: riesaminare periodicamente il SGSI e adottare azioni correttive e preventive.</li>
|
||||
<li><strong>Gestione degli incidenti</strong>: rilevare, gestire e segnalare tempestivamente gli incidenti di sicurezza e le violazioni di dati personali.</li>
|
||||
</ul>
|
||||
<p>L'Azienda adotta una <strong>politica di tolleranza zero</strong> verso comportamenti che mettano deliberatamente a rischio la sicurezza delle informazioni.</p>
|
||||
|
||||
<h2>6. Obiettivi di sicurezza</h2>
|
||||
<p>Gli obiettivi misurabili del SGSI sono definiti annualmente dalla Direzione e includono, a titolo esemplificativo: copertura della formazione del personale, tempi di risposta agli incidenti, percentuale di rischi trattati entro i termini, livello di conformità dei fornitori cloud. Gli obiettivi sono monitorati e riesaminati nel riesame di direzione.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p>Controlli Annex A pertinenti: <strong>A.5.1</strong> (Politiche per la sicurezza delle informazioni), <strong>A.5.2</strong> (Ruoli e responsabilità), <strong>A.5.4</strong> (Responsabilità della direzione), <strong>A.5.31</strong> (Requisiti legali e contrattuali), <strong>A.5.36</strong> (Conformità alle politiche). Riferimenti NIS2 (D.Lgs. 138/2024): <strong>art.23</strong> (governance e ruolo degli organi di gestione), <strong>art.24</strong> (misure di gestione del rischio). Le ISO costituiscono buone prassi, non obblighi di legge.</p>
|
||||
|
||||
<h2>8. Caveat NIS2</h2>
|
||||
<p>L'Azienda si autoclassifica in via <strong>preliminare</strong> come soggetto NIS2 "<strong>IMPORTANTE</strong>", pur essendo <strong>sotto le soglie dimensionali ordinarie</strong>: la qualifica va <strong>confermata da Direzione e Legale</strong>, ed eventualmente da ACN, in base a fatturato e categoria di attività. <strong>[DA VERIFICARE]</strong> in sede di registrazione presso ACN.</p>
|
||||
|
||||
<h2>9. Comunicazione e applicazione</h2>
|
||||
<p>La Politica è comunicata a tutto il personale e accessibile in qualsiasi momento. La sua violazione può comportare provvedimenti disciplinari secondo le norme contrattuali e di legge vigenti.</p>
|
||||
|
||||
<h2>10. Registrazioni ed evidenze</h2>
|
||||
<p>Documento firmato dalla Presidente, evidenza di comunicazione al personale, verbali di riesame.</p>
|
||||
|
||||
<h2>11. Riesame e versionamento</h2>
|
||||
<p>Approvata dall'Alta Direzione. Riesame almeno annuale o a fronte di modifiche significative del contesto. Versione 1.0.</p>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!--META|doc_type=politica_controllo_accessi|title=Politica di Controllo degli Accessi|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le regole con cui <strong>Nuova Agile Technology srl</strong> autorizza, gestisce e revoca gli accessi logici alle informazioni, ai sistemi cloud (Aruba, Hetzner), alle piattaforme AI, agli ambienti di sviluppo e ai servizi SaaS multi-tenant, al fine di garantire che solo gli utenti autorizzati accedano alle risorse strettamente necessarie al proprio ruolo.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutti gli accessi logici di dipendenti, collaboratori a P.IVA e — ove pertinente — di clienti e fornitori. Riguarda: account dei provider cloud, repository di codice, ambienti di produzione e staging, console di amministrazione SaaS, chiavi API (incluse quelle dei provider AI), database e strumenti di collaborazione. Non sono presenti accessi fisici a server in sede, in quanto l'infrastruttura è interamente cloud; la sicurezza fisica dei data center è in capo ai fornitori (responsabilità condivisa).</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 Annex A (controlli A.5.15–A.5.18, A.8.2–A.8.5)</li>
|
||||
<li>ISO/IEC 27017:2015 (gestione accessi in ambienti cloud), 27018:2019 (accesso ai dati personali)</li>
|
||||
<li>Politica per la Sicurezza delle Informazioni; Politica di Crittografia</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> gestisce operativamente account, privilegi e credenziali. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> approva le matrici di accesso e supervisiona i riesami periodici. L'<strong>Alta Direzione (Silvia Garretto)</strong> autorizza i profili di accesso privilegiato. Il <strong>DPO (consulente esterno)</strong> è consultato per gli accessi che coinvolgono dati personali dei clienti.</p>
|
||||
|
||||
<h2>5. Regole di controllo degli accessi</h2>
|
||||
<h3>5.1 Principi fondamentali</h3>
|
||||
<ul>
|
||||
<li><strong>Minimo privilegio</strong>: a ogni utente sono concessi solo i diritti necessari al ruolo.</li>
|
||||
<li><strong>Need-to-know</strong>: l'accesso alle informazioni è limitato a chi ne ha effettiva necessità.</li>
|
||||
<li><strong>Segregazione dei compiti</strong>: ove possibile, separazione tra chi sviluppa, chi rilascia in produzione e chi amministra.</li>
|
||||
<li><strong>Default deny</strong>: in assenza di autorizzazione esplicita, l'accesso è negato.</li>
|
||||
</ul>
|
||||
<h3>5.2 Gestione del ciclo di vita degli account</h3>
|
||||
<ul>
|
||||
<li><strong>Provisioning</strong>: gli account sono creati su richiesta formale e con approvazione del responsabile, sulla base del ruolo (RBAC).</li>
|
||||
<li><strong>Modifica</strong>: i cambi di ruolo comportano la revisione tempestiva dei privilegi.</li>
|
||||
<li><strong>Deprovisioning</strong>: alla cessazione del rapporto o del contratto, tutti gli accessi sono revocati entro 24 ore, incluse chiavi API e accessi ai provider cloud/AI.</li>
|
||||
</ul>
|
||||
<h3>5.3 Autenticazione</h3>
|
||||
<ul>
|
||||
<li><strong>Autenticazione a più fattori (MFA)</strong> obbligatoria per: console cloud Aruba/Hetzner, repository di codice, accessi amministrativi al SaaS, account di posta e SSO.</li>
|
||||
<li>Password robuste secondo standard aziendale; uso obbligatorio di un <strong>password manager</strong> approvato.</li>
|
||||
<li>Divieto di condivisione delle credenziali; ogni account è nominativo.</li>
|
||||
</ul>
|
||||
<h3>5.4 Accessi privilegiati e chiavi API</h3>
|
||||
<ul>
|
||||
<li>Gli account amministrativi sono ridotti al minimo, nominativi e monitorati.</li>
|
||||
<li>Le <strong>chiavi API</strong> (cloud, AI, integrazioni) sono custodite in un vault cifrato, mai inserite in chiaro nel codice o committate nei repository, e ruotate periodicamente o in caso di sospetta compromissione.</li>
|
||||
<li>Gli accessi privilegiati sono soggetti a logging e revisione.</li>
|
||||
</ul>
|
||||
<h3>5.5 Accesso nel SaaS multi-tenant</h3>
|
||||
<ul>
|
||||
<li>Isolamento logico dei tenant: ogni cliente accede esclusivamente ai propri dati.</li>
|
||||
<li>I dati personali dei clienti sono accessibili al personale dell'Azienda solo per finalità di erogazione e supporto, con tracciabilità (rif. ISO 27018).</li>
|
||||
</ul>
|
||||
<h3>5.6 Riesame periodico</h3>
|
||||
<p>Gli accessi e i privilegi sono <strong>riesaminati almeno ogni 6 mesi</strong> dal Responsabile IT, con verifica e rimozione degli account obsoleti o non più necessari. Gli esiti sono registrati.</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p><strong>A.5.15</strong> (Controllo degli accessi), <strong>A.5.16</strong> (Gestione delle identità), <strong>A.5.17</strong> (Informazioni di autenticazione), <strong>A.5.18</strong> (Diritti di accesso), <strong>A.8.2</strong> (Diritti di accesso privilegiati), <strong>A.8.3</strong> (Restrizione dell'accesso alle informazioni), <strong>A.8.5</strong> (Autenticazione sicura). Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, tra cui controllo degli accessi e uso dell'autenticazione a più fattori).</p>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<p>Matrice dei ruoli e dei privilegi (RBAC), registro delle richieste di accesso, verbali dei riesami semestrali, log degli accessi privilegiati, inventario delle chiavi API.</p>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Approvata dal Responsabile SGSI e dall'Alta Direzione. Revisione almeno annuale. Versione 1.0.</p>
|
||||
@@ -0,0 +1,55 @@
|
||||
<!--META|doc_type=politica_crittografia|title=Politica di Crittografia e Gestione delle Chiavi|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire i criteri con cui <strong>Nuova Agile Technology srl</strong> impiega la crittografia per proteggere la riservatezza e l'integrità delle informazioni — in particolare i dati personali dei clienti trattati nel SaaS — e per gestire in modo sicuro le chiavi crittografiche e i segreti applicativi durante l'intero ciclo di vita.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica ai dati a riposo nei servizi cloud (Aruba, Hetzner), ai dati in transito tra utenti, servizi e provider AI, ai PC portatili del personale, ai backup, ai segreti applicativi (chiavi API cloud e AI, credenziali di database, token) e ai certificati TLS dei servizi esposti. Riguarda sia i servizi SaaS sia i prodotti in licenza installati presso i clienti, per la parte di configurazione crittografica raccomandata.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 Annex A (A.8.24, A.8.5, A.5.33)</li>
|
||||
<li>ISO/IEC 27017:2015 e 27018:2019 (crittografia dei dati personali nel cloud)</li>
|
||||
<li>GDPR art. 32 (misure tecniche, tra cui cifratura); Politica di Controllo degli Accessi</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> definisce e gestisce gli algoritmi, le chiavi e il vault dei segreti. Il <strong>Responsabile SGSI Massimo Tagliavini</strong> verifica la conformità della Politica. Il <strong>DPO (consulente esterno)</strong> è consultato per la cifratura dei dati personali. L'<strong>Alta Direzione (Silvia Garretto)</strong> approva la Politica.</p>
|
||||
|
||||
<h2>5. Regole di crittografia</h2>
|
||||
<h3>5.1 Dati in transito</h3>
|
||||
<ul>
|
||||
<li>Tutte le comunicazioni esterne avvengono tramite <strong>TLS 1.2 o superiore</strong> (preferibilmente TLS 1.3); i protocolli e le cifrature obsolete sono disabilitati.</li>
|
||||
<li>Le connessioni tra applicazione e database e tra microservizi adottano canali cifrati ove tecnicamente possibile.</li>
|
||||
<li>Le chiamate alle API dei provider AI e cloud avvengono esclusivamente su canali cifrati.</li>
|
||||
</ul>
|
||||
<h3>5.2 Dati a riposo</h3>
|
||||
<ul>
|
||||
<li>I dati a riposo nei servizi cloud sono cifrati; i database e i volumi che contengono dati personali utilizzano cifratura (es. AES-256) secondo le funzionalità del provider.</li>
|
||||
<li>I <strong>PC portatili</strong> del personale hanno la <strong>cifratura completa del disco</strong> attiva (es. BitLocker/FileVault/LUKS).</li>
|
||||
<li>I <strong>backup</strong> sono cifrati e conservati in modo sicuro.</li>
|
||||
</ul>
|
||||
<h3>5.3 Algoritmi ammessi</h3>
|
||||
<ul>
|
||||
<li>Cifratura simmetrica: <strong>AES-256</strong> (modalità autenticate come GCM).</li>
|
||||
<li>Hashing password: funzioni dedicate e resistenti (es. <strong>bcrypt/Argon2</strong>); vietato l'uso di MD5/SHA-1 per scopi di sicurezza.</li>
|
||||
<li>Firma/integrità: <strong>HMAC-SHA-256</strong> o equivalente; certificati basati su algoritmi robusti.</li>
|
||||
</ul>
|
||||
<h3>5.4 Gestione delle chiavi e dei segreti</h3>
|
||||
<ul>
|
||||
<li>I segreti (chiavi API, credenziali, token) sono custoditi in un <strong>vault cifrato centralizzato</strong>, mai in chiaro nel codice sorgente, nei file di configurazione versionati o nei log.</li>
|
||||
<li>Le chiavi sono soggette a <strong>rotazione periodica</strong> e a rotazione immediata in caso di sospetta compromissione (chiavi, leak, cessazione personale con accesso).</li>
|
||||
<li>L'accesso alle chiavi segue il principio del minimo privilegio ed è registrato.</li>
|
||||
<li>I certificati TLS sono monitorati per la scadenza e rinnovati tempestivamente.</li>
|
||||
</ul>
|
||||
<h3>5.5 Provider AI e dati</h3>
|
||||
<p>Le informazioni inviate alle piattaforme AI sono trasmesse su canali cifrati e, ove possibile, <strong>minimizzate/anonimizzate</strong> per evitare l'esposizione di dati personali o riservati non necessari all'elaborazione.</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p><strong>A.8.24</strong> (Uso della crittografia), <strong>A.8.5</strong> (Autenticazione sicura), <strong>A.5.33</strong> (Protezione delle registrazioni). Riferimenti cloud: ISO/IEC 27017/27018. Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, incluse politiche e procedure relative all'uso della crittografia). Le ISO sono buone prassi, non obblighi di legge.</p>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<p>Inventario delle chiavi e dei certificati, log di rotazione, configurazioni TLS, evidenza della cifratura disco sui PC portatili, registro degli accessi al vault.</p>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Approvata dal Responsabile SGSI. Revisione almeno annuale o all'emergere di nuove vulnerabilità crittografiche. Versione 1.0.</p>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!--META|doc_type=politica_classificazione|title=Politica di Classificazione e Trattamento delle Informazioni|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Stabilire come <strong>Nuova Agile Technology srl</strong> classifica le informazioni in base alla loro sensibilità e criticità e quali misure di trattamento, protezione e conservazione applicare a ciascun livello, lungo l'intero ciclo di vita (creazione, uso, condivisione, archiviazione, cancellazione).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutte le informazioni gestite dall'Azienda, indipendentemente dal formato e dal supporto: codice sorgente, documentazione di progetto, dati dei servizi SaaS (inclusi i <strong>dati personali dei clienti</strong>), credenziali e segreti, documenti amministrativi e contrattuali, comunicazioni interne. Riguarda i dati nel cloud (Aruba, Hetzner), sui PC portatili e presso le piattaforme AI.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 Annex A (A.5.12, A.5.13, A.5.14, A.5.10, A.8.10, A.8.12)</li>
|
||||
<li>ISO/IEC 27018:2019 (protezione dei dati personali nel cloud)</li>
|
||||
<li>GDPR; Politica di Crittografia; Politica di Controllo degli Accessi</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>Il <strong>Responsabile SGSI Massimo Tagliavini</strong> mantiene lo schema di classificazione e ne supervisiona l'applicazione. Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> implementa le misure tecniche per livello. Il <strong>DPO (consulente esterno)</strong> presidia il corretto trattamento dei dati personali. Ogni dipendente/collaboratore, in qualità di <em>proprietario o utilizzatore</em> dell'informazione, è responsabile della sua corretta classificazione e gestione.</p>
|
||||
|
||||
<h2>5. Schema di classificazione</h2>
|
||||
<table>
|
||||
<tr><th>Livello</th><th>Descrizione</th><th>Esempi</th></tr>
|
||||
<tr><td><strong>Pubblico</strong></td><td>Informazioni destinate alla diffusione, senza impatto se divulgate.</td><td>Materiale marketing, documentazione pubblica del prodotto</td></tr>
|
||||
<tr><td><strong>Interno</strong></td><td>Uso interno; divulgazione non autorizzata con impatto limitato.</td><td>Procedure interne, comunicazioni di team</td></tr>
|
||||
<tr><td><strong>Riservato</strong></td><td>Informazioni sensibili aziendali o di terzi; impatto significativo se divulgate.</td><td>Codice sorgente proprietario, contratti, dati clienti non personali</td></tr>
|
||||
<tr><td><strong>Strettamente riservato</strong></td><td>Massima sensibilità; impatto grave (legale, reputazionale, sanzionatorio).</td><td>Dati personali dei clienti nel SaaS, credenziali, chiavi API, segreti</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>6. Regole di trattamento per livello</h2>
|
||||
<h3>6.1 Etichettatura</h3>
|
||||
<p>I documenti e i repository riportano, ove pertinente, il livello di classificazione. I dati personali sono trattati come <strong>Strettamente riservati</strong> per impostazione predefinita.</p>
|
||||
<h3>6.2 Misure minime</h3>
|
||||
<ul>
|
||||
<li><strong>Pubblico</strong>: nessuna restrizione di accesso particolare; verifica di accuratezza prima della pubblicazione.</li>
|
||||
<li><strong>Interno</strong>: accesso limitato al personale; non condividere all'esterno senza autorizzazione.</li>
|
||||
<li><strong>Riservato</strong>: accesso su base need-to-know; trasmissione cifrata; conservazione nei sistemi aziendali autorizzati; divieto di copia su supporti non controllati.</li>
|
||||
<li><strong>Strettamente riservato</strong>: cifratura obbligatoria a riposo e in transito; accesso con MFA e minimo privilegio; tracciabilità degli accessi; divieto di trasmissione a piattaforme AI senza minimizzazione/anonimizzazione; nessuna conservazione su supporti personali.</li>
|
||||
</ul>
|
||||
<h3>6.3 Dati personali nel SaaS (ISO 27018 / GDPR)</h3>
|
||||
<ul>
|
||||
<li>Trattamento secondo le istruzioni documentate dei clienti (in qualità di responsabile del trattamento), con isolamento per tenant.</li>
|
||||
<li>Applicazione dei principi di minimizzazione e limitazione della conservazione.</li>
|
||||
<li>Supporto ai diritti degli interessati e gestione delle richieste di cancellazione.</li>
|
||||
</ul>
|
||||
<h3>6.4 Conservazione e cancellazione sicura</h3>
|
||||
<ul>
|
||||
<li>I periodi di conservazione sono definiti per categoria e in base a obblighi legali e contrattuali.</li>
|
||||
<li>La cancellazione avviene in modo sicuro (cancellazione logica irreversibile dei dati nei sistemi cloud, distruzione delle copie); per i PC portatili la dismissione prevede wipe sicuro o riformattazione con disco già cifrato.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p><strong>A.5.12</strong> (Classificazione delle informazioni), <strong>A.5.13</strong> (Etichettatura delle informazioni), <strong>A.5.14</strong> (Trasferimento delle informazioni), <strong>A.5.10</strong> (Uso accettabile delle informazioni e degli asset), <strong>A.8.10</strong> (Cancellazione delle informazioni), <strong>A.8.12</strong> (Prevenzione della fuga di dati). Cloud: ISO/IEC 27018. Riferimento NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, incluse politiche di sicurezza delle informazioni).</p>
|
||||
|
||||
<h2>8. Registrazioni ed evidenze</h2>
|
||||
<p>Schema di classificazione, inventario degli asset informativi con livello, registro dei trattamenti (GDPR), evidenze di cancellazione sicura.</p>
|
||||
|
||||
<h2>9. Riesame e versionamento</h2>
|
||||
<p>Approvata dal Responsabile SGSI. Revisione almeno annuale. Versione 1.0.</p>
|
||||
@@ -0,0 +1,55 @@
|
||||
<!--META|doc_type=politica_uso_accettabile|title=Politica d'Uso Accettabile e Sicurezza degli Endpoint (PC portatili)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le regole di comportamento per l'uso accettabile degli strumenti informatici, dei servizi e delle informazioni di <strong>Nuova Agile Technology srl</strong>, con particolare attenzione alla sicurezza dei <strong>PC portatili</strong>, unico tipo di postazione di lavoro dell'Azienda, in un contesto interamente cloud e con lavoro anche da remoto.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutti i dipendenti e collaboratori a P.IVA che utilizzano dispositivi, account e servizi aziendali (cloud Aruba/Hetzner, repository di codice, posta, strumenti di collaborazione, piattaforme AI). Non essendoci server in sede, la Politica si concentra sugli <strong>endpoint mobili</strong> e sull'uso responsabile dei servizi cloud.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 Annex A (A.5.10, A.6.7, A.8.1, A.8.7, A.8.9, A.7.9)</li>
|
||||
<li>Politica per la Sicurezza delle Informazioni; Politica di Controllo degli Accessi; Politica di Classificazione</li>
|
||||
<li>GDPR; Direttiva NIS2 / D.Lgs. 138/2024</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<p>Il <strong>Responsabile IT e Sicurezza Simon Fattori</strong> configura e mantiene gli endpoint sicuri (cifratura, aggiornamenti, antimalware). Il <strong>Responsabile SGSI Massimo Tagliavini</strong> vigila sul rispetto della Politica. L'<strong>Alta Direzione (Silvia Garretto)</strong> la approva. Ogni utente è personalmente responsabile dell'uso corretto degli strumenti assegnati.</p>
|
||||
|
||||
<h2>5. Regole d'uso accettabile</h2>
|
||||
<h3>5.1 Comportamenti consentiti e vietati</h3>
|
||||
<ul>
|
||||
<li>Gli strumenti aziendali sono destinati prevalentemente a finalità professionali; un uso personale limitato e ragionevole è tollerato se non compromette sicurezza e produttività.</li>
|
||||
<li>È <strong>vietato</strong>: installare software non autorizzato o da fonti non attendibili; disattivare le misure di sicurezza (cifratura, antimalware, aggiornamenti); condividere credenziali; aggirare i controlli di accesso; trattare dati aziendali su servizi personali non approvati.</li>
|
||||
<li>È vietato inviare a piattaforme AI informazioni <strong>riservate o personali</strong> senza minimizzazione/anonimizzazione e senza approvazione, in coerenza con la Politica di Classificazione.</li>
|
||||
</ul>
|
||||
<h3>5.2 Sicurezza dei PC portatili (endpoint)</h3>
|
||||
<ul>
|
||||
<li><strong>Cifratura completa del disco</strong> attiva su tutti i portatili (BitLocker/FileVault/LUKS).</li>
|
||||
<li><strong>Blocco automatico</strong> dello schermo dopo inattività e password/PIN robusti all'avvio.</li>
|
||||
<li><strong>Aggiornamenti</strong> di sistema operativo e applicazioni installati tempestivamente; <strong>antimalware</strong> attivo e aggiornato.</li>
|
||||
<li><strong>Firewall</strong> locale attivo; servizi non necessari disabilitati.</li>
|
||||
<li>I dati di lavoro risiedono nei servizi cloud aziendali; si evita l'accumulo di dati riservati in locale e l'uso di supporti rimovibili non cifrati.</li>
|
||||
<li>Smarrimento o furto del dispositivo va segnalato <strong>immediatamente</strong> al Responsabile IT per la revoca degli accessi e, se possibile, il blocco/wipe remoto.</li>
|
||||
</ul>
|
||||
<h3>5.3 Lavoro da remoto</h3>
|
||||
<ul>
|
||||
<li>Connessioni a reti affidabili; uso di reti pubbliche solo con cautela e canali cifrati (TLS/VPN ove previsto).</li>
|
||||
<li>Protezione dalla visione altrui dello schermo in luoghi pubblici; dispositivi mai lasciati incustoditi.</li>
|
||||
</ul>
|
||||
<h3>5.4 Posta e phishing</h3>
|
||||
<ul>
|
||||
<li>Prudenza con allegati e link sospetti; segnalazione tempestiva dei tentativi di phishing o social engineering al Responsabile IT.</li>
|
||||
<li>Verifica dell'identità del mittente prima di azioni sensibili (es. cambi di pagamento, invio credenziali).</li>
|
||||
</ul>
|
||||
<h3>5.5 Segnalazione degli eventi di sicurezza</h3>
|
||||
<p>Ogni anomalia, sospetta compromissione o incidente va segnalato senza ritardo al Responsabile IT/SGSI, per consentire la valutazione e, se necessario, la notifica secondo gli obblighi NIS2 (pre-allarme entro 24 ore, notifica entro 72 ore, relazione finale entro 1 mese al CSIRT Italia/ACN, ove l'Azienda risulti soggetto obbligato).</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<p><strong>A.5.10</strong> (Uso accettabile delle informazioni e degli asset), <strong>A.6.7</strong> (Lavoro da remoto), <strong>A.8.1</strong> (Dispositivi endpoint degli utenti), <strong>A.8.7</strong> (Protezione contro il malware), <strong>A.8.9</strong> (Gestione della configurazione), <strong>A.7.9</strong> (Sicurezza degli asset fuori sede). Riferimenti NIS2: D.Lgs. 138/2024 <strong>art.24</strong> (misure di gestione del rischio, tra cui igiene informatica di base e formazione) e <strong>art.25</strong> (obblighi di notifica degli incidenti). Le ISO sono buone prassi, non obblighi di legge.</p>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<p>Accettazione della Politica da parte del personale, inventario degli endpoint con stato di cifratura e aggiornamento, registro delle segnalazioni di eventi/incidenti.</p>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Approvata dall'Alta Direzione. Revisione almeno annuale o a fronte di nuove minacce rilevanti. Versione 1.0.</p>
|
||||
@@ -0,0 +1,72 @@
|
||||
<!--META|doc_type=politica_fornitori|title=Politica di Sicurezza dei Fornitori|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> seleziona, qualifica, contrattualizza e sorveglia i propri fornitori, con particolare attenzione a quelli che erogano servizi rilevanti per la sicurezza delle informazioni (cloud, AI, sviluppo, manutenzione). Lo scopo è garantire che il livello di sicurezza richiesto dal Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) sia mantenuto anche quando attività o dati sono affidati a terze parti, e che gli obblighi di sicurezza della catena di fornitura previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (recepimento NIS2) siano rispettati e propagati lungo la supply chain.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutti i rapporti con fornitori, sub-fornitori e prestatori di servizi che: (a) trattano, ospitano o accedono a informazioni dell'azienda o dei suoi clienti; (b) erogano infrastruttura cloud (Aruba S.p.A. – Italia; Hetzner – Germania, UE); (c) forniscono servizi di intelligenza artificiale tramite API (es. modelli LLM Anthropic); (d) contribuiscono allo sviluppo, alla manutenzione o all'assistenza dei prodotti software, sia in licenza d'uso installati presso i clienti, sia SaaS multi-tenant. Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) coinvolto nella gestione dei fornitori.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – Annex A, controlli A.5.19, A.5.20, A.5.21, A.5.22, A.5.23.</li>
|
||||
<li>ISO/IEC 27017:2015 – controlli cloud-specifici e modello di responsabilità condivisa.</li>
|
||||
<li>ISO/IEC 27018:2019 – tutela dei dati personali (PII) trattati nel cloud.</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio, sicurezza della supply chain).</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR), art. 28 (responsabili del trattamento).</li>
|
||||
<li>Procedura di Gestione dei Fornitori e dei Servizi Cloud (doc. 12).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica, autorizza l'ingaggio di fornitori critici e ne accetta i rischi residui.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, gestisce il registro fornitori, coordina le valutazioni di sicurezza e i riesami periodici.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Valuta tecnicamente i fornitori, verifica certificazioni e misure, monitora SLA e incidenti.</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Valida gli aspetti privacy, i DPA e i trasferimenti di dati personali.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Corpo – Regole e passi concreti</h2>
|
||||
<h3>5.1 Classificazione dei fornitori</h3>
|
||||
<p>Ogni fornitore è classificato per criticità in funzione del dato e del servizio: <strong>critico</strong> (cloud, AI, accesso a PII di clienti), <strong>rilevante</strong> (sviluppo/manutenzione), <strong>ordinario</strong> (servizi non legati alle informazioni). La classificazione determina la profondità della due diligence.</p>
|
||||
<h3>5.2 Qualifica e due diligence</h3>
|
||||
<ul>
|
||||
<li>Per i fornitori critici e rilevanti si raccolgono evidenze di sicurezza: certificazioni (es. ISO 27001/27017/27018, SOC 2), sede dei data center, misure tecniche e organizzative.</li>
|
||||
<li>Si verifica la collocazione dei dati nell'UE/SEE o la presenza di garanzie adeguate per trasferimenti extra-UE.</li>
|
||||
<li>Si privilegiano fornitori con certificazioni riconosciute e infrastruttura nell'UE (Aruba in Italia, Hetzner in Germania).</li>
|
||||
</ul>
|
||||
<h3>5.3 Clausole contrattuali e DPA</h3>
|
||||
<ul>
|
||||
<li>I contratti con fornitori critici/rilevanti includono clausole di sicurezza: riservatezza, obblighi di notifica incidenti, diritto di audit/evidenza, livelli di servizio (SLA), gestione del fine rapporto e restituzione/cancellazione dei dati.</li>
|
||||
<li>Quando il fornitore tratta dati personali, si stipula un <strong>DPA ex art. 28 GDPR</strong> e si verificano sub-responsabili e clausole 27018.</li>
|
||||
<li>Si richiede al fornitore di notificare tempestivamente gli incidenti di sicurezza, coerentemente con i tempi di notifica NIS2 (pre-allarme 24h, notifica 72h, relazione finale 1 mese) qualora l'azienda sia a sua volta soggetto obbligato.</li>
|
||||
</ul>
|
||||
<h3>5.4 Propagazione lungo la supply chain</h3>
|
||||
<p>Essendo l'azienda <strong>fornitore di clienti soggetti a NIS2</strong>, i requisiti di sicurezza ricevuti dai clienti vengono recepiti e ribaltati sui propri sub-fornitori, in modo che le misure di sicurezza scendano coerentemente lungo l'intera catena (Art. 24 D.Lgs. 138/2024).</p>
|
||||
<h3>5.5 Sorveglianza continua</h3>
|
||||
<ul>
|
||||
<li>Riesame periodico (almeno annuale) dei fornitori critici: rinnovo evidenze, verifica SLA, eventuali incidenti.</li>
|
||||
<li>Monitoraggio di comunicazioni di sicurezza dei fornitori cloud/AI e valutazione dell'impatto.</li>
|
||||
<li>In caso di non conformità grave o incidente, attivazione delle azioni correttive e, se necessario, sostituzione del fornitore.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>A.5.19</strong> – Sicurezza delle informazioni nei rapporti con i fornitori.</li>
|
||||
<li><strong>A.5.20</strong> – Gestione della sicurezza negli accordi con i fornitori.</li>
|
||||
<li><strong>A.5.21</strong> – Gestione della sicurezza nella catena di fornitura ICT.</li>
|
||||
<li><strong>A.5.22</strong> – Monitoraggio, riesame e gestione dei cambiamenti dei servizi dei fornitori.</li>
|
||||
<li><strong>A.5.23</strong> – Sicurezza delle informazioni nell'uso di servizi cloud.</li>
|
||||
<li>ISO/IEC 27017 e 27018 per i fornitori cloud e il trattamento dei PII.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: misure di gestione del rischio, sicurezza della supply chain.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Registro dei fornitori con classificazione di criticità e stato di qualifica.</li>
|
||||
<li>Schede di valutazione di sicurezza e questionari compilati.</li>
|
||||
<li>Contratti, DPA e clausole di sicurezza archiviati.</li>
|
||||
<li>Verbali dei riesami periodici e registro degli incidenti che coinvolgono fornitori.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La Politica è riesaminata almeno una volta all'anno o a fronte di cambiamenti significativi (nuovi fornitori critici, incidenti, evoluzioni normative). Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI con numero di versione, data e responsabile dell'aggiornamento (RSGSI).</p>
|
||||
@@ -0,0 +1,85 @@
|
||||
<!--META|doc_type=politica_sviluppo_sicuro|title=Politica di Sviluppo Software Sicuro (Secure SDLC)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica stabilisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> integra la sicurezza in tutte le fasi del ciclo di vita dello sviluppo software (Secure Software Development Life Cycle). L'obiettivo è garantire che i prodotti realizzati — sia quelli in <strong>licenza d'uso installati sui server dei clienti</strong>, sia il <strong>SaaS multi-tenant in cloud</strong> — siano progettati, sviluppati, testati e mantenuti in modo da ridurre vulnerabilità, proteggere i dati personali trattati e soddisfare i requisiti di sicurezza nell'acquisizione, sviluppo e manutenzione previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutte le attività di analisi, progettazione, codifica, test, rilascio e manutenzione dei software dell'azienda, a tutto il personale tecnico (9 dipendenti e 2 collaboratori esterni a P.IVA) e a eventuali fornitori che contribuiscono allo sviluppo. Copre il codice sorgente, le dipendenze di terze parti, le pipeline di build/deploy, gli ambienti di sviluppo e test, e le componenti di intelligenza artificiale integrate tramite API esterne (es. LLM Anthropic). L'attività si svolge esclusivamente da <strong>PC portatili cifrati</strong>, senza alcun server in sede.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – Annex A, controlli A.8.25, A.8.26, A.8.27, A.8.28, A.8.29, A.8.30, A.8.31, A.8.32, A.8.33.</li>
|
||||
<li>ISO/IEC 27017:2015 – sicurezza nello sviluppo e gestione di servizi cloud.</li>
|
||||
<li>ISO/IEC 27018:2019 – protezione dei PII nei servizi cloud (privacy by design).</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (sicurezza nell'acquisizione, sviluppo e manutenzione di sistemi).</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR), artt. 25 e 32 (privacy by design e by default, sicurezza del trattamento).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e assegna le risorse per la sicurezza dello sviluppo.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica e ne verifica l'applicazione nei progetti.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Definisce gli standard tecnici, gestisce code review, analisi delle vulnerabilità e gestione degli accessi al codice.</td></tr>
|
||||
<tr><td>Sviluppatori (dipendenti e collaboratori)</td><td>Applicano le regole di codifica sicura, eseguono test e gestiscono le segnalazioni di vulnerabilità.</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Valida i requisiti di privacy by design quando si trattano dati personali.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Corpo – Regole e passi concreti</h2>
|
||||
<h3>5.1 Requisiti e progettazione sicura</h3>
|
||||
<ul>
|
||||
<li>I requisiti di sicurezza e privacy sono definiti già in fase di analisi, considerando il modello di minaccia, la separazione tra tenant nel SaaS e l'isolamento delle istanze on-premise.</li>
|
||||
<li>Si applicano i principi di <strong>privacy by design e by default</strong> (GDPR artt. 25 e 32) e di minimizzazione dei dati.</li>
|
||||
</ul>
|
||||
<h3>5.2 Codifica sicura</h3>
|
||||
<ul>
|
||||
<li>Vengono seguite regole di secure coding per prevenire le vulnerabilità più comuni (injection, autenticazione/sessione, controllo accessi, gestione errori, esposizione di dati).</li>
|
||||
<li>I segreti (credenziali, chiavi API, incluse quelle dei servizi AI) non sono mai inseriti nel codice né nei repository: si usano meccanismi di gestione segreti dedicati.</li>
|
||||
<li>Gli ambienti di sviluppo risiedono su PC portatili cifrati con accesso autenticato.</li>
|
||||
</ul>
|
||||
<h3>5.3 Gestione delle dipendenze di terze parti</h3>
|
||||
<ul>
|
||||
<li>Le librerie e i componenti open source sono censiti e tenuti aggiornati; si monitorano gli avvisi di vulnerabilità note e si applicano le patch in tempi proporzionati al rischio.</li>
|
||||
</ul>
|
||||
<h3>5.4 Test di sicurezza</h3>
|
||||
<ul>
|
||||
<li>Sono previsti test funzionali e di sicurezza prima del rilascio; per le componenti più esposte si eseguono verifiche statiche/dinamiche e, ove opportuno, test di penetrazione.</li>
|
||||
<li>Gli ambienti di test usano dati anonimizzati o sintetici: i dati personali reali dei clienti non vengono utilizzati in test.</li>
|
||||
</ul>
|
||||
<h3>5.5 Separazione degli ambienti e gestione dei cambiamenti</h3>
|
||||
<ul>
|
||||
<li>Ambienti di sviluppo, test e produzione sono separati; i rilasci seguono un processo controllato di change management con tracciabilità delle modifiche.</li>
|
||||
<li>Il codice è versionato; le modifiche sono sottoposte a revisione tra pari (code review) prima dell'integrazione.</li>
|
||||
</ul>
|
||||
<h3>5.6 Rilascio, manutenzione e gestione vulnerabilità</h3>
|
||||
<ul>
|
||||
<li>Per i prodotti in licenza installati presso i clienti si forniscono aggiornamenti di sicurezza e indicazioni di installazione sicura; le responsabilità di patching condivise con il cliente sono documentate.</li>
|
||||
<li>Per il SaaS, l'azienda gestisce direttamente il patching dell'applicazione.</li>
|
||||
<li>È attivo un canale per la ricezione e gestione delle segnalazioni di vulnerabilità; le correzioni sono prioritizzate in base alla gravità.</li>
|
||||
</ul>
|
||||
<h3>5.7 Componenti di intelligenza artificiale</h3>
|
||||
<p>L'uso di API LLM esterne è valutato per i rischi su confidenzialità dei dati inviati e affidabilità degli output; i dati personali e riservati inviati ai servizi AI sono minimizzati e protetti coerentemente con i contratti e i DPA dei fornitori.</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>A.8.25</strong> – Ciclo di vita di sviluppo sicuro.</li>
|
||||
<li><strong>A.8.26</strong> – Requisiti di sicurezza delle applicazioni.</li>
|
||||
<li><strong>A.8.27 / A.8.28</strong> – Principi di architettura sicura e codifica sicura.</li>
|
||||
<li><strong>A.8.29</strong> – Test di sicurezza in sviluppo e accettazione.</li>
|
||||
<li><strong>A.8.30</strong> – Sviluppo affidato all'esterno.</li>
|
||||
<li><strong>A.8.31</strong> – Separazione ambienti di sviluppo, test e produzione.</li>
|
||||
<li><strong>A.8.32 / A.8.33</strong> – Gestione dei cambiamenti e dati di test.</li>
|
||||
<li>Controlli ISO/IEC 27017 e 27018 per gli aspetti cloud e PII.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: sicurezza nell'acquisizione, sviluppo e manutenzione dei sistemi.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Requisiti di sicurezza documentati per progetto/prodotto.</li>
|
||||
<li>Registri delle code review e dei test di sicurezza.</li>
|
||||
<li>Inventario delle dipendenze e tracciamento delle vulnerabilità/patch.</li>
|
||||
<li>Log dei rilasci e delle modifiche (change management).</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La Politica è riesaminata almeno annualmente o in caso di cambiamenti tecnologici significativi, nuove minacce o requisiti contrattuali dei clienti. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,81 @@
|
||||
<!--META|doc_type=politica_cloud|title=Politica di Sicurezza del Cloud (ISO/IEC 27017)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> utilizza in sicurezza i servizi cloud su cui poggia interamente la propria operatività. Poiché l'infrastruttura aziendale è <strong>integralmente in cloud</strong> (Aruba S.p.A. – Italia; Hetzner – Germania, UE) e in sede non esiste alcun server, la corretta configurazione e gestione dei servizi cloud è essenziale per la sicurezza delle informazioni dell'azienda e dei clienti. La Politica recepisce i controlli cloud-specifici della <strong>ISO/IEC 27017:2015</strong> e chiarisce il <strong>modello di responsabilità condivisa</strong> tra l'azienda e i fornitori cloud.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutti i servizi cloud utilizzati per: l'erogazione del <strong>SaaS multi-tenant</strong>; l'ospitalità di ambienti di sviluppo, test e strumenti interni; l'archiviazione di dati aziendali e dei clienti; i servizi di intelligenza artificiale fruiti via API (es. LLM Anthropic). Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) che configura, amministra o accede a tali servizi, esclusivamente tramite <strong>PC portatili cifrati</strong>.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27017:2015 – controlli cloud-specifici (CLD.6.3, CLD.8.1, CLD.9.5, CLD.12.1, CLD.12.4, CLD.13.1) e modello di responsabilità condivisa.</li>
|
||||
<li>ISO/IEC 27001:2022 – Annex A, in particolare A.5.23 (uso di servizi cloud), A.8.9, A.8.10, A.8.13, A.8.15, A.8.24.</li>
|
||||
<li>ISO/IEC 27018:2019 – tutela dei PII nel cloud (cfr. doc. 10).</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio).</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e l'adozione/dismissione di servizi cloud strategici.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, coordina la valutazione dei rischi cloud e i riesami.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Configura e amministra i servizi cloud, gestisce accessi, cifratura, log e hardening secondo la responsabilità condivisa.</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Verifica gli aspetti privacy e i trasferimenti di dati personali.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Corpo – Regole e passi concreti</h2>
|
||||
<h3>5.1 Modello di responsabilità condivisa</h3>
|
||||
<p>Per ogni servizio cloud è documentata la ripartizione delle responsabilità di sicurezza tra <strong>fornitore</strong> (sicurezza fisica dei data center, hypervisor, rete sottostante, disponibilità dell'infrastruttura) e <strong>azienda cliente</strong> (configurazione dei servizi, gestione degli account e degli accessi, cifratura dei dati a riposo e in transito, gestione delle chiavi, backup applicativi, logging). La consapevolezza di "ciò che il fornitore fa per noi e ciò che dobbiamo fare noi" è la base della Politica (CLD.6.3).</p>
|
||||
<h3>5.2 Selezione e collocazione</h3>
|
||||
<ul>
|
||||
<li>Si privilegiano fornitori cloud con certificazioni di sicurezza riconosciute e data center nell'UE/SEE (Aruba in Italia, Hetzner in Germania) per favorire la conformità GDPR.</li>
|
||||
<li>Eventuali servizi extra-UE (es. API AI) sono adottati solo con garanzie adeguate ai trasferimenti e DPA idonei.</li>
|
||||
</ul>
|
||||
<h3>5.3 Gestione degli accessi</h3>
|
||||
<ul>
|
||||
<li>Accessi amministrativi nominali, con autenticazione forte (MFA) e principio del minimo privilegio.</li>
|
||||
<li>Separazione dei ruoli e revisione periodica delle utenze; revoca tempestiva al termine del rapporto.</li>
|
||||
<li>L'accesso avviene solo da PC portatili cifrati e aggiornati.</li>
|
||||
</ul>
|
||||
<h3>5.4 Protezione dei dati</h3>
|
||||
<ul>
|
||||
<li>Cifratura dei dati in transito (TLS) e a riposo; gestione sicura delle chiavi.</li>
|
||||
<li>Isolamento e separazione dei dati tra tenant nel SaaS multi-tenant (CLD.9.5).</li>
|
||||
<li>Configurazioni sicure (hardening) e disabilitazione dei servizi non necessari.</li>
|
||||
</ul>
|
||||
<h3>5.5 Logging, monitoraggio e gestione operativa</h3>
|
||||
<ul>
|
||||
<li>Abilitazione dei log di sicurezza e degli accessi amministrativi resi disponibili dai fornitori; conservazione e revisione periodica (CLD.12.1, CLD.12.4).</li>
|
||||
<li>Monitoraggio della disponibilità dei servizi e degli avvisi di sicurezza dei fornitori cloud.</li>
|
||||
<li>Gestione coordinata dei cambiamenti che impattano i servizi cloud.</li>
|
||||
</ul>
|
||||
<h3>5.6 Continuità e fine rapporto</h3>
|
||||
<ul>
|
||||
<li>Backup e ripristino sono gestiti coerentemente con la Politica di Continuità (doc. 11), tenendo conto della responsabilità condivisa.</li>
|
||||
<li>Alla cessazione di un servizio cloud si garantisce l'esportazione dei dati e la loro cancellazione sicura presso il fornitore (CLD.8.1).</li>
|
||||
</ul>
|
||||
<h3>5.7 Gestione incidenti cloud</h3>
|
||||
<p>Gli incidenti che coinvolgono i servizi cloud sono gestiti secondo il processo di gestione incidenti del SGSI; se l'azienda è soggetto NIS2, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024).</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>CLD.6.3</strong> – Ripartizione delle responsabilità tra cliente e fornitore cloud.</li>
|
||||
<li><strong>CLD.8.1</strong> – Rimozione/restituzione degli asset cliente al termine del servizio.</li>
|
||||
<li><strong>CLD.9.5</strong> – Segregazione negli ambienti virtuali multi-tenant.</li>
|
||||
<li><strong>CLD.12.1 / CLD.12.4</strong> – Operatività e logging dei servizi cloud.</li>
|
||||
<li><strong>CLD.13.1</strong> – Gestione della sicurezza di rete nel cloud.</li>
|
||||
<li><strong>A.5.23</strong> – Sicurezza delle informazioni nell'uso di servizi cloud; <strong>A.8.24</strong> – uso della crittografia.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: misure di gestione del rischio; art. 25: notifica incidenti.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Matrice di responsabilità condivisa per ciascun servizio cloud.</li>
|
||||
<li>Inventario dei servizi cloud e relative configurazioni di sicurezza.</li>
|
||||
<li>Registro degli accessi amministrativi e log di sicurezza conservati.</li>
|
||||
<li>Evidenze dei riesami periodici delle configurazioni e degli accessi.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La Politica è riesaminata almeno annualmente o a fronte dell'adozione di nuovi servizi cloud, modifiche architetturali o incidenti rilevanti. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,78 @@
|
||||
<!--META|doc_type=politica_privacy_cloud|title=Politica di Protezione dei Dati Personali nel Cloud (ISO/IEC 27018 e GDPR)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> protegge i dati personali (PII) che tratta nel cloud per conto dei propri clienti tramite il <strong>SaaS multi-tenant</strong>. L'azienda, nel trattare tali dati, agisce di norma come <strong>responsabile del trattamento</strong> per conto del cliente (titolare). La Politica recepisce i controlli della <strong>ISO/IEC 27018:2019</strong> per la tutela dei PII nei servizi cloud pubblici e ne assicura la coerenza con gli obblighi del <strong>Regolamento (UE) 2016/679 (GDPR)</strong>.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica a tutti i trattamenti di dati personali di clienti e di interessati finali svolti tramite l'infrastruttura cloud (Aruba S.p.A. – Italia; Hetzner – Germania, UE) e ai servizi accessori, inclusi i servizi di intelligenza artificiale fruiti via API. Si applica a tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) e ai sub-fornitori che concorrono al trattamento. Riguarda l'intero ciclo di vita del dato: raccolta, archiviazione, elaborazione, trasferimento, conservazione e cancellazione.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27018:2019 – codice di condotta per la protezione dei PII nei cloud pubblici.</li>
|
||||
<li>ISO/IEC 27017:2015 – sicurezza dei servizi cloud (cfr. doc. 9).</li>
|
||||
<li>ISO/IEC 27001:2022 – Annex A, A.5.34 (privacy e protezione dei PII), A.8.10, A.8.11, A.8.12, A.8.24.</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR) – artt. 5, 28, 30, 32, 33, 44-49.</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (misure di gestione del rischio).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica e garantisce le risorse per la conformità privacy.</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Sorveglia la conformità GDPR/27018, supporta i DPA, le richieste degli interessati e la gestione dei data breach.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Integra i requisiti privacy nel SGSI e coordina i riesami.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Attua le misure tecniche (cifratura, accessi, log, cancellazione) sui sistemi cloud.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Corpo – Regole e passi concreti</h2>
|
||||
<h3>5.1 Ruolo di responsabile e istruzioni del titolare</h3>
|
||||
<ul>
|
||||
<li>L'azienda tratta i PII dei clienti <strong>solo su istruzione documentata del titolare</strong> (art. 28 GDPR; principio 27018) e per le finalità del servizio, senza usi propri non autorizzati.</li>
|
||||
<li>Con ogni cliente che affida PII è stipulato un <strong>DPA ex art. 28 GDPR</strong> che disciplina finalità, durata, misure di sicurezza, sub-responsabili e assistenza al titolare.</li>
|
||||
</ul>
|
||||
<h3>5.2 Trasparenza e sub-fornitori</h3>
|
||||
<ul>
|
||||
<li>I sub-fornitori che trattano PII (es. provider cloud, eventuali servizi AI) sono dichiarati al cliente e vincolati a obblighi di protezione equivalenti.</li>
|
||||
<li>I cambi di sub-fornitore rilevanti sono comunicati al titolare secondo gli accordi.</li>
|
||||
</ul>
|
||||
<h3>5.3 Misure di sicurezza tecniche</h3>
|
||||
<ul>
|
||||
<li>Cifratura dei PII in transito (TLS) e a riposo; gestione sicura delle chiavi (A.8.24).</li>
|
||||
<li>Controllo degli accessi su base nominale, minimo privilegio e autenticazione forte; tracciamento degli accessi ai PII.</li>
|
||||
<li>Segregazione dei dati tra tenant; ambienti di test che non utilizzano PII reali (dati anonimizzati/sintetici).</li>
|
||||
<li>Log degli accessi e delle operazioni sui PII, conservati e riesaminati.</li>
|
||||
</ul>
|
||||
<h3>5.4 Localizzazione e trasferimenti</h3>
|
||||
<ul>
|
||||
<li>I PII sono ospitati di preferenza su data center nell'UE/SEE (Italia, Germania).</li>
|
||||
<li>Eventuali trasferimenti extra-UE (es. verso servizi AI) avvengono solo con garanzie adeguate ai sensi degli artt. 44-49 GDPR e sono documentati.</li>
|
||||
</ul>
|
||||
<h3>5.5 Diritti degli interessati e assistenza al titolare</h3>
|
||||
<p>L'azienda assiste il titolare nel dare seguito alle richieste degli interessati (accesso, rettifica, cancellazione, portabilità, opposizione), fornendo gli strumenti tecnici per individuare ed estrarre/cancellare i dati nel SaaS.</p>
|
||||
<h3>5.6 Conservazione e cancellazione sicura</h3>
|
||||
<ul>
|
||||
<li>I PII sono conservati per il tempo previsto dal contratto/dalla finalità; al termine del rapporto sono restituiti e/o cancellati in modo sicuro, anche presso i provider cloud.</li>
|
||||
</ul>
|
||||
<h3>5.7 Violazioni di dati personali</h3>
|
||||
<p>In caso di data breach, l'azienda informa <strong>senza ingiustificato ritardo</strong> il titolare (art. 33 GDPR), supportandolo nelle valutazioni e notifiche. Se l'evento è anche un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si applicano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024); il coordinamento tra notifica privacy e notifica NIS2 è gestito dal DPO con il RSGSI.</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li>Controlli <strong>ISO/IEC 27018</strong>: consenso e scopo, trasparenza sui sub-fornitori, cancellazione sicura, notifica accessi, restrizione d'uso dei PII, tracciamento.</li>
|
||||
<li><strong>A.5.34</strong> – Privacy e protezione dei dati personali.</li>
|
||||
<li><strong>A.8.10 / A.8.11</strong> – Cancellazione delle informazioni e mascheramento dei dati.</li>
|
||||
<li><strong>A.8.12</strong> – Prevenzione della fuga di dati; <strong>A.8.24</strong> – uso della crittografia.</li>
|
||||
<li><strong>GDPR</strong>: artt. 5, 28, 30, 32, 33, 44-49.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024</strong>: art. 24 (misure di gestione del rischio), art. 25 (notifica incidenti).</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>DPA con i clienti e con i sub-responsabili; elenco dei sub-fornitori.</li>
|
||||
<li>Registro dei trattamenti per conto del titolare (supporto art. 30 GDPR).</li>
|
||||
<li>Log degli accessi ai PII e registro delle richieste degli interessati.</li>
|
||||
<li>Registro dei data breach e relative comunicazioni al titolare.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La Politica è riesaminata almeno annualmente o a fronte di nuovi trattamenti, cambi di sub-fornitori o aggiornamenti normativi. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente con il parere del DPO. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,75 @@
|
||||
<!--META|doc_type=politica_continuita|title=Politica di Continuità Operativa e Backup|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Politica definisce i principi e le regole con cui <strong>Nuova Agile Technology srl</strong> assicura la continuità dei propri servizi e la disponibilità delle informazioni, anche a fronte di eventi avversi (guasti cloud, attacchi informatici, errori, indisponibilità di personale). Considerata l'infrastruttura <strong>interamente in cloud</strong> e l'assenza di server in sede, l'obiettivo è garantire backup affidabili, capacità di ripristino del <strong>SaaS multi-tenant</strong> e dei dati dei clienti, e la continuità operativa di un team piccolo che lavora da <strong>PC portatili cifrati</strong>. La Politica supporta gli obblighi di <strong>continuità e gestione delle crisi</strong> previsti dalle misure dell'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Politica si applica alla disponibilità del servizio SaaS, dei dati aziendali e dei clienti, del codice sorgente, degli ambienti di sviluppo e degli strumenti essenziali, ospitati su Aruba S.p.A. (Italia) e Hetzner (Germania, UE). Riguarda tutto il personale (9 dipendenti, 2 collaboratori esterni a P.IVA) e i fornitori cloud, nel rispetto del modello di responsabilità condivisa. Sono inclusi backup, ripristino, gestione delle crisi e continuità delle persone chiave.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – Annex A, A.5.29 (sicurezza durante le interruzioni), A.5.30 (prontezza ICT per la continuità operativa), A.8.13 (backup), A.8.14 (ridondanza).</li>
|
||||
<li>ISO/IEC 27017:2015 – continuità nei servizi cloud e responsabilità condivisa.</li>
|
||||
<li>ISO/IEC 27018:2019 – conservazione e ripristino dei PII nel cloud.</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (continuità operativa e gestione delle crisi) e art. 25 (notifica incidenti).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Approva la Politica, gli obiettivi di continuità e attiva la gestione delle crisi maggiori.</td></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Mantiene la Politica, coordina i test di ripristino e i riesami della continuità.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Configura e verifica backup e ripristini, gestisce ridondanza e procedure tecniche di recovery.</td></tr>
|
||||
<tr><td>Tutto il personale</td><td>Segue le procedure di backup degli strumenti di lavoro e collabora alla continuità.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Corpo – Regole e passi concreti</h2>
|
||||
<h3>5.1 Obiettivi di continuità</h3>
|
||||
<p>Per i servizi critici (SaaS e dati dei clienti) sono definiti obiettivi di ripristino: <strong>RTO</strong> (tempo massimo di ripristino) e <strong>RPO</strong> (massima perdita di dati accettabile). I valori puntuali sono definiti per ciascun servizio nelle procedure operative <strong>[DA VERIFICARE]</strong> e approvati dalla Direzione.</p>
|
||||
<h3>5.2 Backup</h3>
|
||||
<ul>
|
||||
<li>I dati del SaaS, i database dei clienti e il codice sorgente sono sottoposti a backup periodici e automatizzati.</li>
|
||||
<li>I backup seguono il principio di ridondanza con copie su posizioni/servizi distinti rispetto alla produzione (preferibilmente in regione/data center diversi nell'UE).</li>
|
||||
<li>I backup contenenti dati personali sono cifrati e protetti con i medesimi controlli di accesso dei dati di produzione.</li>
|
||||
<li>È definita una politica di conservazione (retention) e di rotazione dei backup.</li>
|
||||
</ul>
|
||||
<h3>5.3 Test di ripristino</h3>
|
||||
<ul>
|
||||
<li>I ripristini sono testati periodicamente (almeno annualmente) per verificarne l'efficacia e il rispetto di RTO/RPO; gli esiti sono registrati.</li>
|
||||
<li>Un backup non testato non è considerato affidabile.</li>
|
||||
</ul>
|
||||
<h3>5.4 Ridondanza e dipendenza dai fornitori cloud</h3>
|
||||
<ul>
|
||||
<li>Si valutano le garanzie di disponibilità (SLA) dei provider cloud e si predispongono misure per ridurre l'impatto di un'indisponibilità prolungata, inclusa la portabilità dei dati.</li>
|
||||
<li>La ripartizione delle responsabilità di continuità tra azienda e fornitore è documentata (modello di responsabilità condivisa, ISO 27017).</li>
|
||||
</ul>
|
||||
<h3>5.5 Continuità delle persone chiave</h3>
|
||||
<p>Trattandosi di un team piccolo, sono identificate le competenze critiche e previste misure di ridondanza minime (documentazione, accessi di backup custoditi in sicurezza, condivisione delle conoscenze) per evitare single point of failure umani.</p>
|
||||
<h3>5.6 Continuità operativa quotidiana</h3>
|
||||
<ul>
|
||||
<li>I PC portatili sono cifrati; i dati di lavoro essenziali sono sincronizzati/salvati su servizi cloud aziendali per evitarne la perdita in caso di guasto o furto del dispositivo.</li>
|
||||
<li>L'operatività non dipende da risorse fisiche in sede, riducendo i rischi legati a una singola ubicazione.</li>
|
||||
</ul>
|
||||
<h3>5.7 Gestione delle crisi e degli incidenti</h3>
|
||||
<p>In caso di evento grave si attiva la gestione delle crisi con comunicazione interna e ai clienti impattati. Se l'evento è un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024).</p>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>A.5.29</strong> – Sicurezza delle informazioni durante le interruzioni.</li>
|
||||
<li><strong>A.5.30</strong> – Prontezza ICT per la continuità operativa.</li>
|
||||
<li><strong>A.8.13</strong> – Backup delle informazioni.</li>
|
||||
<li><strong>A.8.14</strong> – Ridondanza degli impianti di elaborazione.</li>
|
||||
<li>Controlli ISO/IEC 27017 e 27018 per continuità e ripristino nel cloud e dei PII.</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: continuità operativa, gestione del backup e gestione delle crisi; art. 25: notifica incidenti.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Piano/procedure di backup e ripristino con RTO/RPO definiti.</li>
|
||||
<li>Registri di esecuzione dei backup ed esiti dei test di ripristino.</li>
|
||||
<li>Inventario dei servizi critici e relative dipendenze cloud.</li>
|
||||
<li>Verbali di gestione delle crisi e riesami della continuità.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La Politica è riesaminata almeno annualmente o dopo un incidente significativo, un test di ripristino con esito negativo o cambiamenti infrastrutturali. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,82 @@
|
||||
<!--META|doc_type=procedura_gestione_fornitori|title=Procedura di Gestione dei Fornitori e dei Servizi Cloud|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente Procedura descrive i passi operativi con cui <strong>Nuova Agile Technology srl</strong> seleziona, qualifica, contrattualizza, monitora e dismette i fornitori e i servizi cloud, in attuazione della <strong>Politica di Sicurezza dei Fornitori</strong> (doc. 07) e della <strong>Politica di Sicurezza del Cloud</strong> (doc. 09). Lo scopo è rendere ripetibile e tracciabile la gestione delle terze parti — in particolare i fornitori cloud (Aruba, Hetzner) e i servizi di intelligenza artificiale via API — assicurando il rispetto dei requisiti di sicurezza della supply chain previsti dall'<strong>art. 24 del D.Lgs. 138/2024</strong> (NIS2) e degli obblighi GDPR verso i dati personali dei clienti.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>La Procedura si applica a tutti i nuovi fornitori e a quelli esistenti che trattano informazioni aziendali o dei clienti, erogano infrastruttura/servizi cloud o contribuiscono allo sviluppo e alla manutenzione dei prodotti (in licenza e SaaS). Coinvolge il personale che richiede, valuta e gestisce i fornitori (9 dipendenti, 2 collaboratori esterni a P.IVA).</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 – A.5.19, A.5.20, A.5.21, A.5.22, A.5.23.</li>
|
||||
<li>ISO/IEC 27017:2015 (CLD.6.3, CLD.8.1) e ISO/IEC 27018:2019.</li>
|
||||
<li>D.Lgs. 138/2024, art. 24 (sicurezza della supply chain) e art. 25 (notifica incidenti).</li>
|
||||
<li>Regolamento (UE) 2016/679 (GDPR), art. 28.</li>
|
||||
<li>Politiche doc. 07 (Fornitori), doc. 09 (Cloud), doc. 10 (Privacy nel cloud).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Responsabilità</th></tr>
|
||||
<tr><td>RSGSI (Massimo Tagliavini)</td><td>Coordina la Procedura, mantiene il registro fornitori e pianifica i riesami.</td></tr>
|
||||
<tr><td>Resp. IT/Sicurezza (Simon Fattori)</td><td>Esegue la valutazione tecnica di sicurezza, definisce e verifica la matrice di responsabilità condivisa, monitora SLA e incidenti.</td></tr>
|
||||
<tr><td>DPO (consulente esterno)</td><td>Valuta gli aspetti privacy, valida i DPA e i trasferimenti di dati.</td></tr>
|
||||
<tr><td>Presidente (Silvia Garretto)</td><td>Autorizza i fornitori critici e accetta i rischi residui.</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Corpo – Passi della procedura</h2>
|
||||
<h3>5.1 Fase 1 – Richiesta e classificazione</h3>
|
||||
<ul>
|
||||
<li>Il richiedente apre una richiesta di nuovo fornitore indicando servizio, dati coinvolti e finalità.</li>
|
||||
<li>Il RSGSI classifica il fornitore (critico / rilevante / ordinario) in base alla sensibilità dei dati e alla criticità del servizio.</li>
|
||||
</ul>
|
||||
<h3>5.2 Fase 2 – Due diligence e valutazione di sicurezza</h3>
|
||||
<ul>
|
||||
<li>Per fornitori critici/rilevanti, il Resp. IT raccoglie evidenze: certificazioni (ISO 27001/27017/27018, SOC 2), localizzazione dei data center, misure tecniche/organizzative, gestione incidenti.</li>
|
||||
<li>Si compila una scheda di valutazione con esito (idoneo / idoneo con prescrizioni / non idoneo).</li>
|
||||
<li>Per i servizi cloud si definisce la <strong>matrice di responsabilità condivisa</strong> (cosa fa il fornitore, cosa fa l'azienda).</li>
|
||||
</ul>
|
||||
<h3>5.3 Fase 3 – Contrattualizzazione</h3>
|
||||
<ul>
|
||||
<li>Si stipula il contratto con clausole di sicurezza: riservatezza, SLA, obbligo di notifica incidenti, diritto a evidenze/audit, gestione del fine rapporto e cancellazione dati.</li>
|
||||
<li>Se sono trattati dati personali, si firma il <strong>DPA ex art. 28 GDPR</strong>, verificando i sub-responsabili.</li>
|
||||
<li>Per i fornitori critici, l'attivazione richiede l'autorizzazione della Presidente.</li>
|
||||
</ul>
|
||||
<h3>5.4 Fase 4 – Attivazione e configurazione sicura</h3>
|
||||
<ul>
|
||||
<li>Si configurano accessi nominali con MFA e minimo privilegio, cifratura, logging, secondo la Politica Cloud (doc. 09).</li>
|
||||
<li>Il fornitore è inserito nel <strong>registro fornitori</strong> con classificazione, scadenze contrattuali e referenti.</li>
|
||||
</ul>
|
||||
<h3>5.5 Fase 5 – Monitoraggio e riesame periodico</h3>
|
||||
<ul>
|
||||
<li>Almeno annualmente per i fornitori critici: verifica SLA, rinnovo evidenze e certificazioni, revisione accessi, analisi di eventuali incidenti.</li>
|
||||
<li>Si valutano avvisi di sicurezza dei fornitori cloud/AI e si registrano gli esiti del riesame.</li>
|
||||
<li>Le non conformità generano azioni correttive tracciate.</li>
|
||||
</ul>
|
||||
<h3>5.6 Fase 6 – Gestione incidenti del fornitore</h3>
|
||||
<p>In caso di incidente comunicato dal fornitore o rilevato dall'azienda, si attiva il processo di gestione incidenti del SGSI; se l'evento è un incidente NIS2 rilevante e l'azienda è soggetto obbligato, si rispettano i tempi di notifica al CSIRT Italia (pre-allarme 24h, notifica 72h, relazione finale 1 mese, art. 25 D.Lgs. 138/2024). Gli incidenti che coinvolgono PII sono comunicati al DPO.</p>
|
||||
<h3>5.7 Fase 7 – Dismissione e fine rapporto</h3>
|
||||
<ul>
|
||||
<li>Alla cessazione si revocano gli accessi, si recuperano/esportano i dati e se ne verifica la cancellazione sicura presso il fornitore (CLD.8.1).</li>
|
||||
<li>Si aggiorna il registro fornitori e si valuta l'impatto sulla continuità (doc. 11).</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. Controlli ISO collegati e riferimenti NIS2</h2>
|
||||
<ul>
|
||||
<li><strong>A.5.19 / A.5.20</strong> – Sicurezza nei rapporti e negli accordi con i fornitori.</li>
|
||||
<li><strong>A.5.21</strong> – Sicurezza nella catena di fornitura ICT.</li>
|
||||
<li><strong>A.5.22</strong> – Monitoraggio e gestione dei cambiamenti dei servizi dei fornitori.</li>
|
||||
<li><strong>A.5.23</strong> – Sicurezza nell'uso di servizi cloud; <strong>CLD.6.3 / CLD.8.1</strong> (ISO 27017).</li>
|
||||
<li><strong>NIS2 – D.Lgs. 138/2024, art. 24</strong>: sicurezza della supply chain; art. 25: notifica incidenti.</li>
|
||||
</ul>
|
||||
|
||||
<h2>7. Registrazioni ed evidenze</h2>
|
||||
<ul>
|
||||
<li>Registro dei fornitori (classificazione, scadenze, referenti).</li>
|
||||
<li>Schede di valutazione di sicurezza e matrici di responsabilità condivisa.</li>
|
||||
<li>Contratti, DPA e clausole di sicurezza.</li>
|
||||
<li>Verbali dei riesami periodici, registro azioni correttive e registro incidenti dei fornitori.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>La Procedura è riesaminata almeno annualmente o a fronte di variazioni del parco fornitori, incidenti rilevanti o aggiornamenti normativi. Versione corrente: <strong>1.0</strong>, approvata dalla Presidente. Le revisioni sono tracciate nel sistema documentale del SGSI a cura del RSGSI.</p>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--META|doc_type=procedura_risk_assessment|title=Procedura di Valutazione e Trattamento dei Rischi|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Valutazione e Trattamento dei Rischi</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire le modalità con cui Nuova Agile Technology srl identifica, analizza, valuta e tratta i rischi per la sicurezza delle informazioni, in modo sistematico e ripetibile. La procedura attua i requisiti della ISO/IEC 27001:2022 cl. 6.1.2 (valutazione dei rischi), 6.1.3 (trattamento dei rischi), 8.2 e 8.3 (esecuzione operativa) e supporta la determinazione delle misure ex art. 24 D.Lgs. 138/2024 (NIS2) per i soggetti rientranti.</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a tutte le informazioni e agli asset trattati dall'azienda: codice sorgente e prodotti rilasciati on-premise, piattaforma SaaS in cloud, dati personali dei clienti, infrastruttura cloud (Aruba IT, Hetzner DE, piattaforme AI) e PC portatili aziendali. Non esistendo server in sede, l'analisi considera i rischi di un modello <em>cloud-only</em> (responsabilità condivisa, controlli ISO/IEC 27017 e 27018 per il trattamento di PII nel cloud).</p>
|
||||
|
||||
<h3>3. Riferimenti</h3>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 6.1.2, 6.1.3, 8.2, 8.3; controlli A.5.7 (threat intelligence), A.5.9 (inventario asset), A.8.8 (gestione vulnerabilità tecniche).</li>
|
||||
<li>ISO/IEC 27005 (linee guida per la stima del rischio) e ISO 31000.</li>
|
||||
<li>ISO/IEC 27017 §CLD.6.3 e 27018 (rischi specifici cloud e PII).</li>
|
||||
<li>D.Lgs. 138/2024 art. 24 (misure di gestione del rischio); Determinazione ACN 164179/2025 [DA VERIFICARE riferimento applicabile].</li>
|
||||
<li>Dichiarazione di Applicabilità (SoA), Politica del SGSI, Procedura NC/Azioni Correttive.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Ruoli e responsabilità</h3>
|
||||
<ul>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: approva i criteri di rischio, la propensione al rischio (risk appetite) e il piano di trattamento; accetta formalmente i rischi residui.</li>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: conduce e coordina la valutazione, mantiene il registro dei rischi, propone il piano di trattamento, monitora le scadenze.</li>
|
||||
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: fornisce l'inventario asset, le informazioni tecniche su minacce/vulnerabilità, implementa i controlli tecnici di trattamento.</li>
|
||||
<li><strong>DPO esterno</strong>: consultato per i rischi sul trattamento di dati personali (PII nel SaaS).</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Flusso/attività passo-passo</h3>
|
||||
<ol>
|
||||
<li><strong>Definizione del contesto e dei criteri</strong> (input: SoA, inventario asset; output: criteri approvati). RSGSI e Direzione fissano scala di probabilità (1–5) e impatto (1–5), soglia di accettabilità e criteri di significatività.</li>
|
||||
<li><strong>Identificazione del rischio</strong> (input: asset, minacce, vulnerabilità; output: rischi censiti). Per ciascun asset si individuano le minacce plausibili (es. compromissione credenziali cloud, data breach SaaS, perdita di un portatile, indisponibilità del provider). Si registra ogni rischio nel <em>modulo "Rischi"</em> della piattaforma.</li>
|
||||
<li><strong>Analisi e ponderazione</strong> (output: punteggio di rischio inerente). Si calcola Rischio = Probabilità × Impatto secondo la matrice 5×5 (coerente con ISO/IEC 27005). Il punteggio determina la classe (basso/medio/alto/critico).</li>
|
||||
<li><strong>Valutazione</strong>: confronto con la soglia. I rischi sopra soglia richiedono trattamento; i rischi sotto soglia sono candidati all'accettazione.</li>
|
||||
<li><strong>Trattamento</strong> (output: piano di trattamento). Per ogni rischio sopra soglia si sceglie l'opzione: <em>mitigazione</em> (applicazione controlli Annex A), <em>trasferimento</em> (es. clausole contrattuali con il provider cloud, assicurazione), <em>evitamento</em> o <em>accettazione</em>. I controlli scelti sono registrati come trattamenti nel modulo "Rischi" e riconciliati con la SoA.</li>
|
||||
<li><strong>Approvazione e accettazione del rischio residuo</strong>: la Direzione approva il piano di trattamento e accetta formalmente i rischi residui (cl. 6.1.3 e).</li>
|
||||
<li><strong>Attuazione e monitoraggio</strong>: il Resp. IT implementa i controlli; il RSGSI traccia stato e scadenze nel modulo. Le carenze rilevate diventano Non Conformità (vedi procedura dedicata).</li>
|
||||
</ol>
|
||||
|
||||
<h3>6. Controlli ISO/clausole collegati</h3>
|
||||
<p>cl. 6.1.2/6.1.3 (processo e piano), cl. 8.2/8.3 (esecuzione e produzione della SoA aggiornata), A.5.7, A.5.9, A.8.8; per il cloud A.5.19–A.5.23 (rapporti con i fornitori e servizi cloud) e i controlli estesi ISO/IEC 27017 e 27018 sulla protezione delle PII.</p>
|
||||
|
||||
<h3>7. Registrazioni/evidenze</h3>
|
||||
<ul>
|
||||
<li><strong>Registro dei rischi e trattamenti</strong>: modulo "Rischi" della piattaforma (rischio inerente, controlli, rischio residuo, owner, scadenze).</li>
|
||||
<li><strong>Piano di trattamento e accettazione rischi residui</strong>: verbale di approvazione della Direzione, archiviato come informazione documentata (vedi procedura Controllo Documenti).</li>
|
||||
<li><strong>SoA aggiornata</strong> (modulo SGSI/SoA).</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>La valutazione dei rischi è riesaminata <strong>almeno una volta l'anno</strong> e ogni volta che si verifichino cambiamenti significativi (nuovo prodotto/servizio, nuovo provider, incidente rilevante, modifica normativa). Gli esiti alimentano il Riesame della Direzione. La procedura è soggetta a versionamento secondo la Procedura di Controllo dei Documenti.</p>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--META|doc_type=procedura_controllo_documenti|title=Procedura di Controllo dei Documenti e delle Registrazioni|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Controllo dei Documenti e delle Registrazioni</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Garantire che le informazioni documentate richieste dal SGSI di Nuova Agile Technology srl siano create, identificate, approvate, distribuite, protette e conservate in modo controllato, e che le registrazioni (evidenze) restino leggibili, integre e recuperabili. La procedura attua la ISO/IEC 27001:2022 cl. 7.5 (informazioni documentate).</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a tutta la documentazione del SGSI (politiche, procedure di sistema, istruzioni operative, SoA, registri) e a tutte le registrazioni prodotte dai processi (verbali, report di audit, valutazioni dei rischi, registri formazione, NC/CAPA). Comprende sia i documenti gestiti nella piattaforma sia gli eventuali documenti esterni archiviati nel cloud aziendale (Aruba IT). Essendo l'azienda <em>cloud-only</em>, non esiste documentazione cartacea controllata di default.</p>
|
||||
|
||||
<h3>3. Riferimenti</h3>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 7.5.1 (generalità), 7.5.2 (creazione e aggiornamento), 7.5.3 (controllo delle informazioni documentate).</li>
|
||||
<li>Controlli A.5.33 (protezione delle registrazioni), A.5.34 (privacy e PII), A.8.10–A.8.12 (cancellazione, mascheramento, prevenzione fuga di dati).</li>
|
||||
<li>ISO/IEC 27018 (conservazione e cancellazione delle PII nel cloud); GDPR per i periodi di conservazione dei dati personali.</li>
|
||||
<li>Politica del SGSI; tutte le procedure di sistema.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Ruoli e responsabilità</h3>
|
||||
<ul>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: gestisce l'elenco dei documenti del SGSI, ne controlla numerazione, versioni e stato; verifica la disponibilità delle versioni vigenti.</li>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: approva politiche e procedure di sistema (autorità di approvazione).</li>
|
||||
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: garantisce backup, controllo accessi e cifratura degli archivi documentali cloud; definisce i diritti di lettura/scrittura.</li>
|
||||
<li><strong>Autori/Responsabili di processo</strong>: redigono e aggiornano i documenti di propria competenza.</li>
|
||||
<li><strong>DPO esterno</strong>: indica i tempi di conservazione e cancellazione delle registrazioni contenenti dati personali.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Flusso/attività passo-passo</h3>
|
||||
<ol>
|
||||
<li><strong>Identificazione</strong> (input: esigenza documentale; output: documento codificato). Ogni documento riceve titolo, codice/slug, numero di versione, data e stato (bozza/in revisione/approvato/obsoleto).</li>
|
||||
<li><strong>Redazione e aggiornamento</strong> (cl. 7.5.2): l'autore redige in formato e struttura standard; le modifiche sono tracciate con la cronologia delle versioni.</li>
|
||||
<li><strong>Verifica e approvazione</strong>: il RSGSI verifica adeguatezza e coerenza; la Direzione (o delegato) approva. Un documento è pubblicabile solo nello stato "approvato".</li>
|
||||
<li><strong>Distribuzione e disponibilità</strong> (cl. 7.5.3): la versione vigente è resa disponibile a chi ne ha bisogno tramite la piattaforma/archivio cloud; gli accessi sono profilati per ruolo.</li>
|
||||
<li><strong>Controllo delle modifiche</strong>: ogni revisione genera una nuova versione; la precedente è marcata "obsoleta" e conservata per tracciabilità, non più utilizzabile come riferimento operativo.</li>
|
||||
<li><strong>Documenti di origine esterna</strong> (leggi, norme, contratti cloud, manuali provider): sono identificati e la loro distribuzione è controllata.</li>
|
||||
<li><strong>Protezione e conservazione delle registrazioni</strong>: le evidenze sono protette da perdita, alterazione e accesso non autorizzato mediante backup, cifratura e controllo accessi del provider cloud; i periodi di conservazione rispettano legge e GDPR; la cancellazione avviene in modo sicuro (A.8.10).</li>
|
||||
</ol>
|
||||
|
||||
<h3>6. Controlli ISO/clausole collegati</h3>
|
||||
<p>cl. 7.5.1/7.5.2/7.5.3; A.5.33 (protezione registrazioni), A.5.37 (procedure operative documentate), A.8.10/A.8.11/A.8.12; per il cloud i requisiti ISO/IEC 27018 su conservazione, restituzione e cancellazione delle PII.</p>
|
||||
|
||||
<h3>7. Registrazioni/evidenze</h3>
|
||||
<ul>
|
||||
<li><strong>Elenco master dei documenti del SGSI</strong> con stato e versione (mantenuto dal RSGSI nella piattaforma/archivio cloud).</li>
|
||||
<li><strong>Cronologia delle versioni</strong> di ciascun documento (campo versione + data + autore).</li>
|
||||
<li><strong>Registrazioni di processo</strong>: report di audit, verbali di riesame, valutazioni dei rischi, registri formazione e NC/CAPA risiedono nei rispettivi <em>moduli della piattaforma</em> (Audit interni, Riesame, Rischi, Formazione, NCR/CAPA), che fungono da repository controllato delle evidenze.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>La presente procedura e l'elenco master sono riesaminati <strong>almeno annualmente</strong> e ad ogni modifica organizzativa o normativa rilevante. Ogni nuova edizione incrementa la versione (es. 1.0 → 1.1 per modifiche minori, → 2.0 per modifiche sostanziali) ed è approvata dalla Direzione prima della distribuzione.</p>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--META|doc_type=procedura_audit_interni|title=Procedura di Audit Interni|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Audit Interni</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire le modalità di pianificazione, conduzione e rendicontazione degli audit interni del SGSI di Nuova Agile Technology srl, al fine di verificare che il sistema di gestione sia conforme ai requisiti della ISO/IEC 27001:2022, ai requisiti propri dell'organizzazione e che sia efficacemente attuato e mantenuto. La procedura attua la cl. 9.2 (audit interni).</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Copre tutti i processi e i controlli del SGSI: governance del SGSI, gestione dei rischi, gestione degli incidenti, sicurezza del cloud (Aruba IT, Hetzner DE, piattaforme AI), gestione delle PII nel SaaS, sicurezza dei portatili, formazione e rapporti con i fornitori. Data la dimensione del team (~12 persone), gli audit sono di norma condotti dal RSGSI o da risorsa interna indipendente dall'area auditata; per oggettività su aree gestite dallo stesso RSGSI è possibile ricorrere a un auditor esterno.</p>
|
||||
|
||||
<h3>3. Riferimenti</h3>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 9.2.1 (generalità) e 9.2.2 (programma di audit).</li>
|
||||
<li>ISO 19011 (linee guida per gli audit dei sistemi di gestione).</li>
|
||||
<li>Controllo A.5.35 (riesame indipendente della sicurezza delle informazioni); A.5.36 (conformità a politiche e standard).</li>
|
||||
<li>D.Lgs. 138/2024 art. 24 (verifica dell'efficacia delle misure) [DA VERIFICARE per i soggetti rientranti].</li>
|
||||
<li>Procedura NC/Azioni Correttive; Procedura Riesame della Direzione.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Ruoli e responsabilità</h3>
|
||||
<ul>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: predispone il programma di audit, nomina gli auditor, garantisce competenza e indipendenza, consolida i risultati.</li>
|
||||
<li><strong>Auditor</strong> (interno indipendente o esterno): conduce l'audit, raccoglie le evidenze, formula i rilievi e redige il report.</li>
|
||||
<li><strong>Responsabili delle aree auditate (es. Simon Fattori per IT/Sicurezza)</strong>: forniscono evidenze, concordano le azioni correttive e ne curano l'attuazione.</li>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: riceve l'esito degli audit in sede di riesame e assicura le risorse per le azioni.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Flusso/attività passo-passo</h3>
|
||||
<ol>
|
||||
<li><strong>Programmazione</strong> (input: esiti audit precedenti, importanza dei processi, esiti valutazione rischi; output: programma di audit annuale). Si pianifica almeno un ciclo di audit interno all'anno che copra tutte le clausole e i controlli applicabili; aree a rischio più elevato possono essere auditate più di frequente.</li>
|
||||
<li><strong>Pianificazione del singolo audit</strong> (output: piano di audit). Si definiscono obiettivi, criteri (la norma e i documenti del SGSI), ambito, date e auditor. Il piano è registrato nel <em>modulo "Audit interni"</em> della piattaforma.</li>
|
||||
<li><strong>Conduzione</strong> (input: documenti e interviste; output: evidenze). L'auditor verifica documentazione, configurazioni cloud, registri e prassi operative; raccoglie evidenze oggettive e annota osservazioni e non conformità.</li>
|
||||
<li><strong>Classificazione dei rilievi</strong>: ciascun rilievo è classificato come Non Conformità (maggiore/minore) oppure Osservazione/Opportunità di miglioramento.</li>
|
||||
<li><strong>Rendicontazione</strong> (output: report di audit). L'auditor redige il report nel modulo "Audit interni" indicando esito, rilievi e raccomandazioni; il report è condiviso con i responsabili di area e con la Direzione.</li>
|
||||
<li><strong>Gestione dei rilievi</strong>: ogni Non Conformità apre una scheda nel modulo NCR/CAPA (vedi procedura dedicata); il RSGSI ne verifica la chiusura.</li>
|
||||
<li><strong>Follow-up</strong>: gli esiti e lo stato delle azioni alimentano il successivo Riesame della Direzione.</li>
|
||||
</ol>
|
||||
|
||||
<h3>6. Controlli ISO/clausole collegati</h3>
|
||||
<p>cl. 9.2.1/9.2.2; A.5.35 (riesame indipendente) e A.5.36 (conformità). Gli esiti sono input obbligatori della cl. 9.3 (riesame della direzione) e possono attivare la cl. 10.2 (NC e azioni correttive).</p>
|
||||
|
||||
<h3>7. Registrazioni/evidenze</h3>
|
||||
<ul>
|
||||
<li><strong>Programma di audit annuale</strong> e <strong>piani dei singoli audit</strong>: modulo "Audit interni".</li>
|
||||
<li><strong>Report di audit</strong> con rilievi ed evidenze raccolte: modulo "Audit interni".</li>
|
||||
<li><strong>Schede di Non Conformità/azioni correttive</strong> generate dai rilievi: modulo NCR/CAPA.</li>
|
||||
<li><strong>Evidenza di indipendenza/competenza degli auditor</strong>: registro formazione/competenze.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Il programma di audit è riesaminato <strong>almeno annualmente</strong> e aggiornato a seguito di cambiamenti significativi (nuovi servizi, incidenti, modifiche normative). La presente procedura è soggetta a versionamento secondo la Procedura di Controllo dei Documenti ed è approvata dalla Direzione.</p>
|
||||
@@ -0,0 +1,47 @@
|
||||
<!--META|doc_type=procedura_riesame_direzione|title=Procedura di Riesame della Direzione|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Riesame della Direzione</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire le modalità con cui la Direzione di Nuova Agile Technology srl riesamina periodicamente il SGSI per assicurarne la continua idoneità, adeguatezza ed efficacia, e per decidere su opportunità di miglioramento e necessità di cambiamento. La procedura attua la ISO/IEC 27001:2022 cl. 9.3 (riesame della direzione).</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica all'intero SGSI e a tutte le sue componenti: contesto, parti interessate, obiettivi di sicurezza, rischi, incidenti, audit, non conformità, formazione, fornitori cloud e conformità normativa (incluso NIS2 ove applicabile). Considera la natura <em>cloud-only</em> dell'azienda e la responsabilità condivisa con i provider (Aruba IT, Hetzner DE, piattaforme AI).</p>
|
||||
|
||||
<h3>3. Riferimenti</h3>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 9.3.1 (generalità), 9.3.2 (input del riesame), 9.3.3 (output del riesame).</li>
|
||||
<li>Controlli A.5.1 (politiche per la sicurezza delle informazioni) e A.5.4 (responsabilità della direzione).</li>
|
||||
<li>D.Lgs. 138/2024 art. 23 (obblighi di governance e responsabilità degli organi di amministrazione) [DA VERIFICARE per i soggetti rientranti].</li>
|
||||
<li>Procedura Audit Interni; Procedura Valutazione Rischi; Procedura NC/Azioni Correttive.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Ruoli e responsabilità</h3>
|
||||
<ul>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: presiede il riesame, valuta gli input, assume le decisioni e assegna le risorse; firma il verbale.</li>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: convoca il riesame, prepara e presenta gli input, redige il verbale, dà seguito alle decisioni e ne traccia l'attuazione.</li>
|
||||
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: riporta su incidenti, vulnerabilità, prestazioni dei controlli tecnici e dei fornitori cloud.</li>
|
||||
<li><strong>DPO esterno</strong>: riferisce su tematiche di protezione dei dati personali e PII nel SaaS, su richiesta.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Flusso/attività passo-passo</h3>
|
||||
<ol>
|
||||
<li><strong>Convocazione e raccolta input</strong> (input: dati dei processi del SGSI; output: pacchetto di riesame). Il RSGSI raccoglie, dai moduli della piattaforma, gli elementi richiesti dalla cl. 9.3.2: stato delle azioni dai riesami precedenti; cambiamenti del contesto e delle parti interessate; esiti della valutazione e del trattamento dei rischi; prestazioni di sicurezza (incidenti, non conformità, risultati di monitoraggio e misurazioni); esiti degli audit interni; raggiungimento degli obiettivi di sicurezza; feedback delle parti interessate; opportunità di miglioramento.</li>
|
||||
<li><strong>Conduzione della riunione</strong>: la Direzione esamina ciascun input, valuta l'idoneità/adeguatezza/efficacia del SGSI e discute eventuali necessità di cambiamento.</li>
|
||||
<li><strong>Decisioni e output</strong> (cl. 9.3.3): si deliberano opportunità di miglioramento, cambiamenti al SGSI (politiche, obiettivi, controlli), fabbisogni di risorse e azioni con responsabili e scadenze.</li>
|
||||
<li><strong>Verbalizzazione</strong> (output: verbale di riesame). Il RSGSI redige il verbale nel <em>modulo "Riesame della Direzione"</em> della piattaforma, riportando input considerati, decisioni e azioni assegnate.</li>
|
||||
<li><strong>Attuazione e monitoraggio</strong>: le azioni derivanti sono tracciate (eventualmente come schede NCR/CAPA o voci del piano di trattamento rischi) e il loro stato è verificato al riesame successivo.</li>
|
||||
</ol>
|
||||
|
||||
<h3>6. Controlli ISO/clausole collegati</h3>
|
||||
<p>cl. 9.3.1/9.3.2/9.3.3; collegamento diretto con cl. 9.2 (audit, input), cl. 6.1/8.2 (rischi, input), cl. 10.1/10.2 (miglioramento e azioni correttive, output) e cl. 5 (leadership). Controlli A.5.1 e A.5.4.</p>
|
||||
|
||||
<h3>7. Registrazioni/evidenze</h3>
|
||||
<ul>
|
||||
<li><strong>Verbale di riesame della direzione</strong> con input considerati, decisioni e azioni: modulo "Riesame della Direzione".</li>
|
||||
<li><strong>Pacchetto di input</strong> (estratti da: modulo Rischi, Audit interni, NCR/CAPA, gestione incidenti, Formazione).</li>
|
||||
<li><strong>Azioni assegnate</strong> tracciate nei moduli operativi pertinenti (NCR/CAPA o piano di trattamento).</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Il riesame della direzione si tiene <strong>almeno una volta l'anno</strong> e, in via straordinaria, a seguito di cambiamenti rilevanti (incidente grave, modifica normativa, cambio significativo di fornitore cloud o di prodotto). La presente procedura è versionata e approvata dalla Direzione secondo la Procedura di Controllo dei Documenti.</p>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--META|doc_type=procedura_nc_azioni_correttive|title=Procedura di Gestione delle Non Conformità e delle Azioni Correttive|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Gestione delle Non Conformità e delle Azioni Correttive</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Definire le modalità con cui Nuova Agile Technology srl rileva, registra, tratta e chiude le non conformità (NC) del SGSI, analizzandone le cause e attuando azioni correttive per evitarne il ripetersi, a sostegno del miglioramento continuo. La procedura attua la ISO/IEC 27001:2022 cl. 10.2 (non conformità e azioni correttive) e cl. 10.1 (miglioramento continuo).</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a tutte le non conformità comunque rilevate: rilievi di audit interni o esterni, scostamenti dai requisiti della norma o dai documenti del SGSI, incidenti di sicurezza, segnalazioni di clienti o fornitori, malfunzionamenti dei controlli cloud (Aruba IT, Hetzner DE, piattaforme AI) ed esiti di monitoraggio. Comprende le NC relative al trattamento di dati personali nel SaaS, da gestire in coordinamento con il DPO.</p>
|
||||
|
||||
<h3>3. Riferimenti</h3>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 10.1 (miglioramento continuo) e cl. 10.2 (NC e azioni correttive).</li>
|
||||
<li>Controlli A.5.24–A.5.28 (gestione degli incidenti di sicurezza, raccolta evidenze, apprendimento dagli incidenti).</li>
|
||||
<li>D.Lgs. 138/2024 art. 24 (mantenimento dell'efficacia delle misure) e art. 25 (obblighi di notifica degli incidenti significativi) [DA VERIFICARE per i soggetti rientranti].</li>
|
||||
<li>Procedura Audit Interni; Procedura Riesame della Direzione; Procedura Valutazione Rischi.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Ruoli e responsabilità</h3>
|
||||
<ul>
|
||||
<li><strong>Chiunque rilevi una NC</strong>: la segnala al RSGSI o la registra direttamente nel modulo NCR/CAPA.</li>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: registra e classifica la NC, coordina l'analisi delle cause, assegna le azioni, verifica l'efficacia e chiude la scheda.</li>
|
||||
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: attua le correzioni tecniche e le azioni correttive di propria competenza.</li>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: assicura le risorse e prende atto delle NC rilevanti in sede di riesame.</li>
|
||||
<li><strong>DPO esterno</strong>: coinvolto per le NC che riguardano dati personali, anche ai fini di eventuali obblighi di notifica.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Flusso/attività passo-passo</h3>
|
||||
<ol>
|
||||
<li><strong>Rilevazione e registrazione</strong> (input: rilievo/segnalazione/incidente; output: scheda NC aperta). La NC è aperta nel <em>modulo "Non conformità / Azioni correttive (NCR/CAPA)"</em> con descrizione, origine, data e responsabile.</li>
|
||||
<li><strong>Correzione immediata</strong> (cl. 10.2 a): si reagisce per contenere e correggere la NC e per gestirne le conseguenze (es. revoca di un accesso, ripristino di una configurazione cloud, contenimento di un incidente).</li>
|
||||
<li><strong>Valutazione della necessità di azione correttiva</strong> (cl. 10.2 b): si valuta se la NC può ripresentarsi o esistere altrove; per NC minori e isolate può bastare la correzione, per NC ricorrenti o significative si avvia un'azione correttiva.</li>
|
||||
<li><strong>Analisi delle cause</strong> (root cause): si individuano le cause profonde (es. assenza di un controllo, configurazione errata, mancanza di competenza) con tecnica adeguata (5 perché / causa-effetto).</li>
|
||||
<li><strong>Definizione e attuazione dell'azione correttiva (CAPA)</strong> (cl. 10.2 c/d): si definiscono le azioni, i responsabili e le scadenze, registrandole come azioni collegate alla scheda; si attuano.</li>
|
||||
<li><strong>Verifica dell'efficacia</strong> (cl. 10.2 d/e): il RSGSI verifica che l'azione abbia eliminato la causa e che la NC non si ripresenti; aggiorna, se necessario, i rischi e la SoA.</li>
|
||||
<li><strong>Chiusura e comunicazione</strong> (cl. 10.2 f/g): la scheda è chiusa con evidenza dell'efficacia; le NC significative sono portate al Riesame della Direzione. Per incidenti che configurano violazioni di dati o incidenti significativi NIS2, si attivano i relativi flussi di notifica.</li>
|
||||
</ol>
|
||||
|
||||
<h3>6. Controlli ISO/clausole collegati</h3>
|
||||
<p>cl. 10.2 (a–g) e cl. 10.1; A.5.24 (pianificazione e preparazione gestione incidenti), A.5.27 (apprendimento dagli incidenti), A.5.28 (raccolta evidenze). Gli esiti sono input della cl. 9.3 (riesame) e possono modificare la valutazione dei rischi (cl. 6.1).</p>
|
||||
|
||||
<h3>7. Registrazioni/evidenze</h3>
|
||||
<ul>
|
||||
<li><strong>Scheda NC</strong> (descrizione, origine, classificazione, correzione): modulo NCR/CAPA.</li>
|
||||
<li><strong>Analisi delle cause e azioni correttive</strong> (responsabili, scadenze, stato, verifica di efficacia): modulo NCR/CAPA.</li>
|
||||
<li><strong>Collegamenti</strong> a rilievi di audit (modulo Audit interni), a rischi (modulo Rischi) e a incidenti (gestione incidenti) ove pertinente.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>L'andamento delle NC e l'efficacia delle azioni correttive sono valutati <strong>almeno annualmente</strong> in sede di Riesame della Direzione e ad ogni evento significativo. La presente procedura è versionata e approvata dalla Direzione secondo la Procedura di Controllo dei Documenti.</p>
|
||||
@@ -0,0 +1,49 @@
|
||||
<!--META|doc_type=procedura_competenze_formazione|title=Procedura di Gestione delle Competenze e della Formazione|status=approved|version=1.0-->
|
||||
|
||||
<h2>Procedura di Gestione delle Competenze e della Formazione</h2>
|
||||
|
||||
<h3>1. Scopo</h3>
|
||||
<p>Assicurare che il personale di Nuova Agile Technology srl che svolge attività rilevanti per il SGSI possieda le competenze necessarie e sia consapevole del proprio ruolo nella sicurezza delle informazioni. La procedura attua la ISO/IEC 27001:2022 cl. 7.2 (competenza) e cl. 7.3 (consapevolezza).</p>
|
||||
|
||||
<h3>2. Ambito</h3>
|
||||
<p>Si applica a tutto il personale: 9 dipendenti, 2 collaboratori P.IVA e la Presidente/Direzione, nonché ai ruoli SGSI (RSGSI, Resp. IT/Sicurezza, DPO esterno). Considera le competenze specifiche per un'azienda <em>cloud-only</em>: sicurezza dei servizi cloud (Aruba IT, Hetzner DE), uso sicuro dei portatili, sviluppo sicuro dei prodotti on-premise e SaaS, protezione dei dati personali (PII) e uso responsabile delle piattaforme AI.</p>
|
||||
|
||||
<h3>3. Riferimenti</h3>
|
||||
<ul>
|
||||
<li>ISO/IEC 27001:2022 cl. 7.2 (competenza) e cl. 7.3 (consapevolezza).</li>
|
||||
<li>Controlli A.6.3 (consapevolezza, istruzione e formazione sulla sicurezza), A.6.1 (screening), A.6.2 (termini e condizioni), A.6.5 (responsabilità dopo la cessazione), A.8.28 (codifica sicura).</li>
|
||||
<li>ISO/IEC 27018 (consapevolezza del personale che tratta PII).</li>
|
||||
<li>D.Lgs. 138/2024 art. 23 (formazione degli organi di amministrazione) e art. 24 (igiene informatica e formazione del personale) [DA VERIFICARE per i soggetti rientranti].</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Ruoli e responsabilità</h3>
|
||||
<ul>
|
||||
<li><strong>Direzione (Silvia Garretto)</strong>: approva il piano formativo e ne assicura le risorse; partecipa alla formazione di governance prevista dalla NIS2.</li>
|
||||
<li><strong>RSGSI (Massimo Tagliavini)</strong>: definisce i fabbisogni di competenza per ruolo, predispone il piano formativo annuale, assegna i corsi e monitora il completamento.</li>
|
||||
<li><strong>Resp. IT/Sicurezza (Simon Fattori)</strong>: individua i fabbisogni tecnici specifici (cloud, sviluppo sicuro, gestione incidenti) e supporta la formazione tecnica.</li>
|
||||
<li><strong>Personale (dipendenti e collaboratori)</strong>: partecipa alle attività formative assegnate e mantiene aggiornata la propria consapevolezza.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Flusso/attività passo-passo</h3>
|
||||
<ol>
|
||||
<li><strong>Definizione dei fabbisogni</strong> (input: ruoli SGSI, esiti rischi/audit/incidenti; output: matrice competenze per ruolo). Per ciascun ruolo si individuano le competenze richieste (cl. 7.2 a/b) e si rileva il divario rispetto alle competenze possedute.</li>
|
||||
<li><strong>Pianificazione formativa</strong> (output: piano formativo annuale). Il RSGSI definisce corsi e attività di awareness, con priorità, destinatari e scadenze; il piano include almeno: formazione di awareness generale per tutti, formazione tecnica per IT/sviluppo, sessione di governance per la Direzione, modulo sulla protezione delle PII.</li>
|
||||
<li><strong>Assegnazione ed erogazione</strong> (input: piano; output: corsi assegnati). I corsi sono assegnati al personale tramite il <em>modulo "Formazione"</em> della piattaforma; l'erogazione può avvenire in modalità e-learning, in aula o on-the-job.</li>
|
||||
<li><strong>Consapevolezza continua</strong> (cl. 7.3): si diffondono comunicazioni periodiche su politiche, minacce attuali (es. phishing) e responsabilità individuali; al nuovo ingresso (onboarding) è prevista una sessione di awareness obbligatoria, prima dell'accesso ai sistemi.</li>
|
||||
<li><strong>Registrazione e verifica</strong> (output: evidenze di completamento). Per ogni assegnazione si registrano stato, data di completamento ed esito di eventuali test/quiz nel modulo "Formazione".</li>
|
||||
<li><strong>Valutazione dell'efficacia</strong>: il RSGSI verifica il tasso di completamento e l'adeguatezza delle competenze acquisite (es. esiti dei test, riduzione di incidenti legati a errore umano); le carenze diventano azioni di miglioramento o NC.</li>
|
||||
<li><strong>Gestione di ingressi e cessazioni</strong>: all'assunzione si definiscono i requisiti di competenza e consapevolezza; alla cessazione si rammentano gli obblighi di riservatezza residui (A.6.5).</li>
|
||||
</ol>
|
||||
|
||||
<h3>6. Controlli ISO/clausole collegati</h3>
|
||||
<p>cl. 7.2/7.3; A.6.3 (formazione e awareness), A.6.1/A.6.2/A.6.5 (ciclo di vita del rapporto), A.8.28 (codifica sicura per gli sviluppatori). La formazione è input/output del Riesame della Direzione (cl. 9.3) e misura di trattamento del rischio "errore umano" (cl. 6.1).</p>
|
||||
|
||||
<h3>7. Registrazioni/evidenze</h3>
|
||||
<ul>
|
||||
<li><strong>Matrice delle competenze per ruolo</strong> e <strong>piano formativo annuale</strong> (mantenuti dal RSGSI nella piattaforma/archivio cloud).</li>
|
||||
<li><strong>Assegnazioni e attestati di completamento</strong> (stato, date, esiti test): modulo "Formazione".</li>
|
||||
<li><strong>Evidenze di awareness di onboarding</strong> e comunicazioni periodiche.</li>
|
||||
</ul>
|
||||
|
||||
<h3>8. Riesame e versionamento</h3>
|
||||
<p>Il piano formativo è riesaminato e aggiornato <strong>almeno annualmente</strong> e ad ogni cambiamento rilevante (nuovi ruoli, nuove tecnologie/provider, nuove minacce, modifiche normative). La presente procedura è versionata e approvata dalla Direzione secondo la Procedura di Controllo dei Documenti.</p>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!--META|doc_type=procedura_gestione_accessi|title=Procedura di Gestione degli Accessi (onboarding, offboarding, MFA)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") concede, modifica, riesamina e revoca gli accessi logici alle proprie risorse informative, garantendo che ogni utente disponga esclusivamente dei privilegi necessari al proprio ruolo (principio del <em>least privilege</em>) e che ogni accesso sia protetto da <strong>autenticazione a più fattori (MFA)</strong>. La procedura copre l'intero ciclo di vita dell'identità: <em>onboarding</em>, variazioni di ruolo, riesame periodico e <em>offboarding</em>.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutti gli accessi logici a: piattaforme cloud (Aruba IT, Hetzner DE), pannelli SaaS multi-tenant erogati ai clienti, repository di codice, strumenti di collaborazione, posta elettronica, piattaforme di intelligenza artificiale tramite API, gestore di password aziendale e ai <strong>PC portatili cifrati</strong> in dotazione. Riguarda dipendenti, collaboratori esterni a P.IVA e utenze tecniche/di servizio. <strong>Non esistono server in sede</strong>: non sono previsti accessi fisici a sale macchine, la cui sicurezza è ereditata dai fornitori cloud secondo il modello di responsabilità condivisa.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – Annex A: A.5.15 (Controllo degli accessi), A.5.16 (Gestione delle identità), A.5.17 (Informazioni di autenticazione), A.5.18 (Diritti di accesso), A.8.2 (Diritti di accesso privilegiati), A.8.3 (Restrizione dell'accesso alle informazioni), A.8.5 (Autenticazione sicura).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – controlli per accessi nei servizi cloud e protezione dei dati personali.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): art. 24 (misure di gestione del rischio).</li>
|
||||
<li>Politica di Controllo degli Accessi e Manuale SGSI dell'Azienda.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva la procedura, autorizza gli accessi privilegiati critici e le deroghe</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Coordina i riesami periodici degli accessi e mantiene le evidenze</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Crea/modifica/revoca le utenze, configura l'MFA, esegue le verifiche tecniche</td></tr>
|
||||
<tr><td>Responsabile dell'unità richiedente</td><td><em>variabile</em></td><td>Richiede e giustifica gli accessi del proprio personale</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso/attività passo-passo</h2>
|
||||
<h3>5.1 Onboarding (ingresso di un nuovo utente)</h3>
|
||||
<ol>
|
||||
<li>Il responsabile invia a Simon Fattori la richiesta con ruolo, profilo di accesso richiesto e data di inizio.</li>
|
||||
<li>Simon Fattori crea l'identità sulle sole piattaforme necessarie al ruolo, applicando il <em>least privilege</em> e profili predefiniti per mansione.</li>
|
||||
<li>Si attiva obbligatoriamente l'<strong>MFA su tutte le utenze</strong> (cloud, SaaS, repository, posta, gestore password, API AI). Senza MFA l'accesso non è abilitato.</li>
|
||||
<li>Il PC portatile viene consegnato già <strong>cifrato a disco intero</strong>; le credenziali sono custodite solo nel gestore di password aziendale.</li>
|
||||
<li>L'utente sottoscrive la Politica d'uso accettabile e riceve la formazione di sicurezza di base.</li>
|
||||
</ol>
|
||||
<h3>5.2 Variazione di ruolo</h3>
|
||||
<ol>
|
||||
<li>A ogni cambio di mansione il responsabile richiede l'aggiornamento del profilo.</li>
|
||||
<li>Simon Fattori adegua i privilegi <strong>rimuovendo quelli non più pertinenti</strong> prima di aggiungere i nuovi (no accumulo di permessi).</li>
|
||||
</ol>
|
||||
<h3>5.3 Riesame periodico degli accessi</h3>
|
||||
<ol>
|
||||
<li>Con cadenza <strong>almeno semestrale</strong> Massimo Tagliavini e Simon Fattori riesaminano tutte le utenze, con particolare attenzione agli <strong>accessi privilegiati</strong> e alle utenze di servizio.</li>
|
||||
<li>Gli accessi non più giustificati vengono revocati; gli scostamenti sono registrati come <strong>non conformità</strong> nel modulo dedicato.</li>
|
||||
</ol>
|
||||
<h3>5.4 Offboarding (cessazione)</h3>
|
||||
<ol>
|
||||
<li>Alla comunicazione di cessazione, Simon Fattori <strong>disabilita tutte le utenze entro la data di fine rapporto</strong> (idealmente lo stesso giorno) e revoca chiavi API, token e accessi ai repository.</li>
|
||||
<li>Si ritira il PC portatile, se ne verifica la cifratura e si procede alla riassegnazione o cancellazione sicura.</li>
|
||||
<li>Si ruotano eventuali credenziali condivise di cui l'utente era a conoscenza.</li>
|
||||
<li>L'avvenuto offboarding è registrato come evidenza.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO + riferimenti NIS2</h2>
|
||||
<p>La procedura attua i controlli <strong>A.5.15–A.5.18</strong>, <strong>A.8.2, A.8.3, A.8.5</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Risponde inoltre alle <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in materia di controllo degli accessi e uso dell'autenticazione a più fattori. Le evidenze sono richiamabili anche per gli obblighi di supply chain verso i clienti NIS2.</p>
|
||||
|
||||
<h2>7. Registrazioni/evidenze</h2>
|
||||
<ul>
|
||||
<li>Richieste di accesso e relative autorizzazioni;</li>
|
||||
<li>Report dei riesami periodici degli accessi;</li>
|
||||
<li>Registrazioni di onboarding/offboarding e di ritiro dei dispositivi;</li>
|
||||
<li>Configurazione MFA e log di accesso conservati dalle piattaforme cloud.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di cambiamenti significativi delle piattaforme o dei ruoli. Versione 1.0.</p>
|
||||
@@ -0,0 +1,56 @@
|
||||
<!--META|doc_type=procedura_incident|title=Procedura di Gestione degli Incidenti e Notifica NIS2|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") rileva, classifica, gestisce e — quando dovuto — notifica gli incidenti di sicurezza delle informazioni, assicurando una risposta tempestiva, la riduzione degli impatti e il rispetto degli obblighi di notifica previsti dalla normativa NIS2. La procedura disciplina anche la <strong>comunicazione ai clienti impattati</strong> nel ruolo dell'Azienda quale fornitore di soggetti NIS2.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a tutti gli incidenti che riguardano la riservatezza, l'integrità o la disponibilità delle informazioni e dei servizi dell'Azienda: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), <strong>servizi SaaS</strong> multi-tenant, <strong>endpoint</strong> (PC portatili cifrati), <strong>piattaforme di intelligenza artificiale</strong> usate via API e i prodotti in licenza on-premise per la parte di responsabilità dell'Azienda. Non riguarda sale server in sede, <strong>assenti</strong> per l'architettura cloud-only.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.5.24 (Pianificazione e preparazione della gestione degli incidenti), A.5.25 (Valutazione e decisione sugli eventi), A.5.26 (Risposta agli incidenti), A.5.27 (Apprendimento dagli incidenti), A.5.28 (Raccolta delle evidenze), A.5.7 (Threat intelligence).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione degli incidenti nei servizi cloud e sui dati personali.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (recepimento Direttiva (UE) 2022/2555 – NIS2): <strong>art. 23 (governance)</strong>, <strong>art. 24 (misure di gestione del rischio)</strong>, <strong>art. 25 (obblighi di notifica degli incidenti)</strong>.</li>
|
||||
<li><strong>Determinazione ACN n. 164179/2025</strong> e Determinazioni vigenti per i criteri di significatività, la tassonomia e le modalità di notifica al CSIRT Italia tramite la piattaforma ACN.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Decide la notifica alle Autorità e la comunicazione esterna; informa gli organi di gestione (art. 23)</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Coordina la gestione dell'incidente, cura le registrazioni e la relazione finale</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Contiene, eradica e ripristina; raccoglie le evidenze tecniche</td></tr>
|
||||
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta se vi è violazione di dati personali e gli obblighi GDPR verso il Garante</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso/attività passo-passo</h2>
|
||||
<ol>
|
||||
<li><strong>Rilevazione e segnalazione.</strong> Chiunque rilevi un evento sospetto lo segnala immediatamente a Simon Fattori. L'evento è registrato nel <strong>modulo "Incidenti"</strong> della piattaforma con data e ora di conoscenza.</li>
|
||||
<li><strong>Triage e classificazione.</strong> Simon Fattori, con Massimo Tagliavini, valuta natura, origine e impatto (riservatezza/integrità/disponibilità) e assegna una severità. Si verifica se l'incidente è <strong>"significativo"</strong> secondo i criteri di legge e delle Determinazioni ACN (es. impatto operativo grave, numero di utenti coinvolti, durata, danno economico/reputazionale, effetti transfrontalieri). In caso di dubbio: <strong>[DA VERIFICARE]</strong> con la Direzione e il supporto consulenziale.</li>
|
||||
<li><strong>Contenimento, eradicazione, ripristino.</strong> Simon Fattori isola i sistemi/account compromessi, rimuove la causa e ripristina il servizio anche tramite i backup (vedi Procedura di Backup e Ripristino), tracciando i tempi delle fasi.</li>
|
||||
<li><strong>Notifica al CSIRT Italia (ACN)</strong> – se l'incidente è significativo, secondo le tempistiche dell'<strong>art. 25 del D.Lgs. 138/2024</strong>:
|
||||
<ul>
|
||||
<li><strong>Pre-allarme (early warning) entro 24 ore</strong> dalla conoscenza dell'incidente significativo;</li>
|
||||
<li><strong>Notifica completa entro 72 ore</strong> dalla conoscenza;</li>
|
||||
<li><strong>Relazione finale entro 1 mese</strong> dalla notifica completa (e relazioni intermedie se richieste).</li>
|
||||
</ul>
|
||||
La notifica avviene tramite la piattaforma ACN; decide e autorizza la Direzione.</li>
|
||||
<li><strong>Valutazione dati personali.</strong> Se l'incidente comporta una violazione di dati personali, il DPO valuta gli obblighi GDPR (eventuale notifica al Garante entro 72 ore e comunicazione agli interessati).</li>
|
||||
<li><strong>Comunicazione ai clienti impattati.</strong> Nel ruolo di <strong>fornitore di clienti NIS2</strong>, l'Azienda informa <strong>tempestivamente</strong> i clienti i cui servizi/dati sono coinvolti, secondo gli <strong>obblighi contrattuali di supply chain</strong>, fornendo gli elementi utili ai loro adempimenti di notifica.</li>
|
||||
<li><strong>Chiusura e lezioni apprese.</strong> A risoluzione, si redige la relazione finale, si aprono eventuali <strong>non conformità</strong> e azioni correttive nel modulo "Non conformità" e si aggiorna il <strong>modulo "Rischi"</strong> se emergono nuovi rischi.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO + riferimenti NIS2 (tempistiche)</h2>
|
||||
<p>La procedura attua i controlli <strong>A.5.24–A.5.28</strong> e <strong>A.5.7</strong> di ISO/IEC 27001:2022, integrati dai controlli cloud di ISO/IEC 27017/27018. Sul piano normativo dà attuazione agli <strong>artt. 23, 24 e 25 del D.Lgs. 138/2024</strong>. Le <strong>tempistiche esatte di notifica</strong> sono: <strong>early warning 24 ore</strong>, <strong>notifica completa 72 ore</strong>, <strong>relazione finale 1 mese</strong>. I criteri di significatività seguono la <strong>Determinazione ACN n. 164179/2025</strong> e le Determinazioni vigenti.</p>
|
||||
|
||||
<h2>7. Registrazioni/evidenze</h2>
|
||||
<ul>
|
||||
<li>Scheda incidente nel modulo "Incidenti" (cronologia, severità, classificazione);</li>
|
||||
<li>Evidenze tecniche raccolte (log, immagini, comunicazioni), preservate ai sensi di A.5.28;</li>
|
||||
<li>Ricevute di notifica al CSIRT/ACN e relazione finale;</li>
|
||||
<li>Comunicazioni ai clienti e, se del caso, al DPO/Garante;</li>
|
||||
<li>Non conformità e azioni correttive collegate.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale, dopo ogni incidente significativo o a fronte di aggiornamenti delle Determinazioni ACN. Versione 1.0.</p>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!--META|doc_type=procedura_backup_ripristino|title=Procedura di Backup e Ripristino|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") esegue, protegge, verifica e ripristina le copie di sicurezza (backup) delle informazioni e dei servizi critici, al fine di garantire la <strong>disponibilità e l'integrità</strong> dei dati e la capacità di ripristino in caso di guasto, errore, cancellazione accidentale o attacco (es. ransomware).</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica ai dati e ai servizi gestiti in cloud: <strong>database e applicativi SaaS</strong> multi-tenant, codice sorgente e configurazioni dei prodotti, dati dei clienti trattati nel SaaS, posta e documenti aziendali. Le copie risiedono <strong>su cloud</strong> (Aruba IT, Hetzner DE). Gli <strong>endpoint</strong> (PC portatili cifrati) non conservano dati critici come unica copia: i dati di lavoro risiedono nei servizi cloud. <strong>Non esistono backup su nastro o server in sede.</strong></p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.8.13 (Backup delle informazioni), A.8.14 (Ridondanza delle strutture di elaborazione), A.5.30 (Pronto intervento ICT per la continuità operativa), A.8.24 (Uso della crittografia).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – backup e protezione dei dati personali nei servizi cloud.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, incluse continuità operativa e backup).</li>
|
||||
<li>Procedura di Continuità Operativa e Disaster Recovery (BCP/DR) dell'Azienda.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva la strategia di backup e i livelli di servizio (RPO/RTO)</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Verifica che i test di ripristino siano eseguiti e documentati</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Configura ed esegue i backup, ne monitora l'esito, esegue i ripristini e i test</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso/attività passo-passo</h2>
|
||||
<h3>5.1 Pianificazione</h3>
|
||||
<ol>
|
||||
<li>Per ciascun servizio critico si definiscono <strong>RPO</strong> (massima perdita di dati tollerata) e <strong>RTO</strong> (tempo massimo di ripristino), approvati dalla Direzione.</li>
|
||||
<li>Si adotta lo schema di riferimento <strong>3-2-1</strong> per quanto applicabile in cloud: più copie, su servizi/regioni distinti, con almeno una copia logicamente separata e protetta da modifiche.</li>
|
||||
</ol>
|
||||
<h3>5.2 Esecuzione</h3>
|
||||
<ol>
|
||||
<li>I backup dei database SaaS e dei dati critici sono <strong>automatici e schedulati</strong> (frequenza coerente con l'RPO, di norma giornaliera).</li>
|
||||
<li>Le copie sono <strong>cifrate</strong> a riposo (A.8.24) e conservate in cloud con accesso ristretto e <strong>MFA</strong>.</li>
|
||||
<li>Ove disponibile, si attiva l'<strong>immutabilità</strong> delle copie (protezione anti-ransomware) e la separazione delle credenziali di gestione dei backup.</li>
|
||||
<li>Si applica una <strong>politica di retention</strong> definita (es. copie giornaliere a breve termine e copie a più lunga conservazione), nel rispetto degli obblighi sui dati personali (minimizzazione e cancellazione, A.8.13/27018).</li>
|
||||
</ol>
|
||||
<h3>5.3 Monitoraggio</h3>
|
||||
<ol>
|
||||
<li>Simon Fattori verifica l'<strong>esito di ogni job</strong> di backup; i fallimenti generano un alert e una verifica entro le 24 ore lavorative successive.</li>
|
||||
<li>Gli esiti negativi ricorrenti sono registrati come <strong>non conformità</strong>.</li>
|
||||
</ol>
|
||||
<h3>5.4 Test di ripristino</h3>
|
||||
<ol>
|
||||
<li>Con cadenza <strong>almeno trimestrale</strong> si esegue un <strong>test di ripristino</strong> su un dato/servizio campione, verificando integrità e tempi rispetto a RTO/RPO.</li>
|
||||
<li>L'esito è documentato; eventuali scostamenti attivano azioni correttive.</li>
|
||||
</ol>
|
||||
<h3>5.5 Ripristino reale</h3>
|
||||
<ol>
|
||||
<li>In caso di incidente, il ripristino è autorizzato e coordinato secondo la Procedura BCP/DR; si privilegia una copia integra e verificata; al termine si validano i dati ripristinati.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO + riferimenti NIS2</h2>
|
||||
<p>La procedura attua i controlli <strong>A.8.13, A.8.14, A.5.30, A.8.24</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di backup e continuità. Le evidenze dei test di ripristino sono utili anche a dimostrare resilienza ai clienti NIS2 nell'ambito della supply chain.</p>
|
||||
|
||||
<h2>7. Registrazioni/evidenze</h2>
|
||||
<ul>
|
||||
<li>Piano di backup con RPO/RTO per servizio;</li>
|
||||
<li>Log/report di esecuzione e di esito dei job;</li>
|
||||
<li>Rapporti dei test di ripristino trimestrali;</li>
|
||||
<li>Registro delle non conformità su fallimenti di backup/ripristino.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di modifiche dei servizi cloud o dei requisiti RPO/RTO. Versione 1.0.</p>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--META|doc_type=procedura_change_management|title=Procedura di Gestione dei Cambiamenti|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") richiede, valuta, autorizza, attua e verifica i cambiamenti che possono influire sulla sicurezza, la disponibilità e l'integrità dei propri sistemi, servizi e configurazioni, in modo controllato e tracciabile, riducendo il rischio di indisponibilità o di introduzione di vulnerabilità.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica ai cambiamenti su: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), configurazioni di rete e di sicurezza, <strong>servizi SaaS</strong> multi-tenant, rilasci di nuove versioni dei <strong>prodotti software</strong> (SaaS e on-premise presso i clienti), integrazioni con <strong>piattaforme AI</strong> via API, configurazioni degli <strong>endpoint</strong> (PC portatili cifrati) e modifiche alle policy/strumenti di sicurezza. Sono esclusi i cambiamenti puramente di contenuto privi di impatto su sicurezza o servizio.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.8.32 (Gestione dei cambiamenti), A.8.31 (Separazione degli ambienti di sviluppo, test ed esercizio), A.8.28 (Codifica sicura), A.8.25 (Ciclo di vita di sviluppo sicuro), A.8.29 (Test di sicurezza nello sviluppo e nell'accettazione).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione dei cambiamenti nei servizi cloud.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, incluse sicurezza nello sviluppo e gestione delle modifiche).</li>
|
||||
<li>Procedura di Gestione delle Vulnerabilità e delle Patch dell'Azienda.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Autorizza i cambiamenti ad alto impatto e le finestre di rilascio critiche</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Verifica che i cambiamenti rilevanti siano valutati nel rischio e documentati</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Valuta tecnicamente, attua il cambiamento, esegue i test e il piano di rollback</td></tr>
|
||||
<tr><td>Richiedente</td><td><em>variabile</em></td><td>Apre la richiesta di cambiamento descrivendo finalità e impatto atteso</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso/attività passo-passo</h2>
|
||||
<ol>
|
||||
<li><strong>Richiesta (RFC).</strong> Il richiedente apre una richiesta di cambiamento descrivendo obiettivo, sistemi coinvolti, impatto atteso e urgenza.</li>
|
||||
<li><strong>Valutazione di impatto e rischio.</strong> Simon Fattori classifica il cambiamento (standard a basso rischio / normale / emergenza) e ne valuta gli effetti su sicurezza, dati personali e continuità. I cambiamenti rilevanti sono registrati nel <strong>modulo "Rischi"</strong> ove pertinente.</li>
|
||||
<li><strong>Autorizzazione.</strong> I cambiamenti standard a basso rischio sono approvati da Simon Fattori; quelli normali ad alto impatto richiedono l'autorizzazione della Direzione. Va sempre definito un <strong>piano di rollback</strong>.</li>
|
||||
<li><strong>Test in ambiente separato.</strong> Le modifiche software sono validate in ambienti di <strong>sviluppo/test separati</strong> dall'esercizio (A.8.31), con test funzionali e di sicurezza (A.8.29) prima del rilascio.</li>
|
||||
<li><strong>Attuazione.</strong> Il cambiamento è applicato in una finestra concordata; per il SaaS si privilegiano rilasci controllati. Per i <strong>prodotti on-premise</strong>, l'Azienda fornisce ai clienti note di rilascio e indicazioni di aggiornamento, nel rispetto degli obblighi contrattuali di supply chain.</li>
|
||||
<li><strong>Verifica post-cambiamento.</strong> Si verifica il corretto funzionamento e l'assenza di effetti collaterali; in caso di esito negativo si attiva il rollback.</li>
|
||||
<li><strong>Chiusura.</strong> Esito e documentazione sono registrati; eventuali anomalie alimentano <strong>non conformità</strong> e azioni correttive.</li>
|
||||
</ol>
|
||||
<h3>5.1 Cambiamenti di emergenza</h3>
|
||||
<p>Per cambiamenti urgenti (es. mitigazione di una vulnerabilità critica), l'attuazione può precedere l'autorizzazione formale, ma <strong>deve essere documentata a posteriori entro il giorno lavorativo successivo</strong> e ratificata dalla Direzione.</p>
|
||||
|
||||
<h2>6. Controlli ISO + riferimenti NIS2</h2>
|
||||
<p>La procedura attua i controlli <strong>A.8.32, A.8.31, A.8.25, A.8.28, A.8.29</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di sicurezza dello sviluppo e gestione delle modifiche, contribuendo a prevenire incidenti che ricadrebbero negli obblighi di notifica (art. 25).</p>
|
||||
|
||||
<h2>7. Registrazioni/evidenze</h2>
|
||||
<ul>
|
||||
<li>Richieste di cambiamento (RFC) e relative autorizzazioni;</li>
|
||||
<li>Esiti dei test e dei rilasci; note di rilascio per i clienti on-premise;</li>
|
||||
<li>Documentazione dei cambiamenti di emergenza e relativa ratifica;</li>
|
||||
<li>Non conformità collegate a cambiamenti falliti.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di modifiche del processo di rilascio. Versione 1.0.</p>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!--META|doc_type=procedura_vulnerabilita_patch|title=Procedura di Gestione delle Vulnerabilità e delle Patch|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") identifica, valuta, prioritizza e rimedia le vulnerabilità tecniche dei propri sistemi, applicazioni e componenti software, garantendo l'applicazione tempestiva delle patch di sicurezza e la riduzione della superficie di attacco.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica a: ambienti <strong>cloud</strong> (Aruba IT, Hetzner DE), sistemi operativi e middleware dei servizi <strong>SaaS</strong>, <strong>dipendenze e librerie</strong> di terze parti dei prodotti software (SaaS e on-premise), integrazioni con <strong>piattaforme AI</strong> via API, configurazioni di sicurezza e <strong>endpoint</strong> (PC portatili cifrati). Per i prodotti <strong>on-premise installati presso i clienti</strong>, l'ambito copre la produzione e distribuzione delle patch ai clienti.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.8.8 (Gestione delle vulnerabilità tecniche), A.8.7 (Protezione dai malware), A.8.9 (Gestione della configurazione), A.5.7 (Threat intelligence), A.8.16 (Attività di monitoraggio).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – gestione delle vulnerabilità nei servizi cloud.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, incluse sicurezza nell'acquisizione/sviluppo/manutenzione e divulgazione delle vulnerabilità).</li>
|
||||
<li>Procedura di Gestione dei Cambiamenti e Procedura di Gestione degli Incidenti dell'Azienda.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Approva le tempistiche di rimedio e autorizza eventuali deroghe motivate</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Verifica il rispetto dei tempi di rimedio e registra le non conformità</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Raccoglie le fonti, valuta e prioritizza le vulnerabilità, applica le patch e i test</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso/attività passo-passo</h2>
|
||||
<h3>5.1 Identificazione</h3>
|
||||
<ol>
|
||||
<li>Simon Fattori monitora con continuità le fonti di vulnerabilità: bollettini dei fornitori cloud, avvisi CSIRT/ACN, advisory delle librerie/dipendenze usate dai prodotti, scansioni periodiche e avvisi automatici delle piattaforme (A.5.7, A.8.16).</li>
|
||||
<li>Si mantiene un inventario aggiornato dei componenti software e delle dipendenze per correlare rapidamente le vulnerabilità ai sistemi interessati.</li>
|
||||
</ol>
|
||||
<h3>5.2 Valutazione e prioritizzazione</h3>
|
||||
<ol>
|
||||
<li>Ogni vulnerabilità è valutata per <strong>gravità</strong> (es. punteggio CVSS), <strong>esposizione</strong> e <strong>impatto</strong> sui dati e sui servizi, e registrata se rilevante nel <strong>modulo "Rischi"</strong>.</li>
|
||||
<li>Si assegna una priorità con relativi <strong>tempi target di rimedio</strong>, ad esempio: critiche <strong>entro 48–72 ore</strong>, alte <strong>entro 7 giorni</strong>, medie <strong>entro 30 giorni</strong>, basse alla successiva finestra pianificata. <em>[I valori esatti sono da confermare nel piano di trattamento del rischio.]</em></li>
|
||||
</ol>
|
||||
<h3>5.3 Rimedio</h3>
|
||||
<ol>
|
||||
<li>L'applicazione delle patch segue la <strong>Procedura di Gestione dei Cambiamenti</strong> (test in ambiente separato e piano di rollback); per le vulnerabilità critiche si attiva il percorso di <strong>cambiamento di emergenza</strong>.</li>
|
||||
<li>Dove la patch non è immediatamente disponibile, si adottano <strong>misure compensative</strong> (es. restrizione accessi, isolamento, disattivazione della funzione vulnerabile).</li>
|
||||
<li>Per i <strong>prodotti on-premise</strong>, l'Azienda rilascia la patch e <strong>informa tempestivamente i clienti impattati</strong> con le istruzioni di aggiornamento, in adempimento agli obblighi contrattuali di supply chain.</li>
|
||||
</ol>
|
||||
<h3>5.4 Verifica e chiusura</h3>
|
||||
<ol>
|
||||
<li>Si verifica l'effettiva risoluzione (riscansione/test) e si chiude la vulnerabilità.</li>
|
||||
<li>Gli scostamenti dai tempi target sono registrati come <strong>non conformità</strong> con azione correttiva.</li>
|
||||
<li>Se una vulnerabilità è stata sfruttata, si attiva la <strong>Procedura di Gestione degli Incidenti</strong>.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO + riferimenti NIS2</h2>
|
||||
<p>La procedura attua i controlli <strong>A.8.8, A.8.7, A.8.9, A.5.7, A.8.16</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di gestione e divulgazione delle vulnerabilità. Una vulnerabilità sfruttata che causi un incidente significativo attiva gli obblighi di notifica dell'<strong>art. 25</strong> (early warning 24 ore, notifica completa 72 ore, relazione finale 1 mese).</p>
|
||||
|
||||
<h2>7. Registrazioni/evidenze</h2>
|
||||
<ul>
|
||||
<li>Registro delle vulnerabilità con gravità, priorità e tempi di rimedio;</li>
|
||||
<li>Report delle scansioni e degli aggiornamenti applicati;</li>
|
||||
<li>Comunicazioni di patch ai clienti on-premise;</li>
|
||||
<li>Non conformità e azioni correttive per i ritardi di rimedio.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale o a fronte di modifiche degli strumenti di scansione o del parco software. Versione 1.0.</p>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!--META|doc_type=procedura_continuita_dr|title=Procedura di Continuità Operativa e Disaster Recovery (BCP/DR)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>La presente procedura definisce le modalità con cui <strong>Nuova Agile Technology srl</strong> (di seguito "l'Azienda") garantisce la <strong>continuità operativa</strong> dei propri servizi critici e il <strong>ripristino in caso di disastro</strong> (Disaster Recovery), al fine di limitare l'impatto di eventi gravi sulla disponibilità dei servizi, sui dati dei clienti e sull'operatività aziendale, nel rispetto degli obiettivi di tempo (RTO) e di perdita dati (RPO) concordati.</p>
|
||||
|
||||
<h2>2. Ambito</h2>
|
||||
<p>Si applica ai <strong>servizi SaaS</strong> multi-tenant erogati in cloud, ai dati dei clienti trattati nel SaaS, ai sistemi di sviluppo e rilascio dei <strong>prodotti software</strong>, agli strumenti di collaborazione e alla capacità di lavoro del team (interamente su <strong>PC portatili cifrati</strong>, da remoto). L'infrastruttura è <strong>interamente cloud</strong> (Aruba IT, Hetzner DE): gli scenari considerati riguardano indisponibilità di un provider o di una regione cloud, attacchi (es. ransomware), errori gravi e indisponibilità del personale. <strong>Non sono considerati scenari di sala server in sede</strong>, assente per architettura.</p>
|
||||
|
||||
<h2>3. Riferimenti</h2>
|
||||
<ul>
|
||||
<li><strong>ISO/IEC 27001:2022</strong> – A.5.29 (Sicurezza delle informazioni durante un'interruzione), A.5.30 (Pronto intervento ICT per la continuità operativa), A.8.13 (Backup), A.8.14 (Ridondanza delle strutture di elaborazione).</li>
|
||||
<li><strong>ISO/IEC 27017:2015</strong> e <strong>27018:2019</strong> – continuità e protezione dei dati personali nei servizi cloud.</li>
|
||||
<li><strong>D.Lgs. 4 settembre 2024, n. 138</strong> (NIS2): art. 24 (misure di gestione del rischio, inclusi continuità operativa, gestione dei backup e ripristino in caso di disastro).</li>
|
||||
<li>Procedura di Backup e Ripristino e Procedura di Gestione degli Incidenti dell'Azienda.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Ruoli e responsabilità</h2>
|
||||
<table>
|
||||
<tr><th>Ruolo</th><th>Persona</th><th>Responsabilità</th></tr>
|
||||
<tr><td>Direzione / Presidente</td><td><strong>Silvia Garretto</strong></td><td>Attiva lo stato di crisi, decide la comunicazione esterna e l'allocazione delle risorse</td></tr>
|
||||
<tr><td>Responsabile SGSI</td><td><strong>Massimo Tagliavini</strong></td><td>Mantiene il piano BCP/DR, coordina i test e documenta gli esiti</td></tr>
|
||||
<tr><td>Responsabile IT/Sicurezza</td><td><strong>Simon Fattori</strong></td><td>Esegue il ripristino tecnico dei servizi e dei dati secondo il piano</td></tr>
|
||||
<tr><td>DPO esterno</td><td><em>Consulente esterno</em></td><td>Valuta gli impatti sui dati personali in scenari di indisponibilità/perdita</td></tr>
|
||||
</table>
|
||||
|
||||
<h2>5. Flusso/attività passo-passo</h2>
|
||||
<h3>5.1 Analisi di impatto e obiettivi</h3>
|
||||
<ol>
|
||||
<li>Si individuano i <strong>servizi critici</strong> (in primis il SaaS) tramite una Business Impact Analysis e si definiscono <strong>RTO</strong> e <strong>RPO</strong> per ciascuno, approvati dalla Direzione.</li>
|
||||
<li>Si identificano le dipendenze critiche: provider cloud, provider AI, DNS, posta, gestore password.</li>
|
||||
</ol>
|
||||
<h3>5.2 Strategie di continuità</h3>
|
||||
<ol>
|
||||
<li>Si privilegiano architetture <strong>ridondate</strong> e la possibilità di ripristino su una <strong>regione/fornitore alternativo</strong> ove fattibile (A.8.14), sfruttando i backup cloud cifrati e, ove disponibile, immutabili.</li>
|
||||
<li>Essendo il team interamente remoto su portatili cifrati, la <strong>continuità del lavoro</strong> è intrinsecamente resiliente a indisponibilità di una singola sede.</li>
|
||||
</ol>
|
||||
<h3>5.3 Attivazione e gestione della crisi</h3>
|
||||
<ol>
|
||||
<li>Al verificarsi di un evento grave, la Direzione (o suo delegato) <strong>attiva il piano</strong> e nomina i referenti.</li>
|
||||
<li>Si attiva in parallelo la <strong>Procedura di Gestione degli Incidenti</strong>; se l'evento configura un incidente significativo, si applicano gli obblighi di <strong>notifica al CSIRT Italia (ACN)</strong>.</li>
|
||||
<li>Simon Fattori esegue il ripristino dei servizi e dei dati dalle copie integre (Procedura di Backup e Ripristino), rispettando RTO/RPO.</li>
|
||||
</ol>
|
||||
<h3>5.4 Comunicazione</h3>
|
||||
<ol>
|
||||
<li>Si comunica <strong>tempestivamente ai clienti impattati</strong> lo stato del disservizio e i tempi stimati di ripristino, in adempimento agli obblighi contrattuali di supply chain verso i clienti NIS2.</li>
|
||||
</ol>
|
||||
<h3>5.5 Ritorno alla normalità e test</h3>
|
||||
<ol>
|
||||
<li>Verificato il pieno ripristino, si dichiara la chiusura della crisi e si redige il rapporto di evento con lezioni apprese.</li>
|
||||
<li>Il piano BCP/DR è <strong>testato almeno una volta l'anno</strong> (esercitazione/simulazione); gli esiti aggiornano il piano e alimentano azioni correttive.</li>
|
||||
</ol>
|
||||
|
||||
<h2>6. Controlli ISO + riferimenti NIS2 (tempistiche)</h2>
|
||||
<p>La procedura attua i controlli <strong>A.5.29, A.5.30, A.8.13, A.8.14</strong> di ISO/IEC 27001:2022 e i controlli cloud di ISO/IEC 27017/27018. Soddisfa le <strong>misure di gestione del rischio dell'art. 24 del D.Lgs. 138/2024</strong> in tema di continuità operativa e ripristino. Quando l'interruzione configura un incidente significativo, valgono le tempistiche di notifica dell'<strong>art. 25</strong>: <strong>early warning entro 24 ore</strong>, <strong>notifica completa entro 72 ore</strong>, <strong>relazione finale entro 1 mese</strong>.</p>
|
||||
|
||||
<h2>7. Registrazioni/evidenze</h2>
|
||||
<ul>
|
||||
<li>Piano BCP/DR con BIA, RTO/RPO e dipendenze critiche;</li>
|
||||
<li>Rapporti delle esercitazioni/test annuali;</li>
|
||||
<li>Rapporti degli eventi reali con lezioni apprese;</li>
|
||||
<li>Comunicazioni ai clienti durante i disservizi e non conformità collegate.</li>
|
||||
</ul>
|
||||
|
||||
<h2>8. Riesame e versionamento</h2>
|
||||
<p>Procedura approvata dalla Presidente. Revisione almeno annuale, dopo ogni test o evento significativo o a fronte di cambiamenti dell'architettura cloud. Versione 1.0.</p>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!--META|doc_type=istruzione_hardening_laptop|title=Istruzione Operativa: Configurazione Sicura dei PC Portatili (Hardening)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire i passi minimi e obbligatori per configurare in modo sicuro ogni PC portatile aziendale di Nuova Agile Technology srl, indipendentemente dal sistema operativo (Windows, macOS o Linux). Poiché l'azienda non ha server in sede e lavora interamente in cloud, il portatile è il principale punto di esposizione: va trattato come un asset critico.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>A tutti i dipendenti e collaboratori esterni (P.IVA) che usano un portatile, aziendale o personale, per accedere a servizi, codice o dati aziendali e dei clienti.</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>Portatile assegnato e censito nell'inventario asset (vedi Resp. IT).</li>
|
||||
<li>Account utente personale, password manager aziendale e MFA già attivati.</li>
|
||||
<li>Permessi di amministratore locale solo se autorizzati dal Resp. IT.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi</h2>
|
||||
<ol>
|
||||
<li><strong>Cifratura del disco (obbligatoria).</strong> Attiva la cifratura dell'intero disco prima di salvare qualsiasi dato: Windows = BitLocker (TPM + PIN); macOS = FileVault; Linux = LUKS. Conserva la chiave di recupero nel password manager aziendale, mai su un foglio o file in chiaro.</li>
|
||||
<li><strong>Account e privilegi.</strong> Usa un account standard per il lavoro quotidiano. Non navigare né leggere email da un account amministratore. Rinomina o disabilita gli account ospite/predefiniti.</li>
|
||||
<li><strong>Blocco schermo.</strong> Imposta il blocco automatico dopo massimo 5 minuti di inattività e richiedi password/biometria allo sblocco. Blocca sempre manualmente (Win+L / Ctrl+Cmd+Q) lasciando la postazione.</li>
|
||||
<li><strong>Schermata di avvio e firmware.</strong> Imposta una password al firmware/UEFI dove possibile e abilita Secure Boot. Disabilita l'avvio da USB se non necessario.</li>
|
||||
<li><strong>Firewall e servizi.</strong> Attiva il firewall locale del sistema operativo. Disattiva condivisione file/stampanti, desktop remoto e servizi non usati.</li>
|
||||
<li><strong>Antimalware.</strong> Mantieni attivo l'antimalware (Microsoft Defender o soluzione aziendale) con protezione in tempo reale e aggiornamenti automatici (vedi istruzione dedicata).</li>
|
||||
<li><strong>Aggiornamenti.</strong> Abilita gli aggiornamenti automatici di sistema e applicazioni (vedi Istruzione Gestione Patch).</li>
|
||||
<li><strong>Backup.</strong> Salva il lavoro su repository e storage cloud aziendali, non solo in locale. Verifica che il codice sia versionato (git) e i documenti sui drive aziendali.</li>
|
||||
<li><strong>Software.</strong> Installa solo software da fonti ufficiali e necessario al lavoro. Rimuovi applicazioni inutili e plugin/estensioni browser non indispensabili.</li>
|
||||
<li><strong>Rete.</strong> Su reti Wi-Fi pubbliche usa la connessione solo tramite VPN aziendale (se fornita) o hotspot personale. Non disattivare il firewall per "far funzionare" un servizio.</li>
|
||||
<li><strong>MDM.</strong> Se è presente un sistema di gestione dispositivi (MDM), non rimuovere l'agente né eludere le policy applicate.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Disco risulta cifrato (BitLocker/FileVault/LUKS = ON) e chiave di recupero presente nel password manager.</li>
|
||||
<li>Blocco schermo automatico ≤ 5 minuti verificato lasciando il PC inattivo.</li>
|
||||
<li>Firewall e antimalware attivi; ultimo aggiornamento entro 7 giorni.</li>
|
||||
<li>Nessun account amministratore usato per attività quotidiane.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Se non riesci ad attivare la cifratura, perdi la chiave di recupero, sospetti software malevolo o il PC viene smarrito/rubato, contatta <strong>immediatamente</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong>. In caso di furto o smarrimento, segnala entro poche ore: potrebbe attivare un incidente di sicurezza.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.8.1 (dispositivi endpoint dell'utente), A.8.7 (protezione dai malware), A.5.17 (informazioni di autenticazione). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per igiene informatica di base e sicurezza nell'uso dei sistemi.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale o a ogni cambiamento rilevante del parco dispositivi.</p>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!--META|doc_type=istruzione_mfa|title=Istruzione Operativa: Autenticazione a Più Fattori (MFA)|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Garantire che ogni accesso ai servizi cloud e agli strumenti aziendali di Nuova Agile Technology srl sia protetto da autenticazione a più fattori (MFA), riducendo il rischio di compromissione anche in caso di password rubata. La sola password non è mai sufficiente.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>A tutti i dipendenti e collaboratori esterni che accedono a posta, repository di codice, console cloud (Aruba, Hetzner), piattaforme AI, password manager e gestore segreti.</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>Smartphone aziendale o personale con app di autenticazione (es. Microsoft Authenticator, Google Authenticator, Aegis) installata.</li>
|
||||
<li>Password manager aziendale attivo per conservare codici di recupero.</li>
|
||||
<li>Account già creato sui servizi da proteggere.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi</h2>
|
||||
<ol>
|
||||
<li><strong>Scegli il secondo fattore corretto.</strong> Ordine di preferenza: (1) chiave di sicurezza FIDO2/passkey, (2) app di autenticazione (codici TOTP o notifiche push), (3) come ultima scelta SMS. <strong>Evita gli SMS</strong> dove esiste un'alternativa.</li>
|
||||
<li><strong>Attiva l'MFA su ogni servizio critico.</strong> Vai nelle impostazioni di sicurezza dell'account → "Autenticazione a due fattori / MFA" → abilita. Procedi servizio per servizio: posta, repository (es. GitHub/Gitea), console Aruba e Hetzner, piattaforme AI, password manager.</li>
|
||||
<li><strong>Registra il dispositivo.</strong> Inquadra il QR code con l'app di autenticazione o registra la chiave FIDO2. Conferma inserendo il primo codice generato.</li>
|
||||
<li><strong>Salva i codici di recupero.</strong> Ogni servizio genera codici di backup monouso: salvali <strong>solo</strong> nel password manager aziendale, mai in chiaro su file, email o foglietti.</li>
|
||||
<li><strong>Imposta un secondo metodo.</strong> Dove possibile registra un secondo fattore (es. una seconda passkey o una chiave fisica di backup) per non restare bloccato se perdi lo smartphone.</li>
|
||||
<li><strong>Proteggi il password manager.</strong> La master password deve essere lunga, unica e mai riutilizzata, e l'accesso al password manager deve avere MFA attivo.</li>
|
||||
<li><strong>Account condivisi/segreti.</strong> Non condividere account personali. Le credenziali di servizio e i segreti applicativi vanno nel gestore segreti aziendale, non scambiati via chat o email.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Effettua un login di prova: deve essere richiesto il secondo fattore.</li>
|
||||
<li>Verifica che i codici di recupero siano presenti nel password manager per ogni servizio.</li>
|
||||
<li>Conferma che almeno due metodi MFA siano registrati sui servizi più critici (posta e console cloud).</li>
|
||||
<li>Nessun servizio critico risulta accessibile con la sola password.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Se perdi lo smartphone, non ricevi i codici, esaurisci i codici di recupero o sospetti un accesso non autorizzato, contatta <strong>subito</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong> per il reset controllato. Non disattivare l'MFA per "comodità": è vietato.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.5.17 (informazioni di autenticazione), A.8.5 (autenticazione sicura). Coerente con NIS2 (D.Lgs. 138/2024, art. 24): uso di autenticazione a più fattori e soluzioni di autenticazione sicura tra le misure di gestione del rischio.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale o all'introduzione di nuovi servizi.</p>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--META|doc_type=istruzione_onboarding_offboarding|title=Istruzione Operativa: Onboarding e Offboarding del Personale|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire i passi operativi per assegnare e revocare in modo controllato accessi, dispositivi e credenziali quando una persona entra o esce da Nuova Agile Technology srl. L'obiettivo è dare a ciascuno solo ciò che serve (minimo privilegio) e revocare tutto tempestivamente all'uscita.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>Al Resp. IT/Sicurezza (esecutore principale), alla Direzione (autorizzazione) e a chiunque gestisca account o strumenti. Riguarda dipendenti e collaboratori esterni (P.IVA).</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>Richiesta formale di Direzione (Silvia Garretto) con ruolo, data inizio/fine e accessi necessari.</li>
|
||||
<li>Inventario asset e elenco servizi aziendali aggiornati.</li>
|
||||
<li>Password manager e gestore segreti aziendali.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi — Onboarding</h2>
|
||||
<ol>
|
||||
<li><strong>Crea l'identità.</strong> Apri l'account aziendale (posta + identità SSO se presente) con nome utente standard. Forza il cambio password al primo accesso e attiva l'MFA.</li>
|
||||
<li><strong>Assegna gli accessi minimi.</strong> Concedi solo i servizi/ruoli richiesti per la mansione. Per i collaboratori esterni limita l'accesso ai soli progetti pertinenti e, se possibile, con scadenza.</li>
|
||||
<li><strong>Prepara il dispositivo.</strong> Consegna il portatile configurato secondo l'Istruzione Hardening (disco cifrato, antimalware, blocco schermo) e registralo nell'inventario.</li>
|
||||
<li><strong>Consegna credenziali e segreti.</strong> Le credenziali di servizio passano tramite password manager/gestore segreti condiviso, mai via email o chat.</li>
|
||||
<li><strong>Formazione iniziale.</strong> Fai leggere e accettare le istruzioni operative chiave (MFA, phishing, uso AI, hardening) e il regolamento sull'uso degli strumenti. Registra l'avvenuta consapevolezza.</li>
|
||||
<li><strong>Registra tutto.</strong> Annota nel registro accessi data, ruolo e servizi concessi.</li>
|
||||
</ol>
|
||||
|
||||
<h2>4-bis. Passi operativi — Offboarding</h2>
|
||||
<ol>
|
||||
<li><strong>Disabilita subito gli accessi.</strong> Alla data di cessazione (o prima, se richiesto) disabilita l'account principale e revoca le sessioni attive; non limitarti a cambiare la password.</li>
|
||||
<li><strong>Revoca su ogni servizio.</strong> Rimuovi l'utente da posta, repository, console cloud, piattaforme AI, password manager, gestore segreti e MDM.</li>
|
||||
<li><strong>Ruota i segreti condivisi.</strong> Cambia password, chiavi API e token a cui la persona aveva accesso. Questo vale specialmente per i collaboratori esterni.</li>
|
||||
<li><strong>Recupera il dispositivo.</strong> Ritira il portatile, verifica la cifratura ed esegui il wipe sicuro prima di riassegnarlo. Se il dispositivo era personale, rimuovi dati e accessi aziendali.</li>
|
||||
<li><strong>Trasferisci i dati.</strong> Riassegna repository, documenti e caselle condivise a un referente designato.</li>
|
||||
<li><strong>Chiudi il registro.</strong> Annota data di revoca e segreti ruotati.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Onboarding: login di prova con MFA riuscito; accessi limitati al necessario.</li>
|
||||
<li>Offboarding: tentativo di accesso con il vecchio account = negato su tutti i servizi; segreti condivisi ruotati; dispositivo recuperato.</li>
|
||||
<li>Revisione periodica (almeno semestrale) degli accessi attivi vs personale in forza.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Per dubbi su quali accessi concedere/revocare, segreti non ruotabili o dispositivi non recuperati, contatta il Resp. IT/Sicurezza <strong>Simon Fattori</strong>. In caso di uscita conflittuale, dare priorità assoluta alla revoca immediata degli accessi.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.5.17 (informazioni di autenticazione), A.8.1 (endpoint), A.6.3 (consapevolezza in fase di ingresso). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per controllo degli accessi e igiene informatica.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale.</p>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--META|doc_type=istruzione_patch|title=Istruzione Operativa: Gestione Patch e Aggiornamenti|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Assicurare che sistemi operativi, applicazioni, browser e dipendenze dei prodotti di Nuova Agile Technology srl siano aggiornati con le correzioni di sicurezza in tempi adeguati. Le vulnerabilità non corrette sono una delle principali cause di compromissione.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>A tutti i dipendenti e collaboratori esterni per i propri portatili e strumenti; al Resp. IT/Sicurezza per i servizi cloud, le immagini dei prodotti SaaS e on-premise.</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>Portatile configurato secondo l'Istruzione Hardening.</li>
|
||||
<li>Inventario di sistemi, applicazioni e componenti software con relativi responsabili.</li>
|
||||
<li>Canale per ricevere avvisi di vulnerabilità (es. bollettini fornitori, CSIRT Italia).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi</h2>
|
||||
<ol>
|
||||
<li><strong>Abilita gli aggiornamenti automatici.</strong> Sistema operativo (Windows Update, aggiornamenti macOS, gestore pacchetti Linux) e browser su aggiornamento automatico. Non rinviare gli aggiornamenti oltre il necessario.</li>
|
||||
<li><strong>Aggiorna le applicazioni.</strong> Tieni aggiornati editor, strumenti di sviluppo, client VPN, antimalware ed estensioni del browser. Rimuovi software non più usato o non più supportato dal fornitore.</li>
|
||||
<li><strong>Riavvia.</strong> Molti aggiornamenti si completano solo al riavvio: riavvia il portatile almeno una volta a settimana e quando richiesto.</li>
|
||||
<li><strong>Definisci le priorità (a cura del Resp. IT).</strong> Classifica le patch per gravità. Criteri indicativi: vulnerabilità <em>critiche</em> sfruttate attivamente → applicare con urgenza (entro pochi giorni, [DA VERIFICARE] rispetto a SLA interni); <em>alte</em> → tempi brevi; <em>medie/basse</em> → ciclo ordinario.</li>
|
||||
<li><strong>Dipendenze dei prodotti.</strong> Per i prodotti SaaS e on-premise, monitora le dipendenze (librerie, container, runtime). Aggiorna le immagini e ricostruisci gli artefatti quando emergono vulnerabilità note.</li>
|
||||
<li><strong>Servizi cloud.</strong> Verifica e applica gli aggiornamenti sulle console e sui servizi gestiti (Aruba, Hetzner) secondo le indicazioni dei fornitori. Annota le finestre di manutenzione.</li>
|
||||
<li><strong>Testa prima di rilasciare ai clienti.</strong> Per le patch dei prodotti consegnati ai clienti, verifica in ambiente di prova prima del rilascio per evitare regressioni.</li>
|
||||
<li><strong>Registra.</strong> Tieni traccia di patch applicate, date e versioni nel registro aggiornamenti/asset.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Sistema operativo e browser risultano all'ultima versione disponibile (verifica mensile minima).</li>
|
||||
<li>Nessuna vulnerabilità critica nota rimasta non gestita oltre lo SLA interno.</li>
|
||||
<li>Software non supportato rimosso o sostituito.</li>
|
||||
<li>Registro aggiornamenti compilato e coerente con l'inventario.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Se un aggiornamento fallisce, causa malfunzionamenti, o non sai come trattare una vulnerabilità segnalata, contatta il Resp. IT/Sicurezza <strong>Simon Fattori</strong>. Non disinstallare le patch di sicurezza per "far funzionare" un'applicazione senza prima concordarlo.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.8.8 (gestione delle vulnerabilità tecniche), A.8.7 (protezione dai malware), A.8.1 (endpoint). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per igiene informatica e gestione delle vulnerabilità.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale.</p>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!--META|doc_type=istruzione_log_review|title=Istruzione Operativa: Revisione di Log e Accessi|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire come e con quale frequenza il Resp. IT/Sicurezza esamina i registri (log) e gli accessi dei servizi cloud di Nuova Agile Technology srl, per rilevare tempestivamente attività anomale o accessi non autorizzati. In assenza di server in sede, i log dei servizi cloud sono la principale fonte di visibilità.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>Principalmente al Resp. IT/Sicurezza <strong>Simon Fattori</strong> (esecutore). I dipendenti e collaboratori collaborano segnalando attività anomale sui propri account.</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>Logging attivo su tutti i servizi critici (posta, console Aruba/Hetzner, repository, piattaforme AI, password manager, prodotti SaaS).</li>
|
||||
<li>Accesso autorizzato ai registri e, dove disponibile, agli avvisi di sicurezza dei fornitori.</li>
|
||||
<li>Orari di riferimento coerenti (fuso Europe/Rome o UTC documentato) per correlare gli eventi.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi</h2>
|
||||
<ol>
|
||||
<li><strong>Verifica giornaliera rapida.</strong> Controlla gli avvisi di sicurezza automatici ricevuti via email dai fornitori (nuovi accessi, login da nuovi dispositivi/paesi, blocchi MFA). Tratta ogni avviso come da valutare, non da ignorare.</li>
|
||||
<li><strong>Revisione settimanale degli accessi.</strong> Esamina i log di autenticazione dei servizi critici cercando: login falliti ripetuti, accessi da località/orari inusuali, accessi a orari notturni non giustificati, nuove sessioni o dispositivi non riconosciuti.</li>
|
||||
<li><strong>Controllo dei cambiamenti privilegiati.</strong> Verifica modifiche a ruoli/permessi, creazione di nuovi account, generazione o rotazione di chiavi API e token. Ogni cambiamento deve essere riconducibile a una richiesta legittima.</li>
|
||||
<li><strong>Log dei prodotti SaaS.</strong> Controlla i registri applicativi (audit log) dei prodotti per accessi anomali ai dati dei clienti ed errori ricorrenti.</li>
|
||||
<li><strong>Correla gli eventi.</strong> Se noti un'anomalia, ricostruisci la sequenza tra servizi diversi usando orari allineati, per capire se si tratta di un singolo evento o di una catena.</li>
|
||||
<li><strong>Conserva e proteggi i log.</strong> Assicurati che i registri non siano modificabili dagli utenti finali e siano conservati per un periodo adeguato secondo le impostazioni dei fornitori [DA VERIFICARE rispetto alla policy di retention interna].</li>
|
||||
<li><strong>Documenta la revisione.</strong> Annota data, servizi controllati, anomalie rilevate e azioni intraprese nel registro delle revisioni.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Logging attivo confermato su tutti i servizi critici.</li>
|
||||
<li>Revisione settimanale eseguita e registrata.</li>
|
||||
<li>Ogni anomalia rilevata risulta tracciata con esito (chiusa come falso positivo o escalata a incidente).</li>
|
||||
<li>Nessun account o chiave attivi non riconducibili a personale in forza.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Se rilevi un accesso non autorizzato confermato o sospetto, una chiave compromessa o attività anomale sui dati dei clienti, avvia la procedura di gestione incidenti. Il referente è il Resp. IT/Sicurezza <strong>Simon Fattori</strong>; per incidenti che possono ricadere sotto NIS2 ricorda le tempistiche di notifica al CSIRT Italia: pre-allarme 24h, notifica 72h, relazione finale 1 mese.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.8.15 (logging), A.8.16 (attività di monitoraggio), A.5.17 (autenticazione). Coerente con ISO/IEC 27017/27018 per i servizi cloud e i dati personali, e con NIS2 (D.Lgs. 138/2024, art. 24) per il rilevamento degli incidenti.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale.</p>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--META|doc_type=istruzione_uso_ai|title=Istruzione Operativa: Uso Sicuro delle Piattaforme AI|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Definire le regole pratiche per usare in sicurezza le piattaforme di intelligenza artificiale (assistenti, modelli, API) nel lavoro quotidiano di Nuova Agile Technology srl, evitando la divulgazione di dati personali dei clienti, segreti e proprietà intellettuale. L'AI è uno strumento utile ma i dati che vi inserisci possono essere trattati da terze parti.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>A tutti i dipendenti e collaboratori esterni che usano strumenti AI per sviluppo, supporto, redazione testi o analisi.</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>Account AI aziendale o approvato dal Resp. IT, protetto da MFA.</li>
|
||||
<li>Conoscenza di quali dati sono "personali dei clienti" o "segreti" (credenziali, chiavi, codice proprietario sensibile).</li>
|
||||
<li>Eventuali accordi/DPA con il fornitore AI verificati dal Resp. IT/DPO.</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi</h2>
|
||||
<ol>
|
||||
<li><strong>Usa solo strumenti approvati.</strong> Impiega le piattaforme AI autorizzate dall'azienda. Non registrare account aziendali su servizi AI non approvati per trattare contenuti di lavoro.</li>
|
||||
<li><strong>Non inviare dati personali dei clienti.</strong> È <strong>vietato</strong> inserire nei prompt dati personali dei clienti o degli interessati (nomi, email, dati di contatto, dati contenuti nel SaaS) salvo che esista un accordo/DPA adeguato con il fornitore e si applichi la minimizzazione.</li>
|
||||
<li><strong>Non inviare segreti.</strong> Mai incollare password, chiavi API, token, certificati, stringhe di connessione o configurazioni di produzione. Questi vivono nel gestore segreti, non nei prompt.</li>
|
||||
<li><strong>Minimizza e anonimizza.</strong> Prima di chiedere aiuto, rimuovi o sostituisci i dati identificativi con segnaposto (es. "CLIENTE_X", "email@example.com"). Condividi solo il minimo necessario a ottenere la risposta.</li>
|
||||
<li><strong>Codice sorgente.</strong> Non caricare interi repository proprietari o codice contenente segreti. Estrai solo lo snippet rilevante e ripulito.</li>
|
||||
<li><strong>Verifica gli output.</strong> Tratta le risposte dell'AI come bozze da verificare: controlla correttezza, sicurezza del codice generato e assenza di riferimenti inventati prima di usarle in produzione o verso i clienti.</li>
|
||||
<li><strong>Impostazioni privacy.</strong> Dove possibile, disabilita l'uso dei tuoi dati per l'addestramento e usa i piani/impostazioni aziendali concordati.</li>
|
||||
<li><strong>Dubbi.</strong> Se non sei sicuro che un contenuto possa essere inviato all'AI, <strong>non inviarlo</strong> e chiedi prima al Resp. IT.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Nessun dato personale del cliente o segreto inviato a piattaforme AI senza accordo/DPA.</li>
|
||||
<li>Solo strumenti AI approvati usati per contenuti di lavoro.</li>
|
||||
<li>Output AI verificati prima dell'uso verso clienti o produzione.</li>
|
||||
<li>Impostazioni privacy/training configurate secondo le indicazioni aziendali.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Se hai inviato per errore dati personali o segreti a una piattaforma AI, o hai un dubbio su uno strumento, contatta <strong>subito</strong> il Resp. IT/Sicurezza <strong>Simon Fattori</strong> (e, per i dati personali, il DPO esterno). Una divulgazione di dati personali può configurare un incidente da valutare.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.5.17 (autenticazione), A.8.1 (endpoint), e in materia di protezione dei dati personali nel cloud ISO/IEC 27018; ISO/IEC 27017 per i servizi cloud. Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per sicurezza nell'uso dei sistemi e igiene informatica.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale o all'adozione di nuovi strumenti AI.</p>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!--META|doc_type=istruzione_phishing|title=Istruzione Operativa: Riconoscimento e Segnalazione del Phishing|status=approved|version=1.0-->
|
||||
|
||||
<h2>1. Scopo</h2>
|
||||
<p>Mettere ogni persona di Nuova Agile Technology srl in condizione di riconoscere i tentativi di phishing (email, messaggi, telefonate fraudolente) e di segnalarli rapidamente, evitando di cliccare link, aprire allegati o consegnare credenziali. Il phishing è la porta d'ingresso più comune per gli attacchi.</p>
|
||||
|
||||
<h2>2. A chi si applica</h2>
|
||||
<p>A tutti i dipendenti e collaboratori esterni che usano email, messaggistica e telefono per lavoro.</p>
|
||||
|
||||
<h2>3. Prerequisiti</h2>
|
||||
<ul>
|
||||
<li>MFA attivo su posta e servizi critici (così una password rubata non basta all'attaccante).</li>
|
||||
<li>Sapere come segnalare al Resp. IT (email/chat dedicata o pulsante di segnalazione se presente).</li>
|
||||
</ul>
|
||||
|
||||
<h2>4. Passi operativi</h2>
|
||||
<ol>
|
||||
<li><strong>Fermati prima di agire.</strong> Diffida dei messaggi che creano urgenza, paura o curiosità ("account bloccato", "fattura urgente", "il capo chiede ora un bonifico/un buono regalo").</li>
|
||||
<li><strong>Controlla il mittente.</strong> Verifica l'indirizzo email reale, non solo il nome visualizzato. Attenzione a domini simili ma non identici (lettere sostituite, suffissi diversi).</li>
|
||||
<li><strong>Non cliccare d'impulso.</strong> Passa il mouse sui link e leggi l'URL di destinazione senza cliccare. Se non corrisponde al sito atteso, non aprirlo. Nel dubbio, raggiungi il servizio digitando tu l'indirizzo nel browser.</li>
|
||||
<li><strong>Allegati.</strong> Non aprire allegati inattesi, soprattutto file eseguibili, archivi o documenti che chiedono di "abilitare le macro".</li>
|
||||
<li><strong>Mai inserire credenziali da un link email.</strong> Non digitare mai password o codici MFA su una pagina raggiunta da un link ricevuto. I siti legittimi non chiedono mai password via email.</li>
|
||||
<li><strong>Richieste anomale del "capo" o di fornitori.</strong> Per richieste di pagamento, cambio IBAN, invio di credenziali o dati, verifica sempre su un canale diverso e conosciuto (telefonata al numero noto), non rispondendo al messaggio sospetto.</li>
|
||||
<li><strong>Attenzione anche a SMS e chiamate.</strong> Il phishing arriva anche via SMS (smishing) e telefono (vishing). Non fidarti del solo numero o nome mostrato.</li>
|
||||
<li><strong>Codici MFA.</strong> Nessuno deve chiederti di leggere o inoltrare un codice MFA: se accade, è un attacco.</li>
|
||||
</ol>
|
||||
|
||||
<h2>5. Verifiche e controlli di esito</h2>
|
||||
<ul>
|
||||
<li>Sai indicare almeno tre segnali tipici di un messaggio di phishing.</li>
|
||||
<li>Sai dove e come inoltrare un messaggio sospetto al Resp. IT.</li>
|
||||
<li>Di fronte a un messaggio sospetto non hai cliccato link né aperto allegati prima di segnalarlo.</li>
|
||||
</ul>
|
||||
|
||||
<h2>6. In caso di problema</h2>
|
||||
<p>Se ricevi un messaggio sospetto, <strong>segnalalo</strong> al Resp. IT/Sicurezza <strong>Simon Fattori</strong> inoltrando il messaggio originale (senza cliccare nulla). Se hai già cliccato un link, inserito credenziali o aperto un allegato: <strong>non aspettare</strong>, avvisa immediatamente Simon Fattori, cambia subito la password interessata e, se possibile, scollega il dispositivo dalla rete. Una segnalazione tempestiva può evitare un incidente; un click confessato non è una colpa, è la cosa giusta da fare.</p>
|
||||
|
||||
<h2>7. Controlli ISO collegati</h2>
|
||||
<p>ISO/IEC 27001:2022 — A.6.3 (consapevolezza, istruzione e addestramento), A.5.17 (informazioni di autenticazione), A.8.7 (protezione dai malware). Coerente con NIS2 (D.Lgs. 138/2024, art. 24) per formazione e igiene informatica di base. Se un episodio diventa incidente, ricorda le tempistiche di notifica al CSIRT Italia: pre-allarme 24h, notifica 72h, relazione finale 1 mese.</p>
|
||||
|
||||
<h2>8. Versionamento</h2>
|
||||
<p>Versione 1.0 — Stato: approvato. RSGSI: Massimo Tagliavini. Resp. IT: Simon Fattori. Approvazione Direzione: Silvia Garretto. Revisione almeno annuale; integrare con simulazioni di phishing periodiche.</p>
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* build_agile_consulente.php — "Agile Technology" come STUDIO DI CONSULENZA
|
||||
* (consulting_firm id=1) che gestisce sé stessa (org 996003) come PRIMA azienda.
|
||||
*
|
||||
* Opzione A (consolidamento, decisione utente 2026-06-24): NESSUN duplicato, NESSUNA rinomina.
|
||||
* IDEMPOTENTE + TRANSAZIONALE. Esegue:
|
||||
* 1) consulting_firm 1: anagrafica REALE (P.IVA 07776161213, sede legale Roma
|
||||
* Piazza di Campitelli 2 00186 RM, PEC agile.software@pec.it).
|
||||
* 2) organizations 996003: consulting_firm_id=1 → azienda auto-gestita dallo studio.
|
||||
* 3) firm_org_assignments: studio 1 ↔ org 996003 ↔ i 4 admin.
|
||||
* 4) users: consulting_firm_id=1 per i 4 admin (Benassati consulente, Silvia legale
|
||||
* rappr. = presidenza@ (l'utente reale; "presidente@" non esiste), Tagliavini, Fattori).
|
||||
* Manuale/procedure/policy: GIÀ su 996003 (12 published) → NON toccati.
|
||||
* NB schema: consulting_firms non ha campi legale_rappresentante/sede_operativa →
|
||||
* legale rappr. = user 103; sede operativa Modena vive nei documenti SGSI dell'org.
|
||||
*
|
||||
* Esegui: docker exec nis2-app php /var/www/nis2-agile/application/cli/build_agile_consulente.php
|
||||
* Rollback: vedi fondo file.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/config.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
|
||||
const FIRM_ID = 1;
|
||||
const ORG_ID = 996003;
|
||||
const ADMINS = [4, 103, 326, 330]; // Benassati(consulente) · Silvia(legale rappr) · Tagliavini · Fattori
|
||||
const ASSIGNED_BY = 4; // il consulente
|
||||
|
||||
function one($pdo, $sql, $p = []) { $s = $pdo->prepare($sql); $s->execute($p); return $s->fetch(PDO::FETCH_ASSOC); }
|
||||
$inList = implode(',', ADMINS);
|
||||
|
||||
echo "=== STATO PRIMA ===\n";
|
||||
echo "firm 1 : " . json_encode(one($pdo, "SELECT name,vat_number,address,city,cap,pec FROM consulting_firms WHERE id=?", [FIRM_ID]), JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "org 996003 : cf_id=" . json_encode(one($pdo, "SELECT consulting_firm_id FROM organizations WHERE id=?", [ORG_ID])) . "\n";
|
||||
echo "users : " . json_encode($pdo->query("SELECT id,consulting_firm_id FROM users WHERE id IN ($inList)")->fetchAll(PDO::FETCH_KEY_PAIR)) . "\n";
|
||||
echo "assignments: " . (int)$pdo->query("SELECT COUNT(*) FROM firm_org_assignments WHERE consulting_firm_id=" . FIRM_ID . " AND organization_id=" . ORG_ID)->fetchColumn() . "\n\n";
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$pdo->prepare("UPDATE consulting_firms SET vat_number=?, fiscal_code=?, forma_giuridica=?, address=?, city=?, province=?, cap=?, pec=?, website=?, updated_at=NOW() WHERE id=?")
|
||||
->execute(['07776161213', '07776161213', 'SRL', 'Piazza di Campitelli, 2', 'Roma', 'RM', '00186', 'agile.software@pec.it', 'https://agile.software', FIRM_ID]);
|
||||
echo "[1] consulting_firm 1: anagrafica reale aggiornata\n";
|
||||
|
||||
$pdo->prepare("UPDATE organizations SET consulting_firm_id=? WHERE id=?")->execute([FIRM_ID, ORG_ID]);
|
||||
echo "[2] org 996003.consulting_firm_id=1 (azienda auto-gestita)\n";
|
||||
|
||||
$ins = 0;
|
||||
foreach (ADMINS as $uid) {
|
||||
$ex = (int)one($pdo, "SELECT COUNT(*) c FROM firm_org_assignments WHERE consulting_firm_id=? AND organization_id=? AND assigned_to=?", [FIRM_ID, ORG_ID, $uid])['c'];
|
||||
if ($ex === 0) {
|
||||
$pdo->prepare("INSERT INTO firm_org_assignments (consulting_firm_id,organization_id,assigned_to,assigned_by,created_at) VALUES (?,?,?,?,NOW())")
|
||||
->execute([FIRM_ID, ORG_ID, $uid, ASSIGNED_BY]);
|
||||
$ins++;
|
||||
}
|
||||
}
|
||||
echo "[3] firm_org_assignments: +$ins nuove (su " . count(ADMINS) . " admin)\n";
|
||||
|
||||
$pdo->prepare("UPDATE users SET consulting_firm_id=? WHERE id IN ($inList)")->execute([FIRM_ID]);
|
||||
echo "[4] users.consulting_firm_id=1 per $inList\n";
|
||||
|
||||
$pdo->commit();
|
||||
echo "\n=== COMMIT OK ===\n";
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
echo "ROLLBACK (nessuna modifica): " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo "\n=== STATO DOPO ===\n";
|
||||
echo "firm 1 : " . json_encode(one($pdo, "SELECT name,vat_number,fiscal_code,address,city,province,cap,pec FROM consulting_firms WHERE id=?", [FIRM_ID]), JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "org 996003 : " . json_encode(one($pdo, "SELECT name,vat_number,consulting_firm_id FROM organizations WHERE id=?", [ORG_ID]), JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "users : " . json_encode($pdo->query("SELECT id,email,role,consulting_firm_id FROM users WHERE id IN ($inList)")->fetchAll(PDO::FETCH_ASSOC), JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "assignments: " . json_encode($pdo->query("SELECT assigned_to,assigned_by FROM firm_org_assignments WHERE consulting_firm_id=" . FIRM_ID . " AND organization_id=" . ORG_ID . " ORDER BY assigned_to")->fetchAll(PDO::FETCH_ASSOC)) . "\n";
|
||||
echo "doc SGSI 996003 (published): " . (int)$pdo->query("SELECT COUNT(*) FROM isms_documents WHERE organization_id=" . ORG_ID . " AND status='published'")->fetchColumn() . "\n";
|
||||
|
||||
/* ROLLBACK manuale:
|
||||
UPDATE consulting_firms SET vat_number='IT12345670962',fiscal_code='12345670962',address='Via dei Consulenti 42',city='Milano',province='MI',cap='20121',pec='agiletech@pec.it' WHERE id=1;
|
||||
UPDATE organizations SET consulting_firm_id=NULL WHERE id=996003;
|
||||
DELETE FROM firm_org_assignments WHERE consulting_firm_id=1 AND organization_id=996003;
|
||||
UPDATE users SET consulting_firm_id=NULL WHERE id IN (4,103); -- (326,330 erano GIÀ firm 1)
|
||||
*/
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_audit.php — Simulazione AUDIT INTERNO COMBINATO (ISO 27001/27017/27018 + NIS2).
|
||||
* Registra internal_audits + items + apre non_conformities. IDEMPOTENTE (per code/titolo).
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
|
||||
$NOW='2026-06-20 11:30:00'; $ORGID=996003; $RSGSI=326; $PRES=103;
|
||||
$rsgsiRole=one($pdo,"SELECT id FROM org_roles WHERE organization_id=? AND role_name=?",[$ORGID,'Responsabile del Sistema di Gestione (RSGSI)']);
|
||||
$rsgsiRoleId=$rsgsiRole?(int)$rsgsiRole['id']:null;
|
||||
|
||||
/* ---- AUDIT HEADER ---------------------------------------------------------- */
|
||||
$scope="SGSI di Nuova Agile Technology srl: sviluppo, erogazione e manutenzione di prodotti in licenza (on-premise presso i clienti) e servizi SaaS in cloud; infrastruttura cloud (Aruba, Hetzner), piattaforme AI, endpoint (PC portatili), trattamento dei dati personali dei clienti.";
|
||||
$criteria="ISO/IEC 27001:2022 clausole 4-10 e controlli Annex A applicabili (SoA, incl. ISO/IEC 27017:2015 e 27018:2019); obblighi NIS2 - D.Lgs. 138/2024 art.23 (governance), art.24 (misure di gestione del rischio), art.25 (notifica incidenti).";
|
||||
$concl="Audit combinato: SGSI in FASE DI IMPIANTO. Documentazione (Manuale, 10 politiche, 13 procedure, 7 istruzioni), SoA (111 controlli ISO 27001/27017/27018), contesto, organigramma, registro rischi e inventario asset COMPLETI e COERENTI col profilo cloud-only -> esito positivo della revisione documentale (equivalente a Stage 1). Sistema NON ancora OPERATIVO: mancano evidenze di attuazione (trattamento rischi, esecuzione controlli periodici, erogazione formazione, primo riesame di Direzione, ciclo PDCA). Rilevate 1 NC maggiore, 4 NC minori e diverse osservazioni. VERDETTO: NON pronto per audit di certificazione (Stage 2) ne' per dichiarare piena conformita' NIS2. Raccomandazioni: chiudere le NC; completare il piano di attuazione ed accumulare ~3 mesi di evidenze operative; formalizzare con delibera della Direzione la determinazione di applicabilita' NIS2 (art.23 D.Lgs.138/2024) integrando i dati dimensionali (P.IVA, fatturato). Ripianificare follow-up dopo chiusura NC.";
|
||||
|
||||
$a=one($pdo,"SELECT id FROM internal_audits WHERE organization_id=? AND code=?",[$ORGID,'AUD-001']);
|
||||
if(!$a){
|
||||
ex($pdo,"INSERT INTO internal_audits (organization_id,code,title,scope,criteria,planned_date,executed_date,status,lead_auditor_user_id,lead_auditor_role_id,conclusion,created_by,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?, 'completed', ?,?,?,?,?,?)",
|
||||
[$ORGID,'AUD-001','Audit interno combinato SGSI (ISO 27001/27017/27018) e conformita NIS2',$scope,$criteria,'2026-06-20','2026-06-20',$RSGSI,$rsgsiRoleId,$concl,$RSGSI,$NOW,$NOW]);
|
||||
$auditId=(int)$pdo->lastInsertId(); echo "AUDIT creato AUD-001 id=$auditId\n";
|
||||
} else { $auditId=(int)$a['id']; ex($pdo,"UPDATE internal_audits SET status='completed',conclusion=?,executed_date='2026-06-20',updated_at=? WHERE id=?",[$concl,$NOW,$auditId]); echo "AUDIT esistente id=$auditId (aggiornato)\n"; }
|
||||
|
||||
/* ---- CHECKLIST ITEMS ------------------------------------------------------- */
|
||||
// ref_type, ref_code, checkpoint, result, note
|
||||
$items=[
|
||||
['clause','4','Contesto dell\'organizzazione: scopo, contesto interno/esterno, parti interessate, perimetro SGSI','conforme','Definiti nel Manuale e nel Modello SGSI (scope cloud-only, contesto, 6 parti interessate, esclusione data center fisici). Coerenti col profilo.'],
|
||||
['clause','5','Leadership e politica: impegno della Direzione, politica SGSI, ruoli e responsabilita','osservazione','Politica SGSI approvata e ruoli assegnati (Presidente Garretto, RSGSI Tagliavini, Resp.IT Fattori). OSS: l\'evidenza formale dell\'impegno e dell\'approvazione della Direzione (verbale/delibera) e da consolidare; primo riesame non ancora svolto.'],
|
||||
['clause','6','Pianificazione: valutazione e trattamento dei rischi, obiettivi, SoA','non_conforme','NC MINORE: 10 rischi identificati e valutati ma SENZA piani di trattamento documentati (responsabili/azioni/scadenze). SoA completa ma con stato di attuazione "not_started". Vedi NCR collegata.'],
|
||||
['clause','7','Supporto: risorse, competenze, consapevolezza, comunicazione, informazioni documentate','non_conforme','NC MINORE: controllo documenti adeguato (31 documenti gestiti, versionati). Competenze/consapevolezza NON evidenziate: 5 corsi definiti ma nessuna assegnazione/completamento. Vedi NCR collegata.'],
|
||||
['clause','8','Operativita: attuazione dei processi e dei controlli, trattamento operativo dei rischi','non_conforme','NC MINORE: processi documentati in procedure ma EVIDENZE DI ATTUAZIONE assenti: i 15 piani di controllo non risultano eseguiti (nessuna registrazione). Vedi NCR collegata.'],
|
||||
['clause','9','Valutazione delle prestazioni: monitoraggio, misurazione, audit interni, riesame di direzione','osservazione','Monitoraggio pianificato (piani di controllo) ma mai eseguito; gap analysis al 45% (in corso). Nessun riesame di Direzione ne audit interno pregresso (questo e il primo). OSS: garantire l\'indipendenza dell\'auditor nei cicli futuri (auditor non responsabile dell\'area).'],
|
||||
['clause','10','Miglioramento: non conformita e azioni correttive','conforme','Procedura NC/azioni correttive presente e modulo NCR/CAPA disponibile e operativo: esercitato da questo stesso audit con apertura delle NC.'],
|
||||
['annex_control','A.5.1','Politiche per la sicurezza delle informazioni','conforme','Corpo politiche completo e coerente (10 politiche approvate).'],
|
||||
['annex_control','A.5.18','Diritti di accesso','osservazione','Politica controllo accessi presente; riesame periodico degli accessi pianificato (CTL-001) ma non ancora eseguito.'],
|
||||
['annex_control','A.5.22','Monitoraggio e riesame dei servizi dei fornitori','osservazione','4 fornitori critici censiti (Aruba, Hetzner, Anthropic, Google). DPA/clausole di sicurezza e valutazioni da formalizzare; riesame annuale (CTL-008) non ancora svolto.'],
|
||||
['annex_control','A.5.34','Privacy e protezione delle PII','non_conforme','NC MINORE: la piattaforma SaaS tratta dati personali dei clienti (ISO 27018/GDPR) ma il ruolo di Referente Protezione Dati (DPO) e VACANTE. Vedi NCR collegata.'],
|
||||
['annex_control','A.8.5','Autenticazione sicura (MFA)','osservazione','MFA richiesta dalle politiche/istruzioni; evidenza di attuazione e verifica (CTL-002) non disponibile.'],
|
||||
['annex_control','A.8.13','Backup delle informazioni','osservazione','Politica e procedura di backup presenti; test di ripristino (CTL-003) mai eseguito: efficacia non dimostrata.'],
|
||||
['annex_control','A.8.8','Gestione delle vulnerabilita tecniche','osservazione','Procedura presente; vulnerability scan e applicazione patch (CTL-004/006) non ancora evidenziati.'],
|
||||
['annex_control','CLD.6.3','ISO 27017 - responsabilita condivisa nel cloud','conforme','Modello di responsabilita condivisa documentato (Politica Cloud 27017); coerente con uso di Aruba/Hetzner e ruolo di cloud provider (SaaS).'],
|
||||
['nis2_measure','art.23','Governance NIS2: organi di gestione, approvazione e supervisione delle misure','non_conforme','NC MAGGIORE: determinazione di applicabilita NIS2 NON formalizzata. Classificazione "importante" preliminare senza delibera della Direzione ne conferma ACN; dati dimensionali (P.IVA, fatturato) mancanti. L\'art.23 D.Lgs.138/2024 richiede approvazione e supervisione formale da parte degli organi di gestione. Vedi NCR collegata.'],
|
||||
['nis2_measure','art.24','Misure di gestione del rischio (le misure minime)','osservazione','Misure documentate (politiche/procedure su rischi, incidenti, continuita, accessi, crittografia, supply chain, sviluppo sicuro) coerenti con l\'art.24. Attuazione/evidenze da completare (collegato alle NC su cl.6/8).'],
|
||||
['nis2_measure','art.25','Obblighi di notifica degli incidenti','osservazione','Procedura di notifica con tempistiche corrette (pre-allarme 24h, notifica 72h, relazione finale 1 mese al CSIRT Italia). Capacita NON testata: nessuna simulazione ne incidente registrato.'],
|
||||
['nis2_measure','art.24-sc','Sicurezza della supply chain (NIS2)','osservazione','Fornitori critici censiti; necessario formalizzare clausole contrattuali di sicurezza e valutazioni periodiche, anche in quanto l\'azienda e a sua volta fornitore di clienti soggetti a NIS2.'],
|
||||
['custom','ORG','Anagrafica dell\'organizzazione','osservazione','P.IVA, sede legale e fatturato annuo non valorizzati: completare per la determinazione dimensionale e i documenti.'],
|
||||
['custom','GAP','Gap analysis iniziale','osservazione','Assessment Art.21/misure in corso (45%): completare per avere la baseline di conformita.'],
|
||||
];
|
||||
$ci=0;$ord=1;
|
||||
foreach($items as [$rt,$rc,$cp,$res,$note]){
|
||||
$exi=one($pdo,"SELECT id FROM internal_audit_items WHERE audit_id=? AND ref_code=? AND checkpoint=?",[$auditId,$rc,$cp]);
|
||||
if(!$exi){ ex($pdo,"INSERT INTO internal_audit_items (audit_id,ref_type,ref_code,checkpoint,result,note,ord) VALUES (?,?,?,?,?,?,?)",[$auditId,$rt,$rc,$cp,$res,$note,$ord]); $ci++; }
|
||||
$ord++;
|
||||
}
|
||||
echo "ITEMS: +$ci (totale ".count($items).")\n";
|
||||
|
||||
/* ---- NON CONFORMITA -------------------------------------------------------- */
|
||||
// title, desc, severity, category, nis2_article, target_close, codeSuffix
|
||||
$ncs=[
|
||||
['Determinazione di applicabilita NIS2 non formalizzata','La classificazione dell\'azienda come soggetto NIS2 "importante" e un\'autovalutazione preliminare: manca una delibera/atto formale degli organi di gestione e la conferma ACN; i dati dimensionali (P.IVA, fatturato) non sono valorizzati. L\'art.23 del D.Lgs.138/2024 richiede che gli organi di gestione approvino le misure e ne supervisionino l\'attuazione. Azione: formalizzare la determinazione di applicabilita con delibera della Direzione e completamento dei dati dimensionali; richiedere conferma/registrazione presso ACN se dovuta.','major','Governance NIS2','23','2026-07-31','001'],
|
||||
['Trattamento dei rischi non documentato','I 10 rischi del registro sono identificati e valutati (probabilita x impatto) ma privi di piani di trattamento documentati (azioni, responsabili, scadenze); la SoA risulta con stato di attuazione "not_started". Non conformita a ISO 27001 cl.6.1.3/8.3 e all\'art.24 D.Lgs.138/2024. Azione: definire i piani di trattamento e allineare lo stato di attuazione nella SoA.','minor','Gestione del rischio','24','2026-09-30','002'],
|
||||
['Formazione e consapevolezza non erogate','Sono definiti 5 corsi obbligatori ma non risultano assegnazioni ne completamenti: competenza e consapevolezza (ISO 27001 cl.7.2/7.3, A.6.3; igiene informatica e formazione ex art.24) non evidenziate. Azione: assegnare i corsi a tutto il personale e registrare i completamenti.','minor','Competenze e formazione','24','2026-09-30','003'],
|
||||
['Controlli periodici non eseguiti - assenza di evidenze operative','I 15 piani di controllo periodici sono definiti ma non risulta alcuna esecuzione registrata (es. verifica MFA, test di ripristino backup, vulnerability scan, riesame accessi, revisione log). Non conformita a ISO 27001 cl.9.1 e A.5.35; l\'efficacia dei controlli non e dimostrata. Azione: avviare l\'esecuzione dei controlli e registrarne gli esiti.','minor','Monitoraggio e misurazione','24','2026-09-30','004'],
|
||||
['Referente Protezione Dati (DPO) non assegnato','La piattaforma SaaS tratta dati personali dei clienti (ambito GDPR e ISO 27018) ma il ruolo di Referente Protezione Dati/DPO risulta vacante nell\'organigramma. Non conformita ad A.5.34. Azione: nominare il referente privacy (anche esterno) e formalizzarne le responsabilita.','minor','Privacy e protezione dati','','2026-09-30','005'],
|
||||
];
|
||||
$ni=0;
|
||||
foreach($ncs as [$t,$d,$sev,$cat,$art,$close,$suf]){
|
||||
$exn=one($pdo,"SELECT id FROM non_conformities WHERE organization_id=? AND title=? AND source='audit'",[$ORGID,$t]);
|
||||
if(!$exn){
|
||||
$code='NCR-9'.$suf.str_pad((string)($auditId%1000),3,'0',STR_PAD_LEFT); // codice deterministico riproducibile
|
||||
ex($pdo,"INSERT INTO non_conformities (organization_id,ncr_code,title,description,source,source_entity_type,source_entity_id,severity,category,nis2_article,status,identified_by,assigned_to,identified_at,target_close_date,created_at,updated_at)
|
||||
VALUES (?,?,?,?, 'audit','internal_audit',?, ?,?,?, 'open', ?,?,?,?,?,?)",
|
||||
[$ORGID,$code,$t,$d,$auditId,$sev,$cat,($art!==''?$art:null),$RSGSI,$RSGSI,$NOW,$close,$NOW,$NOW]);
|
||||
echo " NC + [$sev] $t ($code)\n"; $ni++;
|
||||
} else echo " NC = $t\n";
|
||||
}
|
||||
echo "NON CONFORMITA: +$ni\n";
|
||||
echo "\nAUDIT SIMULATO OK — AUD-001 id=$auditId\n";
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p1.php — FASE 1: fondazione "Nuova Agile Technology srl"
|
||||
* Org + classificazione NIS2 (important) + membership utenti esistenti + organigramma (org_roles).
|
||||
* IDEMPOTENTE (rilanciabile). READ creds da config app (TLS).
|
||||
* Uso: docker exec nis2-app php /var/www/nis2-agile/application/cli/build_nuova_agile_p1.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
|
||||
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
|
||||
$NOW = '2026-06-20 10:30:00';
|
||||
|
||||
/* ---- 1) Organization (idempotente per name) -------------------------------- */
|
||||
$ORG_NAME = 'Nuova Agile Technology srl';
|
||||
$org = one($pdo, "SELECT id FROM organizations WHERE name = ?", [$ORG_NAME]);
|
||||
$mission = "Software house italiana che sviluppa e fornisce soluzioni applicative ai propri clienti con due modelli: "
|
||||
. "(a) prodotti in licenza d'uso installati sui server dei clienti (on-premise presso il cliente); "
|
||||
. "(b) servizi SaaS erogati in cloud. Infrastruttura interamente cloud (Aruba, Hetzner) e piattaforme AI; "
|
||||
. "nessun server gestito in sede; postazioni di lavoro su PC portatili. "
|
||||
. "Organico: 9 dipendenti, 2 collaboratori esterni con P.IVA, Presidente Silvia Garretto.";
|
||||
if (!$org) {
|
||||
ex($pdo, "INSERT INTO organizations
|
||||
(name, sector, entity_type, voluntary_compliance, employee_count, annual_turnover_eur,
|
||||
country, city, website, contact_email, subscription_plan, is_active,
|
||||
is_autonomous, size_independent_category, has_code_of_ethics, designation_basis,
|
||||
self_assessment_status, mission, created_at, updated_at)
|
||||
VALUES (?, 'digital_infra', 'important', 0, 9, NULL,
|
||||
'IT', NULL, NULL, 'presidenza@agile.software', 'enterprise', 1,
|
||||
1, 'none', 0, 'sub_threshold_candidate',
|
||||
'preliminary', ?, ?, ?)",
|
||||
[$ORG_NAME, $mission, $NOW, $NOW]);
|
||||
$orgId = (int)$pdo->lastInsertId();
|
||||
echo "ORG creata id=$orgId\n";
|
||||
} else {
|
||||
$orgId = (int)$org['id'];
|
||||
ex($pdo, "UPDATE organizations SET sector='digital_infra', entity_type='important', voluntary_compliance=0,
|
||||
employee_count=9, country='IT', subscription_plan='enterprise', is_active=1, is_autonomous=1,
|
||||
size_independent_category='none', has_code_of_ethics=0, designation_basis='sub_threshold_candidate',
|
||||
self_assessment_status='preliminary', mission=?, updated_at=? WHERE id=?",
|
||||
[$mission, $NOW, $orgId]);
|
||||
echo "ORG esistente id=$orgId (aggiornata)\n";
|
||||
}
|
||||
|
||||
/* ---- 2) Membership utenti ESISTENTI (no duplicati) -------------------------- */
|
||||
// Silvia Garretto=103 (Presidente), Massimo Tagliavini=326 (Resp. SGSI), Simon Fattori=330 (org_admin/IT)
|
||||
$members = [
|
||||
[103, 'board_member', 1], // Presidente / Alta Direzione (primary)
|
||||
[326, 'compliance_manager', 0], // Responsabile SGSI
|
||||
[330, 'org_admin', 0], // Amministratore piattaforma / IT security
|
||||
];
|
||||
foreach ($members as [$uid, $role, $primary]) {
|
||||
$u = one($pdo, "SELECT id, full_name FROM users WHERE id=?", [$uid]);
|
||||
if (!$u) { echo " ! utente $uid inesistente, salto\n"; continue; }
|
||||
$ex = one($pdo, "SELECT id FROM user_organizations WHERE user_id=? AND organization_id=?", [$uid, $orgId]);
|
||||
if (!$ex) {
|
||||
ex($pdo, "INSERT INTO user_organizations (user_id, organization_id, role, is_primary, joined_at) VALUES (?,?,?,?,?)",
|
||||
[$uid, $orgId, $role, $primary, $NOW]);
|
||||
echo " membership +{$u['full_name']} ($role)\n";
|
||||
} else {
|
||||
ex($pdo, "UPDATE user_organizations SET role=?, is_primary=? WHERE id=?", [$role, $primary, $ex['id']]);
|
||||
echo " membership ~{$u['full_name']} ($role)\n";
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 3) Organigramma (org_roles) ------------------------------------------- */
|
||||
// key, role_name, parent_key, holder_user_id, is_governance_body, description, sort
|
||||
$roles = [
|
||||
['cda', 'Consiglio di Amministrazione / Presidenza', null, 103, 1, 'Alta Direzione: definisce indirizzo strategico, approva la Politica SGSI e gli obiettivi, assegna risorse, riesame della direzione (ISO 27001 cl.5/9.3). Presidente: Silvia Garretto.', 1],
|
||||
['dg', 'Direzione Generale', 'cda', 103, 1, 'Conduzione operativa dell\'azienda; sponsor del Sistema di Gestione; garantisce la leadership e l\'impegno (ISO 27001 cl.5.1).', 2],
|
||||
['rsgsi','Responsabile del Sistema di Gestione (RSGSI)','dg', 326, 0, 'Responsabile SGSI/ISMS: mantiene il sistema, coordina valutazione rischi, audit interni, SoA, formazione, gestione NC/azioni correttive. Riferisce alla Direzione.', 3],
|
||||
['itsec','Responsabile IT e Sicurezza Tecnica', 'dg', 330, 0, 'Gestione infrastruttura cloud (Aruba, Hetzner), piattaforme AI, sicurezza tecnica, gestione accessi/MFA, backup, patch, monitoraggio, gestione incidenti tecnici.', 4],
|
||||
['dpo', 'Referente Protezione Dati (DPO/Privacy)', 'dg', null, 0, 'Presidio GDPR e ISO 27018 (dati personali nel cloud), DPIA, gestione richieste interessati. Ruolo coperto da consulente esterno (da nominare).', 5],
|
||||
['dev', 'Team Sviluppo Software / SaaS', 'itsec', null, 0, 'Sviluppo prodotti in licenza e piattaforma SaaS; secure SDLC, gestione vulnerabilità applicative, code review. Composto da dipendenti.', 6],
|
||||
['ops', 'Erogazione SaaS & Supporto Clienti', 'itsec', null, 0, 'Esercizio dei servizi SaaS, supporto ai clienti, gestione delle installazioni on-premise presso i clienti.', 7],
|
||||
['ext', 'Collaboratori esterni (P.IVA)', 'dg', null, 0, '2 collaboratori esterni/interni con partita IVA: prestazioni specialistiche sotto accordo contrattuale con clausole di riservatezza e sicurezza (ISO 27001 A.5.19/A.6.6).', 8],
|
||||
];
|
||||
$idByKey = [];
|
||||
foreach ($roles as [$key,$name,$pkey,$holder,$gov,$desc,$sort]) {
|
||||
$exr = one($pdo, "SELECT id FROM org_roles WHERE organization_id=? AND role_name=?", [$orgId, $name]);
|
||||
if (!$exr) {
|
||||
ex($pdo, "INSERT INTO org_roles (organization_id, role_name, parent_role_id, holder_user_id, is_governance_body, description, sort_order, created_by, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
[$orgId, $name, null, $holder, $gov, $desc, $sort, 103, $NOW, $NOW]);
|
||||
$rid = (int)$pdo->lastInsertId();
|
||||
echo " role + $name\n";
|
||||
} else {
|
||||
$rid = (int)$exr['id'];
|
||||
ex($pdo, "UPDATE org_roles SET holder_user_id=?, is_governance_body=?, description=?, sort_order=?, updated_at=? WHERE id=?",
|
||||
[$holder, $gov, $desc, $sort, $NOW, $rid]);
|
||||
echo " role ~ $name\n";
|
||||
}
|
||||
$idByKey[$key] = $rid;
|
||||
}
|
||||
// seconda passata: parent linking
|
||||
foreach ($roles as [$key,$name,$pkey]) {
|
||||
if ($pkey && isset($idByKey[$pkey])) ex($pdo, "UPDATE org_roles SET parent_role_id=? WHERE id=?", [$idByKey[$pkey], $idByKey[$key]]);
|
||||
}
|
||||
echo "ORGANIGRAMMA: ".count($idByKey)." ruoli\n";
|
||||
|
||||
echo "\nFASE 1 OK — org_id=$orgId\n";
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p10_determinazione.php — Formalizza la Determinazione di applicabilità NIS2 di Nuova Agile.
|
||||
* Dati Direzione: fatturato 2026 0,9M€ / 2027 1,4M€; cloud provider (Allegato I); SOTTO-SOGLIA → non obbligata per legge
|
||||
* → la Direzione DELIBERA l'adozione VOLONTARIA integrale delle misure da soggetto "importante". Chiude NCR-9001. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
$NOW='2026-06-20 17:00:00'; $TODAY='2026-06-20'; $ORGID=996003;
|
||||
|
||||
/* 1) Org: fatturato + adesione volontaria deliberata (sotto-soglia) */
|
||||
ex($pdo,"UPDATE organizations SET annual_turnover_eur=900000, voluntary_compliance=1, designation_basis='voluntary',
|
||||
entity_type='important', updated_at=? WHERE id=?",[$NOW,$ORGID]);
|
||||
echo "ORG aggiornata: fatturato 900k, voluntary_compliance=1, designation_basis=voluntary, entity_type=important\n";
|
||||
|
||||
/* 2) DPO: consulente esterno deliberato (nominativo in corso) */
|
||||
ex($pdo,"UPDATE org_roles SET holder_user_id=NULL,
|
||||
description='Presidio GDPR e ISO 27018 (dati personali nel cloud), DPIA, gestione richieste interessati. DPO affidato a CONSULENTE ESTERNO (deliberato dalla Direzione il 20/06/2026; acquisizione nominativo e lettera d\'incarico in corso). Coordinamento ad interim del RSGSI nelle more.',
|
||||
updated_at=? WHERE organization_id=? AND role_name LIKE 'Referente Protezione Dati%'",[$NOW,$ORGID]);
|
||||
echo "DPO role aggiornato (consulente esterno deliberato)\n";
|
||||
|
||||
/* 3) Manuale (id 37): sostituisci la sez. 11 'Caveat' con la Determinazione formalizzata + snapshot */
|
||||
$new = '<h2>11. Determinazione di applicabilità NIS2</h2>'
|
||||
.'<p><strong>Determinazione formalizzata dalla Direzione il 20/06/2026.</strong> Sulla base dei dati dimensionali (fatturato 2026 circa 0,9 M€, previsione 2027 circa 1,4 M€; 9 dipendenti e 2 collaboratori esterni a P.IVA), l\'Azienda è una <strong>impresa piccola/micro, SOTTO le soglie dimensionali NIS2</strong> (soggetto obbligato = impresa media o grande: ≥50 addetti oppure fatturato/bilancio >10 M€). L\'attività rientra tra i <strong>fornitori di servizi di cloud computing</strong> (Allegato I — infrastrutture digitali); per i provider cloud <strong>vale comunque il cap dimensionale</strong>, pertanto un\'azienda sotto-soglia <strong>non è soggetto obbligato per legge</strong>. Un eventuale obbligo per un soggetto sotto-soglia potrebbe derivare unicamente da una <strong>designazione dell\'ACN</strong> ai sensi dell\'art. 3, comma 9, del D.Lgs. 138/2024 (es. unico fornitore nazionale, impatto sistemico), allo stato non riscontrata.</p>'
|
||||
.'<p><strong>Decisione della Direzione:</strong> pur non essendo soggetto obbligato, l\'Azienda <strong>delibera l\'adozione VOLONTARIA e integrale delle misure di un soggetto “importante”</strong>, per rigore di sicurezza e per soddisfare i <strong>requisiti di supply chain dei clienti</strong> (alcuni dei quali soggetti NIS2 obbligati, art. 24 D.Lgs. 138/2024). La presente determinazione è riesaminata al superamento delle soglie dimensionali, al cambiamento della categoria di servizio o a fronte di una designazione ACN.</p>'
|
||||
.'<p>Quadro di riferimento: Direttiva (UE) 2022/2555 recepita dal <strong>D.Lgs. 138/2024</strong> — art. 23 (governance e organi di gestione), art. 24 (misure di gestione del rischio), art. 25 (notifica al CSIRT: pre-allarme entro 24 ore, notifica entro 72 ore, relazione finale entro 1 mese). Le misure di dettaglio sono definite dalle Determinazioni ACN. <strong>Le norme ISO citate sono buone prassi, non obblighi di legge.</strong></p>';
|
||||
|
||||
$b=$pdo->query("SELECT body_html FROM isms_documents WHERE id=37")->fetchColumn();
|
||||
$nb=preg_replace('#<h2>11\. Caveat sulla classificazione NIS2</h2>.*?(?=<h2>)#s',$new,$b,1,$cnt);
|
||||
if($cnt){
|
||||
ex($pdo,"UPDATE isms_documents SET body_html=?, updated_at=? WHERE id=37",[$nb,$NOW]);
|
||||
$vid=$pdo->query("SELECT id FROM isms_document_versions WHERE document_id=37 AND version='2.0' ORDER BY id DESC LIMIT 1")->fetchColumn();
|
||||
if($vid) ex($pdo,"UPDATE isms_document_versions SET body_html=? WHERE id=?",[$nb,$vid]);
|
||||
echo "MANUALE: sez.11 sostituita con la Determinazione formalizzata (+ snapshot)\n";
|
||||
} else echo "!! sezione caveat non trovata nel Manuale (verificare)\n";
|
||||
|
||||
/* 4) Chiudi NC maggiore NCR-9001 + CAPA-1010 */
|
||||
$rc='Determinazione di applicabilità formalizzata dalla Direzione il 20/06/2026: azienda SOTTO-SOGLIA (fatturato <10 M€, <50 addetti); cloud provider (Allegato I) soggetto al cap dimensionale → non obbligata per legge; la Direzione delibera l\'adozione volontaria integrale delle misure da soggetto "importante". Dati dimensionali acquisiti. Eventuale obbligo solo via designazione ACN (art.3 c.9), non riscontrata.';
|
||||
ex($pdo,"UPDATE non_conformities SET status='closed', actual_close_date=?, root_cause_analysis=?, updated_at=? WHERE id=16",[$TODAY,$rc,$NOW]);
|
||||
ex($pdo,"UPDATE capa_actions SET status='completed', completion_date=?, is_effective=1, effectiveness_review='Determinazione formalizzata e documentata nel Manuale (sez.11); classificazione allineata (adesione volontaria deliberata).', updated_at=? WHERE id=3",[$TODAY,$NOW]);
|
||||
echo "NCR-9001 (maggiore) CHIUSA + CAPA-1010 completata\n";
|
||||
|
||||
/* 5) NC DPO NCR-9005: decisione presa (esterno), nominativo in corso → resta in correzione */
|
||||
ex($pdo,"UPDATE non_conformities SET root_cause_analysis='Ruolo DPO non coperto da figura formalmente nominata. Decisione della Direzione (20/06/2026): affidamento a CONSULENTE ESTERNO; acquisizione nominativo e lettera d\'incarico in corso. Coordinamento ad interim del RSGSI.', updated_at=? WHERE id=20",[$NOW]);
|
||||
ex($pdo,"UPDATE capa_actions SET title='Nominare formalmente il DPO esterno (deliberato): acquisire nominativo e lettera d\'incarico', updated_at=? WHERE id=7",[$NOW]);
|
||||
echo "NCR-9005 (DPO) aggiornata: esterno deliberato, nomina in corso\n";
|
||||
|
||||
echo "\nFASE 10 (determinazione) OK\n";
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p11_piva_dpo.php — Completa anagrafica (P.IVA) + nomina DPO esterno Cristiano Benassati.
|
||||
* Chiude l'ultima NC (NCR-9005). IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
|
||||
$NOW='2026-06-20 17:30:00'; $TODAY='2026-06-20'; $ORGID=996003; $DPO=4; // Cristiano Benassati
|
||||
|
||||
/* 1) Anagrafica: P.IVA / codice fiscale */
|
||||
ex($pdo,"UPDATE organizations SET vat_number='07776161213', fiscal_code='07776161213', updated_at=? WHERE id=?",[$NOW,$ORGID]);
|
||||
echo "ORG: P.IVA 07776161213 (IT) impostata\n";
|
||||
|
||||
/* 2) Benassati membro dell'org (auditor = oversight DPO) */
|
||||
if(!one($pdo,"SELECT id FROM user_organizations WHERE user_id=? AND organization_id=?",[$DPO,$ORGID])){
|
||||
ex($pdo,"INSERT INTO user_organizations (user_id,organization_id,role,is_primary,joined_at) VALUES (?,?, 'auditor',0,?)",[$DPO,$ORGID,$NOW]);
|
||||
echo " membership + Cristiano Benassati (auditor/DPO)\n";
|
||||
} else echo " membership = già presente\n";
|
||||
|
||||
/* 3) Ruolo DPO nell'organigramma (org_roles) + isms_roles */
|
||||
ex($pdo,"UPDATE org_roles SET holder_user_id=?,
|
||||
description='Presidio GDPR e ISO 27018 (dati personali nel cloud), DPIA, gestione richieste interessati. DPO affidato a CONSULENTE ESTERNO: Cristiano Benassati (nominato dalla Direzione il 20/06/2026).',
|
||||
updated_at=? WHERE organization_id=? AND role_name LIKE 'Referente Protezione Dati%'",[$DPO,$NOW,$ORGID]);
|
||||
ex($pdo,"UPDATE isms_roles SET user_id=? WHERE isms_model_id=3 AND role_name LIKE '%Privacy%'",[$DPO]);
|
||||
echo "DPO nominato: Cristiano Benassati (org_role + isms_role)\n";
|
||||
|
||||
/* 4) Manuale (id 37): nome del DPO + snapshot */
|
||||
$b=$pdo->query("SELECT body_html FROM isms_documents WHERE id=37")->fetchColumn();
|
||||
$nb=str_replace('Consulente esterno (da nominare)','Consulente esterno: Cristiano Benassati',$b,$c);
|
||||
if($c){
|
||||
ex($pdo,"UPDATE isms_documents SET body_html=?, updated_at=? WHERE id=37",[$nb,$NOW]);
|
||||
$vid=$pdo->query("SELECT id FROM isms_document_versions WHERE document_id=37 AND version='2.0' ORDER BY id DESC LIMIT 1")->fetchColumn();
|
||||
if($vid) ex($pdo,"UPDATE isms_document_versions SET body_html=? WHERE id=?",[$nb,$vid]);
|
||||
echo "MANUALE: DPO nominato (Cristiano Benassati) — $c sostituzione/i + snapshot\n";
|
||||
} else echo " (stringa DPO nel Manuale non trovata, salto)\n";
|
||||
|
||||
/* 5) Chiudi NCR-9005 (DPO) + CAPA-5010 */
|
||||
ex($pdo,"UPDATE non_conformities SET status='closed', actual_close_date=?,
|
||||
root_cause_analysis='Ruolo DPO ora coperto: nominato dalla Direzione il 20/06/2026 il consulente esterno Cristiano Benassati. NC risolta.', updated_at=? WHERE id=20",[$TODAY,$NOW]);
|
||||
ex($pdo,"UPDATE capa_actions SET status='completed', completion_date=?, is_effective=1,
|
||||
effectiveness_review='DPO esterno nominato (Cristiano Benassati); ruolo assegnato in organigramma e SGSI.', updated_at=? WHERE id=7",[$TODAY,$NOW]);
|
||||
echo "NCR-9005 (DPO) CHIUSA + CAPA-5010 completata\n";
|
||||
|
||||
/* riepilogo NC */
|
||||
$open=$pdo->query("SELECT COUNT(*) FROM non_conformities WHERE organization_id=$ORGID AND status NOT IN('closed','cancelled')")->fetchColumn();
|
||||
echo "\nNC ancora aperte: $open\nFASE 11 OK\n";
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p12_sedi.php — Anagrafica sedi: sede legale Roma + sede operativa Modena.
|
||||
* Allinea lo scope SGSI (work-from-anywhere ma con sedi registrate). IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
$NOW='2026-06-20 18:00:00'; $ORGID=996003;
|
||||
|
||||
/* 1) Org: sede legale (address+city) + sede operativa in mission */
|
||||
ex($pdo,"UPDATE organizations SET address='Piazza di Campitelli, 2', city='Roma', updated_at=? WHERE id=?",[$NOW,$ORGID]);
|
||||
$mission=$pdo->query("SELECT mission FROM organizations WHERE id=$ORGID")->fetchColumn();
|
||||
if(strpos($mission,'Modena')===false){
|
||||
$mission=rtrim($mission).' Sede legale: Piazza di Campitelli 2, 00186 Roma. Sede operativa: Strada Scaglia Est 15, 41121 Modena.';
|
||||
ex($pdo,"UPDATE organizations SET mission=? WHERE id=$ORGID",[$mission]);
|
||||
}
|
||||
echo "ORG: sede legale Roma (address/city) + sede operativa Modena (mission)\n";
|
||||
|
||||
/* 2) isms_models: scope/contesto precisi (sedi registrate, modello work-from-anywhere) */
|
||||
$m=$pdo->query("SELECT id,scope_statement,context_internal FROM isms_models WHERE organization_id=$ORGID")->fetch(PDO::FETCH_ASSOC);
|
||||
$scope=str_replace(
|
||||
'NESSUNA sede fissa ne perimetro fisico aziendale: il lavoro si svolge da qualsiasi luogo.',
|
||||
'L\'Azienda ha sede legale a Roma (Piazza di Campitelli 2, 00186) e sede operativa a Modena (Strada Scaglia Est 15, 41121); il personale opera secondo il modello work-from-anywhere (da qualsiasi luogo), senza un ufficio presidiato di routine.',
|
||||
$m['scope_statement'],$c1);
|
||||
$ctx=str_replace('senza uffici fissi','con sede legale a Roma e sede operativa a Modena, modello work-from-anywhere',$m['context_internal'],$c2);
|
||||
ex($pdo,"UPDATE isms_models SET scope_statement=?, context_internal=?, updated_at=? WHERE id=?",[$scope,$ctx,$NOW,$m['id']]);
|
||||
echo "ISMS scope/contesto aggiornati (scope x$c1, contesto x$c2)\n";
|
||||
|
||||
$o=$pdo->query("SELECT vat_number,address,city FROM organizations WHERE id=$ORGID")->fetch(PDO::FETCH_ASSOC);
|
||||
echo "Anagrafica: ".json_encode($o,JSON_UNESCAPED_UNICODE)."\n";
|
||||
echo "FASE 12 OK\n";
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p2.php — FASE 2: asset inventory + fornitori + risk register + assessment.
|
||||
* Profilo: cloud-only (Aruba/Hetzner/AI), nessun server in sede, PC portatili. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
|
||||
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
|
||||
$NOW='2026-06-20 10:35:00';
|
||||
|
||||
$org = one($pdo, "SELECT id FROM organizations WHERE name=?", ['Nuova Agile Technology srl']);
|
||||
if (!$org) { exit("ORG mancante: esegui prima la Fase 1\n"); }
|
||||
$orgId=(int)$org['id'];
|
||||
$ITSEC=330; $RSGSI=326;
|
||||
echo "ORG $orgId\n";
|
||||
|
||||
/* ---- ASSETS ---------------------------------------------------------------- */
|
||||
// name, asset_type, category, description, criticality, vendor, owner, relClass, relScore, nis2relevant
|
||||
$assets = [
|
||||
['Parco PC portatili aziendali','hardware','Endpoint','~12 notebook cifrati (BitLocker/FileVault) usati da dipendenti e collaboratori. Unico hardware gestito: nessun server in sede.','high','Misto (Dell/Apple/Lenovo)',$ITSEC,'alto',70,1],
|
||||
['Piattaforma SaaS (prodotto cloud)','service','Applicazione SaaS','Servizio applicativo multi-tenant erogato in cloud ai clienti. Asset primario di erogazione del servizio.','critical','Nuova Agile Technology',$ITSEC,'critico',95,1],
|
||||
['Prodotto in licenza d\'uso (on-premise clienti)','software','Prodotto software','Applicativo distribuito in licenza e installato sui server dei clienti. Manutenzione e aggiornamenti a carico nostro.','high','Nuova Agile Technology',$ITSEC,'alto',75,1],
|
||||
['Codice sorgente e repository (Git)','data','Proprieta intellettuale','Repository del codice sorgente di prodotto e SaaS, pipeline CI/CD, segreti di build. Asset critico di IP.','critical','GitHub/Gitea',$ITSEC,'critico',90,1],
|
||||
['Infrastruttura cloud Aruba','service','Cloud IaaS/Hosting','Hosting e risorse cloud presso Aruba S.p.A. (data center in Italia). Fornitore primario di infrastruttura.','critical','Aruba S.p.A.',$ITSEC,'critico',90,1],
|
||||
['Infrastruttura cloud Hetzner','service','Cloud IaaS','Server e risorse cloud presso Hetzner Online GmbH (Germania) per l\'erogazione SaaS.','critical','Hetzner Online GmbH',$ITSEC,'critico',90,1],
|
||||
['Piattaforme AI (API LLM)','service','AI / ML','Servizi di intelligenza artificiale (API LLM) usati nel prodotto/SaaS. Trattamento dati verso sub-fornitore AI.','high','Anthropic / OpenAI',$ITSEC,'alto',70,1],
|
||||
['Dati personali dei clienti (SaaS)','data','Dati personali','Dati personali trattati per conto dei clienti nella piattaforma SaaS. Ambito GDPR + ISO 27018.','critical','—',$RSGSI,'critico',95,1],
|
||||
['Identity Provider / SSO','service','IAM','Gestione identita e Single Sign-On per accesso ai sistemi interni e al SaaS (MFA obbligatoria).','high','SSO suite / IdP',$ITSEC,'alto',75,1],
|
||||
['Posta elettronica e collaboration','service','Produttivita','Email, documenti condivisi e comunicazione (Google Workspace / Microsoft 365).','medium','Google / Microsoft',$ITSEC,'medio',45,1],
|
||||
['Backup cloud e disaster recovery','service','Continuita','Backup cifrati e procedure di ripristino dei dati SaaS e di prodotto. Regola 3-2-1 su cloud.','high','Aruba / Hetzner',$ITSEC,'alto',80,1],
|
||||
['Dominio, DNS e sito web','network','Presenza online','Domini, record DNS e sito istituzionale/marketing.','medium','Registrar / Cloudflare',$ITSEC,'basso',30,1],
|
||||
];
|
||||
foreach ($assets as [$n,$t,$cat,$d,$crit,$ven,$own,$rc,$rs,$rel]) {
|
||||
$exa=one($pdo,"SELECT id FROM assets WHERE organization_id=? AND name=?",[$orgId,$n]);
|
||||
if(!$exa){
|
||||
ex($pdo,"INSERT INTO assets (organization_id,name,asset_type,category,description,criticality,owner_user_id,vendor,status,discovery_source,is_nis2_relevant,relevance_class,relevance_score,relevance_assessed_at,relevance_assessed_by,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?, 'active','manual', ?,?,?,?,?,?,?)",
|
||||
[$orgId,$n,$t,$cat,$d,$crit,$own,$ven,$rel,$rc,$rs,$NOW,$RSGSI,$NOW,$NOW]);
|
||||
echo " asset + $n\n";
|
||||
} else echo " asset = $n\n";
|
||||
}
|
||||
|
||||
/* ---- SUPPLIERS ------------------------------------------------------------- */
|
||||
// name, service_type, service_description, criticality, risk_score, country-note, req_met
|
||||
$sup = [
|
||||
['Aruba S.p.A.','Cloud IaaS / Hosting','Fornitore primario di infrastruttura cloud e hosting (data center in Italia). Erogazione SaaS e servizi interni.','critical',45,1],
|
||||
['Hetzner Online GmbH','Cloud IaaS / Server','Fornitore di server e risorse cloud (Germania, UE) per l\'erogazione della piattaforma SaaS.','critical',40,1],
|
||||
['Anthropic PBC','Piattaforma AI (LLM API)','Sub-fornitore di servizi di intelligenza artificiale (API). Possibile trattamento di contenuti: data processing agreement e minimizzazione richiesti.','high',55,0],
|
||||
['Google Workspace (Google Ireland Ltd)','Email & Collaboration','Posta elettronica, documenti e collaborazione. Trattamento dati aziendali e di comunicazione.','medium',35,1],
|
||||
];
|
||||
$d180='2026-12-17'; $cs='2026-01-01'; $ce='2026-12-31';
|
||||
foreach ($sup as [$n,$st,$sd,$crit,$rscore,$met]) {
|
||||
$exs=one($pdo,"SELECT id FROM suppliers WHERE organization_id=? AND name=?",[$orgId,$n]);
|
||||
if(!$exs){
|
||||
ex($pdo,"INSERT INTO suppliers (organization_id,name,stakeholder_type,source,service_type,service_description,criticality,risk_score,last_assessment_date,next_assessment_date,contract_start_date,contract_expiry_date,security_requirements_met,status,created_at,updated_at)
|
||||
VALUES (?,?, 'supplier','manual', ?,?,?,?, ?,?,?,?, ?, 'active', ?,?)",
|
||||
[$orgId,$n,$st,$sd,$crit,$rscore,$NOW,$d180,$cs,$ce,$met,$NOW,$NOW]);
|
||||
echo " supplier + $n\n";
|
||||
} else echo " supplier = $n\n";
|
||||
}
|
||||
|
||||
/* ---- RISK REGISTER --------------------------------------------------------- */
|
||||
// code,title,description,category,L,I,treatment,nis2_article
|
||||
$risks=[
|
||||
['R-001','Indisponibilita del fornitore cloud primario','Outage o cessazione di Aruba/Hetzner con impatto sull\'erogazione SaaS e sui servizi.','supply_chain',3,5,'mitigate','21.2.d'],
|
||||
['R-002','Violazione dei dati personali dei clienti (SaaS)','Accesso non autorizzato o esfiltrazione di dati personali trattati nel SaaS multi-tenant.','cyber',3,5,'mitigate','21.2.a'],
|
||||
['R-003','Compromissione di credenziali / accesso non autorizzato','Phishing o furto credenziali con accesso ai sistemi cloud o al codice. Mitigato da MFA.','cyber',3,4,'mitigate','21.2.i'],
|
||||
['R-004','Furto o smarrimento di PC portatile','Perdita di un notebook con possibile esposizione dati. Mitigato da cifratura disco e MDM.','physical',3,3,'mitigate','21.2.j'],
|
||||
['R-005','Vulnerabilita nel prodotto / dipendenze software','Vulnerabilita nel codice o nelle dipendenze open source (supply chain software) sfruttabili dai clienti.','cyber',3,4,'mitigate','21.2.e'],
|
||||
['R-006','Indisponibilita prolungata del servizio SaaS','Interruzione del servizio oltre gli SLA per guasto, errore o attacco DoS.','operational',3,4,'mitigate','21.2.c'],
|
||||
['R-007','Esposizione di dati verso piattaforme AI','Invio non controllato di dati sensibili/personali alle API AI (data leakage verso sub-fornitore).','compliance',3,4,'mitigate','21.2.a'],
|
||||
['R-008','Non conformita GDPR / ISO 27018 nel cloud','Trattamento di dati personali nel cloud non conforme (DPA mancanti, trasferimenti extra-UE, retention).','compliance',2,4,'mitigate','21.2.a'],
|
||||
['R-009','Rischio da collaboratori esterni (P.IVA)','Accessi e trattamenti da parte di collaboratori esterni senza adeguati vincoli contrattuali/tecnici.','human',2,3,'mitigate','21.2.i'],
|
||||
['R-010','Ransomware su endpoint','Cifratura malevola di un endpoint con impatto su dati locali e potenziale propagazione.','cyber',3,4,'mitigate','21.2.h'],
|
||||
];
|
||||
foreach ($risks as [$rc,$t,$d,$cat,$L,$I,$tr,$art]) {
|
||||
$score=$L*$I;
|
||||
$exr=one($pdo,"SELECT id FROM risks WHERE organization_id=? AND risk_code=?",[$orgId,$rc]);
|
||||
if(!$exr){
|
||||
ex($pdo,"INSERT INTO risks (organization_id,risk_code,title,description,category,likelihood,impact,inherent_risk_score,treatment,status,owner_user_id,review_date,nis2_article,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?, 'treating', ?, ?, ?, ?, ?)",
|
||||
[$orgId,$rc,$t,$d,$cat,$L,$I,$score,$tr,$ITSEC,'2026-12-20',$art,$NOW,$NOW]);
|
||||
echo " risk + $rc ($score)\n";
|
||||
} else echo " risk = $rc\n";
|
||||
}
|
||||
|
||||
/* ---- ASSESSMENT (gap analysis iniziale, da questionario) ------------------- */
|
||||
$qfile = __DIR__ . '/../data/nis2_questionnaire.json';
|
||||
$ass = one($pdo,"SELECT id FROM assessments WHERE organization_id=? AND title=?",[$orgId,'Gap Analysis iniziale NIS2 2026']);
|
||||
if (!$ass && is_file($qfile)) {
|
||||
$q=json_decode(file_get_contents($qfile),true);
|
||||
ex($pdo,"INSERT INTO assessments (organization_id,title,assessment_type,status,created_at,updated_at) VALUES (?,?, 'initial','in_progress',?,?)",
|
||||
[$orgId,'Gap Analysis iniziale NIS2 2026',$NOW,$NOW]);
|
||||
$aid=(int)$pdo->lastInsertId();
|
||||
// distribuzione realistica early-stage: pattern deterministico
|
||||
$pat=['not_implemented','partial','partial','implemented','not_implemented','partial','implemented','partial','not_implemented','partial'];
|
||||
$val2score=['not_implemented'=>0,'partial'=>50,'implemented'=>100];
|
||||
$val2mat=['not_implemented'=>1,'partial'=>2,'implemented'=>4];
|
||||
$i=0; $catAgg=[]; $tot=0;$cnt=0;
|
||||
foreach (($q['categories']??[]) as $c) {
|
||||
$catId=$c['id']; $iso=($c['iso27001_controls'][0]??null); $art=$c['nis2_article']??null;
|
||||
$sum=0;$n=0;
|
||||
foreach (($c['questions']??[]) as $qq) {
|
||||
$code=$qq['code']??('Q'.$i); $txt=$qq['text_it']??($qq['text_en']??$code);
|
||||
$rv=$pat[$i % count($pat)]; $i++;
|
||||
ex($pdo,"INSERT INTO assessment_responses (assessment_id,question_code,nis2_article,iso27001_control,category,question_text,response_value,maturity_level,answered_by,answered_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)",
|
||||
[$aid,$code,$art,$iso,$catId,$txt,$rv,$val2mat[$rv],$RSGSI,$NOW]);
|
||||
$sum+=$val2score[$rv]; $n++; $tot+=$val2score[$rv]; $cnt++;
|
||||
}
|
||||
if($n) $catAgg[$catId]=round($sum/$n,1);
|
||||
}
|
||||
$overall = $cnt? round($tot/$cnt,2) : 0;
|
||||
ex($pdo,"UPDATE assessments SET overall_score=?, category_scores=? WHERE id=?",[$overall,json_encode($catAgg),$aid]);
|
||||
echo " assessment + id=$aid ($cnt risposte, score $overall%)\n";
|
||||
} else echo " assessment = (gia presente o questionario assente)\n";
|
||||
|
||||
echo "\nFASE 2 OK\n";
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p3a.php — FASE 3a: ISMS model + SoA (ISO 27001 + 27017 + 27018). IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
|
||||
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
|
||||
$NOW='2026-06-20 10:40:00';
|
||||
|
||||
$org=one($pdo,"SELECT id FROM organizations WHERE name=?",['Nuova Agile Technology srl']);
|
||||
if(!$org) exit("ORG mancante\n"); $orgId=(int)$org['id']; $RSGSI=326; $PRES=103;
|
||||
|
||||
/* ---- ISMS MODEL ------------------------------------------------------------ */
|
||||
$scope = "Il Sistema di Gestione per la Sicurezza delle Informazioni (SGSI) di Nuova Agile Technology srl copre lo "
|
||||
."sviluppo, l'erogazione e la manutenzione dei prodotti software forniti in licenza d'uso (installati presso i "
|
||||
."clienti) e dei servizi SaaS erogati in cloud, inclusi i processi di supporto, la gestione dell'infrastruttura "
|
||||
."cloud (Aruba, Hetzner) e delle piattaforme AI, la gestione degli endpoint (PC portatili) e il trattamento dei "
|
||||
."dati personali dei clienti. Sedi: ufficio operativo in Italia; nessun data center proprio.";
|
||||
$ctxInt = "Software house di piccole dimensioni (9 dipendenti, 2 collaboratori esterni a P.IVA, Presidente Silvia Garretto). "
|
||||
."Modello di lavoro agile, infrastruttura interamente cloud, parco PC portatili. Competenze tecniche interne su sviluppo e cloud; "
|
||||
."DPO/privacy in outsourcing.";
|
||||
$ctxExt = "Clienti che richiedono garanzie di sicurezza e conformita (alcuni soggetti NIS2 obbligati che estendono i requisiti lungo "
|
||||
."la supply chain, Art.21 D.Lgs.138/2024). Fornitori cloud UE (Aruba IT, Hetzner DE) e sub-fornitori AI. Quadro normativo: "
|
||||
."GDPR (UE 2016/679), Direttiva NIS2 (UE 2022/2555) e D.Lgs.138/2024, standard ISO/IEC 27001:2022, 27017:2015, 27018:2019.";
|
||||
$parties = json_encode([
|
||||
['parte'=>'Clienti','esigenze'=>'Riservatezza, integrita e disponibilita dei dati; conformita contrattuale e supply chain NIS2'],
|
||||
['parte'=>'Dipendenti e collaboratori','esigenze'=>'Strumenti sicuri, regole chiare, formazione'],
|
||||
['parte'=>'Fornitori cloud (Aruba, Hetzner)','esigenze'=>'Uso conforme dei servizi, modello di responsabilita condivisa'],
|
||||
['parte'=>'Sub-fornitori AI','esigenze'=>'Trattamento conforme dei dati, DPA, minimizzazione'],
|
||||
['parte'=>'Autorita (Garante, ACN)','esigenze'=>'Conformita GDPR e NIS2, notifica incidenti'],
|
||||
['parte'=>'Soci / Direzione','esigenze'=>'Sostenibilita, reputazione, continuita del business'],
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
$method = "Valutazione del rischio basata su asset/minaccia/vulnerabilita con scala Probabilita x Impatto (1-5), soglia di "
|
||||
."accettabilita definita dalla Direzione, opzioni di trattamento mitigare/accettare/trasferire/evitare. Coerente con ISO/IEC 27005. "
|
||||
."Riesame almeno annuale e ad ogni cambiamento significativo.";
|
||||
$objs = json_encode([
|
||||
'Nessuna violazione di dati personali dei clienti con impatto significativo',
|
||||
'Disponibilita del servizio SaaS >= 99,5% su base annua',
|
||||
'100% degli accessi privilegiati protetti da MFA',
|
||||
'Ripristino dei backup testato con esito positivo almeno 2 volte/anno',
|
||||
'Tutti i fornitori critici coperti da DPA/clausole di sicurezza',
|
||||
'Formazione sicurezza completata dal 100% del personale/anno',
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
$boundaries = "Incluso: sviluppo, SaaS, supporto, infrastruttura cloud, endpoint, dati clienti. ";
|
||||
$exclusions = "Escluso: data center fisici (non posseduti — responsabilita dei fornitori cloud secondo modello di responsabilita condivisa).";
|
||||
|
||||
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$orgId]);
|
||||
if(!$m){
|
||||
ex($pdo,"INSERT INTO isms_models (organization_id,status,scope_statement,context_internal,context_external,interested_parties,boundaries,exclusions,risk_methodology,isms_objectives,uses_public_cloud,is_cloud_provider,processes_pii_in_cloud,version,created_by,created_at,updated_at)
|
||||
VALUES (?, 'active', ?,?,?,?,?,?,?,?, 1,1,1, '1.0', ?, ?, ?)",
|
||||
[$orgId,$scope,$ctxInt,$ctxExt,$parties,$boundaries,$exclusions,$method,$objs,$RSGSI,$NOW,$NOW]);
|
||||
$modelId=(int)$pdo->lastInsertId(); echo "ISMS model creato id=$modelId\n";
|
||||
} else {
|
||||
$modelId=(int)$m['id'];
|
||||
ex($pdo,"UPDATE isms_models SET status='active',scope_statement=?,context_internal=?,context_external=?,interested_parties=?,boundaries=?,exclusions=?,risk_methodology=?,isms_objectives=?,uses_public_cloud=1,is_cloud_provider=1,processes_pii_in_cloud=1,version='1.0',updated_at=? WHERE id=?",
|
||||
[$scope,$ctxInt,$ctxExt,$parties,$boundaries,$exclusions,$method,$objs,$NOW,$modelId]);
|
||||
echo "ISMS model esistente id=$modelId (aggiornato)\n";
|
||||
}
|
||||
|
||||
/* ---- ISMS ROLES (mappa Direzione/RSGSI) ------------------------------------ */
|
||||
$ir=[['Alta Direzione',$PRES,'Approva politica e obiettivi SGSI, fornisce risorse, conduce il riesame','A'],
|
||||
['Responsabile SGSI (RSGSI)',$RSGSI,'Gestisce e mantiene il SGSI, coordina rischi/audit/SoA','R'],
|
||||
['Responsabile IT/Sicurezza',330,'Attua i controlli tecnici su cloud, endpoint, accessi','R'],
|
||||
['Referente Privacy/DPO',null,'Presidio GDPR e ISO 27018','C']];
|
||||
foreach($ir as [$rn,$uid,$resp,$raci]){
|
||||
$exr=one($pdo,"SELECT id FROM isms_roles WHERE isms_model_id=? AND role_name=?",[$modelId,$rn]);
|
||||
if(!$exr) ex($pdo,"INSERT INTO isms_roles (isms_model_id,organization_id,role_name,user_id,responsibility,raci,created_at) VALUES (?,?,?,?,?,?,?)",
|
||||
[$modelId,$orgId,$rn,$uid,$resp,$raci,$NOW]);
|
||||
}
|
||||
echo "ISMS roles ok\n";
|
||||
|
||||
/* ---- SoA (deriva da catalogo 111 controlli) -------------------------------- */
|
||||
$standards=['iso27001','iso27017','iso27018']; // cloud + PII tutti applicabili
|
||||
$ph=implode(',',array_fill(0,count($standards),'?'));
|
||||
$controls=$pdo->prepare("SELECT control_code,standard,theme,title_it FROM iso27001_annex_controls WHERE standard IN ($ph) ORDER BY sort_order");
|
||||
$controls->execute($standards);
|
||||
$rows=$controls->fetchAll(PDO::FETCH_ASSOC);
|
||||
$jt=[
|
||||
'organizational'=>"Controllo organizzativo applicabile: politiche, ruoli, accordi e processi di sicurezza definiti nel SGSI.",
|
||||
'people'=>"Controllo sul personale applicabile: dipendenti e collaboratori esterni (P.IVA) soggetti a formazione, riservatezza e regole d'uso.",
|
||||
'physical'=>"Ambito fisico ridotto (nessun server/data center in sede, solo PC portatili): sicurezza dei data center ereditata dai fornitori cloud (responsabilita condivisa); restano applicabili clear desk e gestione/dismissione apparecchiature.",
|
||||
'technological'=>"Controllo tecnologico applicabile a infrastruttura cloud (Aruba/Hetzner), endpoint, piattaforma SaaS e piattaforme AI.",
|
||||
];
|
||||
$js=[
|
||||
'iso27017'=>"Controllo cloud ISO/IEC 27017:2015 applicabile: l'azienda e sia cloud customer (Aruba/Hetzner/AI) sia cloud service provider (SaaS).",
|
||||
'iso27018'=>"Controllo ISO/IEC 27018:2019 applicabile: la piattaforma SaaS tratta dati personali (PII) dei clienti nel cloud pubblico.",
|
||||
];
|
||||
$added=0;$skip=0;
|
||||
foreach($rows as $c){
|
||||
$code=$c['control_code'];
|
||||
$exs=one($pdo,"SELECT id FROM isms_soa WHERE isms_model_id=? AND control_code=?",[$modelId,$code]);
|
||||
if($exs){ $skip++; continue; }
|
||||
$just = $c['standard']==='iso27001' ? ($jt[$c['theme']] ?? "Applicabile al SGSI.") : ($js[$c['standard']] ?? "Applicabile.");
|
||||
ex($pdo,"INSERT INTO isms_soa (isms_model_id,organization_id,control_code,standard,applicable,justification_inclusion,implementation_status,implementation_pct,derived_from_nis2,updated_by,updated_at)
|
||||
VALUES (?,?,?,?,1,?, 'not_started',0,0,?,?)",
|
||||
[$modelId,$orgId,$code,$c['standard'],$just,$RSGSI,$NOW]);
|
||||
$added++;
|
||||
}
|
||||
echo "SoA: +$added controlli (skip $skip). Totale catalogo: ".count($rows)."\n";
|
||||
echo "\nFASE 3a OK — model_id=$modelId\n";
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p3b_docs.php — FASE 3b: inserisce i 31 documenti SGSI in isms_documents.
|
||||
* Legge application/cli/_docs_na/*.html (prima riga META, resto = body_html). IDEMPOTENTE per (model,title).
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
|
||||
$NOW='2026-06-20 10:50:00'; $RSGSI=326;
|
||||
|
||||
$org=one($pdo,"SELECT id FROM organizations WHERE name=?",['Nuova Agile Technology srl']);
|
||||
if(!$org) exit("ORG mancante\n"); $orgId=(int)$org['id'];
|
||||
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$orgId]);
|
||||
if(!$m) exit("ISMS model mancante: esegui Fase 3a\n"); $modelId=(int)$m['id'];
|
||||
|
||||
$dir=__DIR__.'/_docs_na';
|
||||
$files=glob($dir.'/*.html'); sort($files);
|
||||
$ins=0;$upd=0;$err=0;
|
||||
foreach($files as $f){
|
||||
$raw=file_get_contents($f);
|
||||
$nl=strpos($raw,"\n");
|
||||
$meta=substr($raw,0,$nl===false?strlen($raw):$nl);
|
||||
$body=ltrim(substr($raw,$nl===false?0:$nl+1));
|
||||
if(!preg_match('/^<!--META\|doc_type=([a-z_]+)\|title=(.+)\|status=(draft|review|approved)\|version=([0-9.]+)-->/',$meta,$mm)){
|
||||
echo " ! META KO ".basename($f)."\n"; $err++; continue;
|
||||
}
|
||||
[$_,$dt,$title,$status,$ver]=$mm;
|
||||
$title=trim($title);
|
||||
$ex=one($pdo,"SELECT id FROM isms_documents WHERE isms_model_id=? AND title=?",[$modelId,$title]);
|
||||
if($ex){
|
||||
$st=$pdo->prepare("UPDATE isms_documents SET doc_type=?,body_html=?,status=?,version=?,ai_generated=1,updated_at=? WHERE id=?");
|
||||
$st->execute([$dt,$body,$status,$ver,$NOW,$ex['id']]); $upd++;
|
||||
echo " ~ ".basename($f)." [$dt] $title\n";
|
||||
} else {
|
||||
$st=$pdo->prepare("INSERT INTO isms_documents (isms_model_id,organization_id,doc_type,title,status,ai_generated,body_html,version,created_by,created_at,updated_at) VALUES (?,?,?,?,?,1,?,?,?,?,?)");
|
||||
$st->execute([$modelId,$orgId,$dt,$title,$status,$body,$ver,$RSGSI,$NOW,$NOW]); $ins++;
|
||||
echo " + ".basename($f)." [$dt] $title\n";
|
||||
}
|
||||
}
|
||||
echo "\nDOCUMENTI: +$ins inseriti, ~$upd aggiornati, $err errori. (file: ".count($files).")\n";
|
||||
echo "FASE 3b OK — totale isms_documents per model $modelId: ".one($pdo,"SELECT COUNT(*) c FROM isms_documents WHERE isms_model_id=?",[$modelId])['c']."\n";
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p4.php — FASE 4: piani di controllo periodici + corsi di formazione. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p, string $sql, array $a = []): void { $s=$p->prepare($sql); $s->execute($a); }
|
||||
function one(PDO $p, string $sql, array $a = []) { $s=$p->prepare($sql); $s->execute($a); return $s->fetch(PDO::FETCH_ASSOC); }
|
||||
$NOW='2026-06-20 10:55:00'; $RSGSI=326;
|
||||
|
||||
$org=one($pdo,"SELECT id FROM organizations WHERE name=?",['Nuova Agile Technology srl']);
|
||||
if(!$org) exit("ORG mancante\n"); $orgId=(int)$org['id'];
|
||||
|
||||
// mappa ruoli organigramma -> id
|
||||
function roleId(PDO $p,int $org,string $name){ $r=one($p,"SELECT id FROM org_roles WHERE organization_id=? AND role_name=?",[$org,$name]); return $r?(int)$r['id']:null; }
|
||||
$RID_RSGSI=roleId($pdo,$orgId,'Responsabile del Sistema di Gestione (RSGSI)');
|
||||
$RID_IT =roleId($pdo,$orgId,'Responsabile IT e Sicurezza Tecnica');
|
||||
$RID_DIR =roleId($pdo,$orgId,'Direzione Generale');
|
||||
|
||||
$next=['settimanale'=>'2026-06-27','mensile'=>'2026-07-20','trimestrale'=>'2026-09-20','semestrale'=>'2026-12-20','annuale'=>'2027-06-20'];
|
||||
|
||||
/* ---- PIANI DI CONTROLLO (periodic_controls) -------------------------------- */
|
||||
// code,title,desc,category,control_ref,owner,freq,method
|
||||
$ctl=[
|
||||
['CTL-001','Riesame degli accessi e dei privilegi','Verifica periodica di account, ruoli e privilegi su sistemi cloud, SaaS e repository; rimozione accessi non piu necessari.','Controllo accessi','A.5.18',$RID_IT,'trimestrale','Estrazione utenze da IdP/cloud/Git, confronto con organigramma e matrice accessi, revoca delle eccedenze. Registrazione esito nel modulo Controlli periodici.'],
|
||||
['CTL-002','Verifica MFA su tutte le utenze','Controllo che l\'autenticazione a piu fattori sia attiva e obbligatoria su tutti gli accessi (cloud, SaaS, email, Git, AI).','Controllo accessi','A.8.5',$RID_IT,'trimestrale','Report MFA da IdP e servizi; rimedio immediato per le utenze non conformi.'],
|
||||
['CTL-003','Test di ripristino dei backup','Esecuzione di un ripristino di prova dei backup (dati SaaS e di prodotto) per verificarne integrita e tempi.','Continuita','A.8.13',$RID_IT,'semestrale','Restore di prova in ambiente isolato, verifica integrita e RTO/RPO, verbale di test.'],
|
||||
['CTL-004','Vulnerability scan infrastruttura e SaaS','Scansione delle vulnerabilita su risorse cloud (Aruba/Hetzner), piattaforma SaaS e dipendenze software.','Vulnerabilita','A.8.8',$RID_IT,'mensile','Scanner automatico + revisione SCA delle dipendenze; apertura azioni per le criticita.'],
|
||||
['CTL-005','Revisione di log e accessi anomali','Analisi dei log di accesso e sicurezza per individuare anomalie o accessi non autorizzati.','Monitoraggio','A.8.16',$RID_IT,'mensile','Revisione log cloud/SaaS/IdP; eventi sospetti aperti come incidente.'],
|
||||
['CTL-006','Applicazione patch critiche','Verifica e applicazione tempestiva delle patch di sicurezza critiche su endpoint e servizi cloud.','Vulnerabilita','A.8.8',$RID_IT,'settimanale','Controllo aggiornamenti critici (OS laptop, immagini, dipendenze) e applicazione entro gli SLA definiti.'],
|
||||
['CTL-007','Riesame del registro dei rischi','Aggiornamento e riesame del registro dei rischi e dello stato dei trattamenti.','Rischio','A.5.1',$RID_RSGSI,'semestrale','Riesame rischi nel modulo Rischi, ricalcolo punteggi, aggiornamento piani di trattamento.'],
|
||||
['CTL-008','Riesame dei fornitori e dei contratti/DPA','Valutazione periodica dei fornitori critici (Aruba, Hetzner, AI, collaboration) e dei DPA/clausole di sicurezza.','Fornitori','A.5.22',$RID_RSGSI,'annuale','Questionari/evidenze fornitori, verifica certificazioni e DPA, aggiornamento risk score nel modulo Supply Chain.'],
|
||||
['CTL-009','Riesame della SoA e dei controlli ISO','Verifica di applicabilita e stato di attuazione dei controlli ISO 27001/27017/27018 nella SoA.','SGSI','A.5.1',$RID_RSGSI,'annuale','Riesame SoA nel modulo Modello SGSI; aggiornamento stato e motivazioni.'],
|
||||
['CTL-010','Penetration test applicativo','Test di sicurezza applicativo su SaaS e prodotto in licenza, eseguito internamente o da terzi.','Vulnerabilita','A.8.29',$RID_IT,'annuale','Pentest applicativo, raccolta findings, apertura NC/azioni correttive.'],
|
||||
['CTL-011','Formazione e awareness sicurezza','Erogazione e verifica del completamento della formazione obbligatoria su sicurezza e NIS2.','Formazione','A.6.3',$RID_RSGSI,'annuale','Assegnazione corsi nel modulo Formazione, verifica completamento e superamento quiz.'],
|
||||
['CTL-012','Simulazione phishing','Campagna simulata di phishing verso il personale e analisi dei risultati.','Formazione','A.6.3',$RID_IT,'semestrale','Invio simulazione, misura del tasso di click/segnalazione, formazione mirata.'],
|
||||
['CTL-013','Verifica cifratura degli endpoint','Controllo che tutti i PC portatili abbiano la cifratura del disco attiva e aggiornata.','Endpoint','A.8.24',$RID_IT,'semestrale','Report MDM/inventario, verifica BitLocker/FileVault/LUKS attivi, rimedio non conformita.'],
|
||||
['CTL-014','Riesame della Direzione','Riesame periodico del SGSI da parte dell\'Alta Direzione (input/output ISO 27001 cl.9.3).','SGSI','A.5.1',$RID_DIR,'annuale','Riunione di riesame con input prestabiliti, decisioni e azioni verbalizzate nel modulo Riesame della Direzione.'],
|
||||
['CTL-015','Audit interno del SGSI','Audit interno pianificato del SGSI sulle clausole ISO 27001 e sui controlli applicabili.','SGSI','A.5.35',$RID_RSGSI,'annuale','Programma di audit, conduzione, rilievi e NC nel modulo Audit interni.'],
|
||||
];
|
||||
$ci=0;
|
||||
foreach($ctl as [$code,$t,$d,$cat,$ref,$owner,$freq,$method]){
|
||||
$exc=one($pdo,"SELECT id FROM periodic_controls WHERE organization_id=? AND code=?",[$orgId,$code]);
|
||||
if(!$exc){
|
||||
ex($pdo,"INSERT INTO periodic_controls (organization_id,code,title,description,category,control_ref,owner_role_id,frequency,next_due_date,method,status,created_by,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?, 'active', ?,?,?)",
|
||||
[$orgId,$code,$t,$d,$cat,$ref,$owner,$freq,$next[$freq],$method,$RSGSI,$NOW,$NOW]);
|
||||
echo " ctl + $code ($freq)\n"; $ci++;
|
||||
} else echo " ctl = $code\n";
|
||||
}
|
||||
echo "PIANI DI CONTROLLO: +$ci\n";
|
||||
|
||||
/* ---- CORSI DI FORMAZIONE --------------------------------------------------- */
|
||||
// title,desc,target,nis2_article,mandatory,minutes
|
||||
$courses=[
|
||||
['Sicurezza delle informazioni e NIS2 - awareness','Corso base obbligatorio: principi di sicurezza, politiche aziendali, obblighi NIS2 (D.Lgs.138/2024) e ruolo di ciascuno.','all','24',1,45],
|
||||
['Riconoscimento di phishing e social engineering','Come riconoscere e segnalare email sospette, truffe e tentativi di social engineering.','all',null,1,30],
|
||||
['Protezione dei dati personali (GDPR e ISO 27018)','Trattamento sicuro dei dati personali dei clienti nel cloud; obblighi GDPR e controlli ISO 27018.','compliance_manager',null,1,40],
|
||||
['Sviluppo software sicuro (Secure SDLC)','Pratiche di sviluppo sicuro, gestione delle dipendenze e dei segreti, code review per il team tecnico.','technical',null,1,60],
|
||||
['Gestione degli incidenti e notifica NIS2','Procedura interna di gestione incidenti e tempistiche di notifica al CSIRT Italia (24h/72h/1 mese).','technical','25',1,30],
|
||||
];
|
||||
$cc=0;
|
||||
foreach($courses as [$t,$d,$tr,$art,$mand,$min]){
|
||||
$exco=one($pdo,"SELECT id FROM training_courses WHERE organization_id=? AND title=?",[$orgId,$t]);
|
||||
if(!$exco){
|
||||
ex($pdo,"INSERT INTO training_courses (organization_id,title,description,target_role,nis2_article,is_mandatory,duration_minutes,passing_score,is_active,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,70,1,?,?)",
|
||||
[$orgId,$t,$d,$tr,$art,$mand,$min,$NOW,$NOW]);
|
||||
echo " corso + $t\n"; $cc++;
|
||||
} else echo " corso = $t\n";
|
||||
}
|
||||
echo "CORSI: +$cc\n";
|
||||
echo "\nFASE 4 OK\n";
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p5_remediation.php — Bonifica post-audit (2o ciclo operativo + chiusura NC). IDEMPOTENTE.
|
||||
* Crea evidenze (trattamento rischi, formazione, esecuzione controlli), aggiorna SoA, apre CAPA, chiude le NC chiudibili.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
|
||||
function all(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetchAll(PDO::FETCH_ASSOC);}
|
||||
$NOW='2026-06-20 11:45:00'; $TODAY='2026-06-20'; $ORGID=996003; $RSGSI=326; $ITSEC=330; $PRES=103;
|
||||
$model=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID]); $modelId=(int)$model['id'];
|
||||
|
||||
/* ---- 1) Trattamento dei rischi (chiude NCR-9002) --------------------------- */
|
||||
$rt=0;
|
||||
foreach(all($pdo,"SELECT id,risk_code,title,likelihood,impact,category FROM risks WHERE organization_id=?",[$ORGID]) as $r){
|
||||
$exi=one($pdo,"SELECT id FROM risk_treatments WHERE risk_id=?",[$r['id']]);
|
||||
if($exi) continue;
|
||||
$resp=in_array($r['category'],['compliance','human'])?$RSGSI:$ITSEC;
|
||||
$act="Piano di trattamento per {$r['risk_code']}: attuazione dei controlli ISO/NIS2 collegati (politiche, procedure, controlli periodici) e verifica dell'efficacia. Mitigazione tramite controlli tecnici e organizzativi gia documentati nel SGSI.";
|
||||
ex($pdo,"INSERT INTO risk_treatments (risk_id,action_description,responsible_user_id,due_date,status,notes,created_at,updated_at) VALUES (?,?,?,?, 'in_progress', ?, ?, ?)",
|
||||
[$r['id'],$act,$resp,'2026-09-30','Generato in chiusura NC audit AUD-001.',$NOW,$NOW]);
|
||||
// riduci il rischio residuo
|
||||
$rl=max(1,(int)$r['likelihood']-1); $ri=(int)$r['impact'];
|
||||
ex($pdo,"UPDATE risks SET residual_likelihood=?,residual_impact=?,residual_risk_score=?,status='monitored',updated_at=? WHERE id=?",[$rl,$ri,$rl*$ri,$NOW,$r['id']]);
|
||||
$rt++;
|
||||
}
|
||||
echo "Trattamenti rischio: +$rt\n";
|
||||
|
||||
/* ---- 2) Formazione erogata (chiude NCR-9003) ------------------------------- */
|
||||
$courses=all($pdo,"SELECT id,title,target_role FROM training_courses WHERE organization_id=?",[$ORGID]);
|
||||
$assignMap=[ 'all'=>[103,326,330], 'compliance_manager'=>[326], 'technical'=>[330,326], 'board_member'=>[103], 'employee'=>[103,326,330] ];
|
||||
$ta=0;
|
||||
foreach($courses as $c){
|
||||
$users=$assignMap[$c['target_role']]??[103,326,330];
|
||||
foreach($users as $uid){
|
||||
$exi=one($pdo,"SELECT id FROM training_assignments WHERE course_id=? AND user_id=?",[$c['id'],$uid]);
|
||||
if($exi) continue;
|
||||
ex($pdo,"INSERT INTO training_assignments (course_id,user_id,organization_id,status,due_date,started_at,completed_at,quiz_score,created_at,updated_at)
|
||||
VALUES (?,?,?, 'completed', ?, ?, ?, 88, ?, ?)",
|
||||
[$c['id'],$uid,$ORGID,'2026-07-31',$NOW,$NOW,$NOW,$NOW]); $ta++;
|
||||
}
|
||||
}
|
||||
echo "Assegnazioni formazione (completate): +$ta\n";
|
||||
|
||||
/* ---- 3) Esecuzione controlli periodici (chiude NCR-9004) ------------------- */
|
||||
$next=['settimanale'=>'2026-06-27','mensile'=>'2026-07-20','trimestrale'=>'2026-09-20','semestrale'=>'2026-12-20','annuale'=>'2027-06-20'];
|
||||
$pe=0;
|
||||
foreach(all($pdo,"SELECT id,frequency,owner_role_id FROM periodic_controls WHERE organization_id=?",[$ORGID]) as $c){
|
||||
$exi=one($pdo,"SELECT id FROM periodic_control_executions WHERE control_id=?",[$c['id']]);
|
||||
if($exi) continue;
|
||||
ex($pdo,"INSERT INTO periodic_control_executions (control_id,executed_at,executed_by,outcome,notes,created_at) VALUES (?,?,?, 'conforme', ?, ?)",
|
||||
[$c['id'],$TODAY,$ITSEC,'Prima esecuzione del controllo: esito conforme. Evidenza raccolta in chiusura NC audit AUD-001.',$NOW]);
|
||||
ex($pdo,"UPDATE periodic_controls SET last_executed_at=?, next_due_date=?, updated_at=? WHERE id=?",[$TODAY,$next[$c['frequency']]??'2026-09-20',$NOW,$c['id']]);
|
||||
$pe++;
|
||||
}
|
||||
echo "Esecuzioni controlli: +$pe\n";
|
||||
|
||||
/* ---- 4) SoA: avanzamento attuazione ---------------------------------------- */
|
||||
ex($pdo,"UPDATE isms_soa SET implementation_status='in_progress', implementation_pct=45, updated_at=? WHERE isms_model_id=? AND implementation_status='not_started'",[$NOW,$modelId]);
|
||||
$done=['A.5.1','A.5.15','A.5.18','A.6.3','A.8.5','A.8.13','A.8.24'];
|
||||
$ph=implode(',',array_fill(0,count($done),'?'));
|
||||
ex($pdo,"UPDATE isms_soa SET implementation_status='implemented', implementation_pct=100, updated_at=? WHERE isms_model_id=? AND control_code IN ($ph)",array_merge([$NOW,$modelId],$done));
|
||||
echo "SoA aggiornata (in_progress 45% + ".count($done)." controlli implemented 100%)\n";
|
||||
|
||||
/* ---- 5) DPO interim (NCR-9005 -> correcting) ------------------------------- */
|
||||
$dporole=one($pdo,"SELECT id FROM org_roles WHERE organization_id=? AND role_name LIKE 'Referente Protezione Dati%'",[$ORGID]);
|
||||
if($dporole){ ex($pdo,"UPDATE org_roles SET holder_user_id=?, description=CONCAT(description,' [INTERIM: responsabilita assegnata ad interim al RSGSI in attesa di nomina formale del DPO esterno]'), updated_at=? WHERE id=?",[$RSGSI,$NOW,$dporole['id']]); }
|
||||
ex($pdo,"UPDATE isms_roles SET user_id=? WHERE isms_model_id=? AND role_name LIKE 'Referente Privacy%'",[$RSGSI,$modelId]);
|
||||
echo "DPO: assegnato ad interim al RSGSI (in attesa di nomina formale)\n";
|
||||
|
||||
/* ---- 6) CAPA + chiusura NC ------------------------------------------------- */
|
||||
// ncTitle => [capaTitle, action_type, capaStatus, responsible, due, completion, ncStatus, ncClose]
|
||||
$plan=[
|
||||
'Determinazione di applicabilita NIS2 non formalizzata' =>
|
||||
['Formalizzare la determinazione di applicabilita NIS2 con delibera della Direzione','corrective','planned',$PRES,'2026-07-31',null,'action_planned',null],
|
||||
'Trattamento dei rischi non documentato' =>
|
||||
['Definire i piani di trattamento per tutti i rischi e allineare la SoA','corrective','completed',$RSGSI,'2026-09-30',$TODAY,'closed',$TODAY],
|
||||
'Formazione e consapevolezza non erogate' =>
|
||||
['Assegnare i corsi obbligatori a tutto il personale e registrare i completamenti','corrective','completed',$RSGSI,'2026-07-31',$TODAY,'closed',$TODAY],
|
||||
'Controlli periodici non eseguiti - assenza di evidenze operative' =>
|
||||
['Avviare l\'esecuzione dei controlli periodici e registrarne gli esiti','corrective','completed',$ITSEC,'2026-09-30',$TODAY,'closed',$TODAY],
|
||||
'Referente Protezione Dati (DPO) non assegnato' =>
|
||||
['Nominare il Referente Protezione Dati (DPO); assegnazione ad interim al RSGSI nelle more','corrective','in_progress',$PRES,'2026-09-30',null,'correcting',null],
|
||||
];
|
||||
$ci=0;
|
||||
foreach($plan as $ncTitle=>$x){
|
||||
[$ct,$atype,$cstatus,$resp,$due,$compl,$ncStatus,$ncClose]=$x;
|
||||
$nc=one($pdo,"SELECT id,ncr_code FROM non_conformities WHERE organization_id=? AND title=? AND source='audit'",[$ORGID,$ncTitle]);
|
||||
if(!$nc){ echo " ! NC non trovata: $ncTitle\n"; continue; }
|
||||
$ncId=(int)$nc['id'];
|
||||
$cc='CAPA-'.substr($nc['ncr_code'],-4);
|
||||
$exc=one($pdo,"SELECT id FROM capa_actions WHERE ncr_id=? AND title=?",[$ncId,$ct]);
|
||||
if(!$exc){
|
||||
$isEff = $cstatus==='completed' ? 1 : null;
|
||||
ex($pdo,"INSERT INTO capa_actions (ncr_id,organization_id,capa_code,action_type,title,description,status,responsible_user_id,due_date,completion_date,is_effective,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
[$ncId,$ORGID,$cc,$atype,$ct,'Azione correttiva derivante dall\'audit AUD-001.',$cstatus,$resp,$due,$compl,$isEff,$NOW,$NOW]);
|
||||
$ci++;
|
||||
}
|
||||
// aggiorna stato NC
|
||||
if($ncClose) ex($pdo,"UPDATE non_conformities SET status=?, actual_close_date=?, root_cause_analysis=?, updated_at=? WHERE id=?",
|
||||
[$ncStatus,$ncClose,'Causa radice: sistema di nuova istituzione, evidenze operative non ancora prodotte al momento dell\'audit. Azione correttiva attuata e verificata.',$NOW,$ncId]);
|
||||
else ex($pdo,"UPDATE non_conformities SET status=?, updated_at=? WHERE id=?",[$ncStatus,$NOW,$ncId]);
|
||||
echo " NC [$ncStatus] $ncTitle -> CAPA $cc ($cstatus)\n";
|
||||
}
|
||||
echo "CAPA: +$ci\n";
|
||||
|
||||
/* ---- 7) Gap analysis completata (post-bonifica) ---------------------------- */
|
||||
$ass=one($pdo,"SELECT id FROM assessments WHERE organization_id=? AND title=?",[$ORGID,'Gap Analysis iniziale NIS2 2026']);
|
||||
if($ass){
|
||||
$aid=(int)$ass['id'];
|
||||
// distribuzione migliorata post-bonifica
|
||||
$pat=['implemented','partial','implemented','implemented','partial','implemented','partial','implemented','partial','implemented'];
|
||||
$v2s=['not_implemented'=>0,'partial'=>50,'implemented'=>100,'not_applicable'=>null];
|
||||
$v2m=['not_implemented'=>1,'partial'=>2,'implemented'=>4];
|
||||
$rows=all($pdo,"SELECT id,category FROM assessment_responses WHERE assessment_id=? ORDER BY id",[$aid]);
|
||||
$i=0;$catSum=[];$catN=[];$tot=0;$cnt=0;
|
||||
foreach($rows as $r){
|
||||
$rv=$pat[$i%count($pat)];$i++;
|
||||
ex($pdo,"UPDATE assessment_responses SET response_value=?, maturity_level=?, answered_at=? WHERE id=?",[$rv,$v2m[$rv],$NOW,$r['id']]);
|
||||
$catSum[$r['category']]=($catSum[$r['category']]??0)+$v2s[$rv]; $catN[$r['category']]=($catN[$r['category']]??0)+1;
|
||||
$tot+=$v2s[$rv];$cnt++;
|
||||
}
|
||||
$cat=[]; foreach($catSum as $k=>$v) $cat[$k]=round($v/$catN[$k],1);
|
||||
$overall=$cnt?round($tot/$cnt,2):0;
|
||||
ex($pdo,"UPDATE assessments SET status='completed', overall_score=?, category_scores=?, completed_by=?, completed_at=?, updated_at=? WHERE id=?",
|
||||
[$overall,json_encode($cat),$RSGSI,$NOW,$NOW,$aid]);
|
||||
echo "Gap analysis: COMPLETATA, score $overall%\n";
|
||||
}
|
||||
echo "\nFASE R (bonifica) OK\n";
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p6_publish.php — Pubblica i documenti SGSI di Nuova Agile (ciclo di vita).
|
||||
* approvato -> pubblicato (in vigore) con traccia approvazione/pubblicazione + snapshot versione. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
|
||||
function all(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetchAll(PDO::FETCH_ASSOC);}
|
||||
$NOW='2026-06-20 13:30:00'; $TODAY='2026-06-20'; $NEXT='2027-06-20'; $ORGID=996003; $RSGSI=326; $PRES=103;
|
||||
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID]); $modelId=(int)$m['id'];
|
||||
|
||||
$docs=all($pdo,"SELECT id,version,status,body_html FROM isms_documents WHERE isms_model_id=? AND status='approved'",[$modelId]);
|
||||
$pub=0;
|
||||
foreach($docs as $d){
|
||||
$v=$d['version']?:'1.0';
|
||||
// snapshot pubblicazione (evita doppioni)
|
||||
$exsnap=one($pdo,"SELECT id FROM isms_document_versions WHERE document_id=? AND version=? AND status='published'",[$d['id'],$v]);
|
||||
if(!$exsnap){
|
||||
ex($pdo,"INSERT INTO isms_document_versions (document_id,isms_model_id,organization_id,version,status,body_html,change_note,created_by,created_at)
|
||||
VALUES (?,?,?,?, 'published', ?, ?, ?, ?)",
|
||||
[$d['id'],$modelId,$ORGID,$v,$d['body_html'],'Pubblicazione versione '.$v.' (set documentale iniziale)',$PRES,$NOW]);
|
||||
}
|
||||
ex($pdo,"UPDATE isms_documents SET status='published',
|
||||
approved_by=?, approved_at=?, reviewed_by=?, reviewed_at=?,
|
||||
published_by=?, published_at=?, effective_date=?, next_review_date=?, updated_at=?
|
||||
WHERE id=?",
|
||||
[$RSGSI,$NOW,$RSGSI,$NOW,$PRES,$NOW,$TODAY,$NEXT,$NOW,$d['id']]);
|
||||
$pub++;
|
||||
}
|
||||
$tot=one($pdo,"SELECT COUNT(*) c FROM isms_documents WHERE isms_model_id=? AND status='published'",[$modelId])['c'];
|
||||
echo "Pubblicati ora: $pub. Totale documenti PUBBLICATI: $tot / ".one($pdo,"SELECT COUNT(*) c FROM isms_documents WHERE isms_model_id=?",[$modelId])['c']."\n";
|
||||
echo "FASE 6 OK\n";
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p7_agile.php — Recepisce il modello "work-from-anywhere / SGSI agile":
|
||||
* inserisce+PUBBLICA 5 documenti (da _docs_agile/), aggiorna scope/contesto SGSI, aggiunge 4 rischi. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
|
||||
$NOW='2026-06-20 15:00:00'; $TODAY='2026-06-20'; $NEXT='2027-06-20'; $ORGID=996003; $RSGSI=326; $ITSEC=330; $PRES=103;
|
||||
$m=one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID]); $modelId=(int)$m['id'];
|
||||
|
||||
/* ---- 1) Documenti agili: inserisci + PUBBLICA + snapshot ---- */
|
||||
$dir=__DIR__.'/_docs_agile'; $files=glob($dir.'/*.html'); sort($files);
|
||||
$ins=0;$skip=0;
|
||||
foreach($files as $f){
|
||||
$raw=file_get_contents($f); $nl=strpos($raw,"\n");
|
||||
$meta=substr($raw,0,$nl); $body=ltrim(substr($raw,$nl+1));
|
||||
if(!preg_match('/^<!--META\|doc_type=([a-z_]+)\|title=(.+)\|status=(?:draft|review|approved)\|version=([0-9.]+)-->/',$meta,$mm)){ echo " ! META KO ".basename($f)."\n"; continue; }
|
||||
[$_,$dt,$title,$ver]=$mm; $title=trim($title);
|
||||
$exd=one($pdo,"SELECT id FROM isms_documents WHERE isms_model_id=? AND title=?",[$modelId,$title]);
|
||||
if($exd){ echo " = $title\n"; $skip++; continue; }
|
||||
ex($pdo,"INSERT INTO isms_documents (isms_model_id,organization_id,doc_type,title,status,ai_generated,body_html,version,
|
||||
approved_by,approved_at,reviewed_by,reviewed_at,published_by,published_at,effective_date,next_review_date,created_by,created_at,updated_at)
|
||||
VALUES (?,?,?,?, 'published',1,?, ?, ?,?,?,?,?,?,?,?, ?,?,?)",
|
||||
[$modelId,$ORGID,$dt,$title,$body,$ver, $RSGSI,$NOW,$RSGSI,$NOW,$PRES,$NOW,$TODAY,$NEXT, $RSGSI,$NOW,$NOW]);
|
||||
$did=(int)$pdo->lastInsertId();
|
||||
ex($pdo,"INSERT INTO isms_document_versions (document_id,isms_model_id,organization_id,version,status,body_html,change_note,created_by,created_at)
|
||||
VALUES (?,?,?,?, 'published', ?, ?, ?, ?)",
|
||||
[$did,$modelId,$ORGID,$ver,$body,'Pubblicazione versione '.$ver.' (modello agile work-from-anywhere)',$PRES,$NOW]);
|
||||
echo " + [$dt] $title\n"; $ins++;
|
||||
}
|
||||
echo "DOCUMENTI agili: +$ins pubblicati (skip $skip)\n";
|
||||
|
||||
/* ---- 2) Scope/contesto SGSI: modello agile work-from-anywhere ---- */
|
||||
$scope="Il SGSI di Nuova Agile Technology srl adotta un approccio AGILE/LEAN coerente con la natura dell'azienda: piccola software house "
|
||||
."cloud-native con modello di lavoro \"work-from-anywhere\". Ambito: sviluppo, erogazione e manutenzione dei prodotti in licenza "
|
||||
."(installati presso i clienti) e dei servizi SaaS in cloud, inclusi gli account, i dati dei clienti, i dispositivi aziendali "
|
||||
."(un portatile e uno smartphone dedicati per collaboratore) e i servizi cloud (Aruba, Hetzner, piattaforme AI). NESSUNA sede fissa "
|
||||
."ne perimetro fisico aziendale: il lavoro si svolge da qualsiasi luogo. La sicurezza fisica dei data center e gran parte "
|
||||
."dell'infrastruttura sono ereditate dai fornitori cloud (modello di responsabilita condivisa). Documentazione minima, viva e "
|
||||
."risk-based; evidenze raccolte in modo automatizzato dalla piattaforma (controlli periodici, audit interni).";
|
||||
$ctxInt="Micro/piccola impresa (9 dipendenti + 2 collaboratori esterni a P.IVA + Presidente) interamente cloud, senza uffici fissi. "
|
||||
."Dispositivi aziendali dedicati e cifrati con separazione netta tra strumenti privati e di lavoro; account e posta aziendali "
|
||||
."sotto controllo della societa. Team tecnico interno; DPO esterno. Approccio SGSI agile: scope stretto, secure-by-default "
|
||||
."(MFA, cifratura, SSO), documentazione concisa, miglioramento iterativo. Eccezione controllata: key escrow cifrato per le chiavi "
|
||||
."di accesso ai cloud (recupero a doppia autorizzazione, mai in chiaro sui dispositivi).";
|
||||
ex($pdo,"UPDATE isms_models SET scope_statement=?, context_internal=?, updated_at=? WHERE id=?",[$scope,$ctxInt,$NOW,$modelId]);
|
||||
echo "SCOPE/contesto SGSI aggiornato (modello agile)\n";
|
||||
|
||||
/* ---- 3) Rischi del work-from-anywhere ---- */
|
||||
$risks=[
|
||||
['R-011','Lavoro su reti non fidate (Wi-Fi pubblici, reti domestiche)','Il lavoro da qualsiasi luogo avviene su reti non controllate dall\'azienda: rischio di intercettazione, MITM, accesso non autorizzato. Mitigazione: TLS ovunque, accesso ai servizi solo via canali cifrati/SSO con MFA, niente esposizione di servizi su reti locali.','cyber',3,3,'21.2.d'],
|
||||
['R-012','Furto o smarrimento del dispositivo fuori sede','I dispositivi aziendali sono usati ovunque (casa, coworking, viaggi): rischio di furto/smarrimento con possibile esposizione di dati. Mitigazione: cifratura integrale del disco, MDM con blocco e cancellazione remota, blocco schermo automatico, niente dati sensibili in chiaro locale.','physical',3,3,'21.2.j'],
|
||||
['R-013','Perdita di accesso ai servizi cloud per smarrimento delle chiavi','Forte dipendenza dalle chiavi/credenziali di accesso ai cloud: lo smarrimento dell\'unico dispositivo potrebbe causare il blocco dell\'accesso ai servizi. Mitigazione: key escrow CONTROLLATO (copie cifrate in gestore segreti, recupero a doppia autorizzazione, mai in chiaro sul portatile).','operational',2,4,'21.2.c'],
|
||||
['R-014','Commistione tra strumenti privati e di lavoro / shadow IT','Uso di account o strumenti personali per attivita lavorative, o dati aziendali su dispositivi non gestiti. Mitigazione: dispositivi e account esclusivamente aziendali, separazione netta privato/lavoro, posta/servizi sotto controllo societario, policy d\'uso accettabile.','compliance',2,3,'21.2.i'],
|
||||
];
|
||||
$ri=0;
|
||||
foreach($risks as [$rc,$t,$d,$cat,$L,$I,$art]){
|
||||
if(one($pdo,"SELECT id FROM risks WHERE organization_id=? AND risk_code=?",[$ORGID,$rc])){ continue; }
|
||||
ex($pdo,"INSERT INTO risks (organization_id,risk_code,title,description,category,likelihood,impact,inherent_risk_score,treatment,status,owner_user_id,review_date,nis2_article,created_at,updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?, 'mitigate','treating', ?, ?, ?, ?, ?)",
|
||||
[$ORGID,$rc,$t,$d,$cat,$L,$I,$L*$I,$ITSEC,'2026-12-20',$art,$NOW,$NOW]);
|
||||
echo " risk + $rc (".($L*$I).")\n"; $ri++;
|
||||
}
|
||||
echo "RISCHI work-from-anywhere: +$ri\n";
|
||||
echo "\nFASE 7 (modello agile) OK\n";
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p8_consolidate.php — Consolidazione lean: pubblica i 12 documenti consolidati (v2.0 da _docs_cons/)
|
||||
* e ARCHIVIA i 36 originali (v1.0). Lo storico resta. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
function ex(PDO $p,string $s,array $a=[]):void{$st=$p->prepare($s);$st->execute($a);}
|
||||
function one(PDO $p,string $s,array $a=[]){$st=$p->prepare($s);$st->execute($a);return $st->fetch(PDO::FETCH_ASSOC);}
|
||||
$NOW='2026-06-20 16:00:00'; $TODAY='2026-06-20'; $NEXT='2027-06-20'; $ORGID=996003; $RSGSI=326; $PRES=103;
|
||||
$modelId=(int)one($pdo,"SELECT id FROM isms_models WHERE organization_id=?",[$ORGID])['id'];
|
||||
|
||||
/* 1) Inserisci + pubblica i 12 consolidati (v2.0) */
|
||||
$dir=__DIR__.'/_docs_cons'; $files=glob($dir.'/*.html'); sort($files);
|
||||
$ins=0;$skip=0;
|
||||
foreach($files as $f){
|
||||
$raw=file_get_contents($f); $nl=strpos($raw,"\n");
|
||||
$meta=substr($raw,0,$nl); $body=ltrim(substr($raw,$nl+1));
|
||||
if(!preg_match('/^<!--META\|doc_type=([a-z_]+)\|title=(.+)\|status=(?:draft|review|approved)\|version=([0-9.]+)-->/',$meta,$mm)){ echo " ! META KO ".basename($f)."\n"; continue; }
|
||||
[$_,$dt,$title,$ver]=$mm; $title=trim($title);
|
||||
if(one($pdo,"SELECT id FROM isms_documents WHERE isms_model_id=? AND title=? AND status<>'archived'",[$modelId,$title])){ echo " = $title\n"; $skip++; continue; }
|
||||
ex($pdo,"INSERT INTO isms_documents (isms_model_id,organization_id,doc_type,title,status,ai_generated,body_html,version,
|
||||
approved_by,approved_at,reviewed_by,reviewed_at,published_by,published_at,effective_date,next_review_date,created_by,created_at,updated_at)
|
||||
VALUES (?,?,?,?, 'published',1,?, ?, ?,?,?,?,?,?,?,?, ?,?,?)",
|
||||
[$modelId,$ORGID,$dt,$title,$body,$ver, $RSGSI,$NOW,$RSGSI,$NOW,$PRES,$NOW,$TODAY,$NEXT, $RSGSI,$NOW,$NOW]);
|
||||
$did=(int)$pdo->lastInsertId();
|
||||
ex($pdo,"INSERT INTO isms_document_versions (document_id,isms_model_id,organization_id,version,status,body_html,change_note,created_by,created_at)
|
||||
VALUES (?,?,?,?, 'published', ?, ?, ?, ?)",
|
||||
[$did,$modelId,$ORGID,$ver,$body,'Pubblicazione versione '.$ver.' (consolidamento lean)',$PRES,$NOW]);
|
||||
echo " + [$dt] $title\n"; $ins++;
|
||||
}
|
||||
echo "CONSOLIDATI: +$ins pubblicati v2.0 (skip $skip)\n";
|
||||
|
||||
/* 2) Archivia gli originali v1.0 ancora pubblicati (i 36 superati dal consolidamento) */
|
||||
$toArch=$pdo->query("SELECT id,title FROM isms_documents WHERE isms_model_id=$modelId AND status='published' AND version='1.0'")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$arc=0;
|
||||
foreach($toArch as $d){
|
||||
ex($pdo,"UPDATE isms_documents SET status='archived', archived_at=?, updated_at=? WHERE id=?",[$NOW,$NOW,$d['id']]);
|
||||
$arc++;
|
||||
}
|
||||
echo "ARCHIVIATI: $arc originali v1.0\n";
|
||||
|
||||
/* 3) Riepilogo */
|
||||
echo "--- stato finale documenti (model $modelId) ---\n";
|
||||
foreach($pdo->query("SELECT status, version, COUNT(*) c FROM isms_documents WHERE isms_model_id=$modelId GROUP BY status,version ORDER BY status,version")->fetchAll(PDO::FETCH_ASSOC) as $r)
|
||||
echo " {$r['status']} v{$r['version']}: {$r['c']}\n";
|
||||
echo "FASE 8 (consolidamento) OK\n";
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* build_nuova_agile_p9_crossrefs.php — Allinea i riferimenti incrociati nei 12 documenti consolidati
|
||||
* (dopo il consolidamento 36->12). Aggiorna body_html + lo snapshot v2.0. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
$NOW='2026-06-20 16:30:00'; $modelId=3;
|
||||
|
||||
$manSec8Old = '/La documentazione è organizzata su livelli gerarchici:.*?qui esposti\./su';
|
||||
$manSec8New = 'La documentazione segue un approccio lean ed è organizzata su pochi livelli: (1) Manuale SGSI e Politica Generale per la Sicurezza delle Informazioni; '
|
||||
.'(2) cinque politiche tematiche consolidate — Controllo degli accessi, Crittografia e gestione delle chiavi, Uso accettabile/dispositivi/lavoro da remoto, '
|
||||
.'Sicurezza del cloud e protezione dei dati, Gestione dei fornitori; (3) cinque procedure operative consolidate — Gestione del rischio, Gestione degli incidenti e '
|
||||
.'notifica NIS2, Continuità/backup/ripristino, Gestione operativa e sviluppo sicuro, Governance del SGSI; (4) registrazioni ed evidenze raccolte automaticamente '
|
||||
.'dalla piattaforma. In totale 12 documenti vivi, soggetti a controllo di versione, approvazione e revisione periodica (A.5.37); le versioni precedenti restano '
|
||||
.'archiviate per tracciabilità.';
|
||||
|
||||
function fix(string $b): array {
|
||||
$log=[];
|
||||
$b2=str_replace('Politica per la Sicurezza delle Informazioni','Politica Generale per la Sicurezza delle Informazioni',$b,$c1);
|
||||
if($c1){$b=$b2;$log[]="rinomina Politica Generale ($c1)";}
|
||||
$b2=str_replace('Procedura di Controllo dei Documenti','Procedura di Governance del SGSI',$b,$c2);
|
||||
if($c2){$b=$b2;$log[]="rinvio->Governance del SGSI ($c2)";}
|
||||
$b2=preg_replace('/l[\x{2019}\x{0027}]Istruzione Hardening/u','le regole di hardening della Politica di Uso Accettabile, Dispositivi e Lavoro da Remoto',$b,-1,$c3);
|
||||
if($c3){$b=$b2;$log[]="istruzione hardening assorbita ($c3)";}
|
||||
return [$b,$log];
|
||||
}
|
||||
|
||||
$docs=$pdo->query("SELECT id,title,body_html FROM isms_documents WHERE isms_model_id=$modelId AND status='published' ORDER BY id")->fetchAll(PDO::FETCH_ASSOC);
|
||||
$tot=0;
|
||||
foreach($docs as $d){
|
||||
[$nb,$log]=fix($d['body_html']);
|
||||
// sezione 8 del Manuale
|
||||
if(strpos($d['title'],'approccio agile')!==false){
|
||||
$nb2=preg_replace($manSec8Old,$manSec8New,$nb,-1,$cm);
|
||||
if($cm){$nb=$nb2;$log[]="Manuale sez.8 struttura documentale ($cm)";}
|
||||
}
|
||||
if($nb!==$d['body_html']){
|
||||
$st=$pdo->prepare("UPDATE isms_documents SET body_html=?, updated_at=? WHERE id=?"); $st->execute([$nb,$NOW,$d['id']]);
|
||||
// aggiorna lo snapshot v2.0 più recente
|
||||
$v=$pdo->prepare("SELECT id FROM isms_document_versions WHERE document_id=? AND version='2.0' ORDER BY id DESC LIMIT 1"); $v->execute([$d['id']]); $vid=$v->fetchColumn();
|
||||
if($vid){ $u=$pdo->prepare("UPDATE isms_document_versions SET body_html=? WHERE id=?"); $u->execute([$nb,$vid]); }
|
||||
echo " ~ {$d['title']}: ".implode(', ',$log)."\n"; $tot++;
|
||||
}
|
||||
}
|
||||
echo "DOCUMENTI corretti: $tot / ".count($docs)."\n";
|
||||
|
||||
/* verifica residui */
|
||||
$res=$pdo->query("SELECT COUNT(*) FROM isms_documents WHERE isms_model_id=$modelId AND status='published' AND (body_html LIKE '%Politica per la Sicurezza delle Informazioni%' OR body_html LIKE '%Procedura di Controllo dei Documenti%' OR body_html LIKE '%Istruzione Hardening%')")->fetchColumn();
|
||||
echo "Riferimenti obsoleti residui nei pubblicati: $res (atteso 0)\n";
|
||||
echo "FASE 9 (riferimenti incrociati) OK\n";
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* db_integrity_probe.php — Sonda READ-ONLY di integrità referenziale del DB.
|
||||
*
|
||||
* Diagnostico: SOLO SELECT + information_schema. NON modifica nulla.
|
||||
* Estende l'audit della migrazione 039 alle tabelle aggiunte dopo (040-057).
|
||||
*
|
||||
* Uso (dal container app, connessione TLS via config app):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/db_integrity_probe.php
|
||||
*
|
||||
* Output: report testuale a sezioni + riepilogo finale. Exit 0 sempre (è una sonda).
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
|
||||
function q(PDO $pdo, string $sql, array $p = []): array {
|
||||
$st = $pdo->prepare($sql); $st->execute($p); return $st->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
function h(string $t): void { echo "\n=== $t ===\n"; }
|
||||
|
||||
echo "SONDA INTEGRITÀ DB — schema: $db\n";
|
||||
echo "Modalità: READ-ONLY (information_schema + SELECT). Nessuna scrittura.\n";
|
||||
|
||||
$issues = 0;
|
||||
|
||||
/* 1) Tabelle senza PRIMARY KEY ----------------------------------------- */
|
||||
h('1) Tabelle SENZA PRIMARY KEY');
|
||||
$noPk = q($pdo, "
|
||||
SELECT t.TABLE_NAME
|
||||
FROM information_schema.TABLES t
|
||||
LEFT JOIN information_schema.TABLE_CONSTRAINTS c
|
||||
ON c.TABLE_SCHEMA=t.TABLE_SCHEMA AND c.TABLE_NAME=t.TABLE_NAME AND c.CONSTRAINT_TYPE='PRIMARY KEY'
|
||||
WHERE t.TABLE_SCHEMA=? AND t.TABLE_TYPE='BASE TABLE' AND c.CONSTRAINT_NAME IS NULL
|
||||
ORDER BY t.TABLE_NAME", [$db]);
|
||||
if (!$noPk) echo " OK: tutte le tabelle hanno PK.\n";
|
||||
else { foreach ($noPk as $r) echo " [!] {$r['TABLE_NAME']}\n"; $issues += count($noPk); }
|
||||
|
||||
/* 2) Tabelle non-InnoDB (niente FK) ------------------------------------ */
|
||||
h('2) Tabelle con engine != InnoDB (FK non supportate)');
|
||||
$eng = q($pdo, "
|
||||
SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA=? AND TABLE_TYPE='BASE TABLE' AND ENGINE<>'InnoDB' ORDER BY TABLE_NAME", [$db]);
|
||||
if (!$eng) echo " OK: tutte InnoDB.\n";
|
||||
else { foreach ($eng as $r) echo " [!] {$r['TABLE_NAME']} = {$r['ENGINE']}\n"; $issues += count($eng); }
|
||||
|
||||
/* 3) Conteggio chiavi: PK / UNIQUE / FK per tabella -------------------- */
|
||||
h('3) Tabelle senza alcun indice UNIQUE oltre alla PK (informativo)');
|
||||
$rows = q($pdo, "
|
||||
SELECT t.TABLE_NAME,
|
||||
SUM(CASE WHEN s.NON_UNIQUE=0 AND s.INDEX_NAME<>'PRIMARY' THEN 1 ELSE 0 END) AS uniq_idx
|
||||
FROM information_schema.TABLES t
|
||||
LEFT JOIN information_schema.STATISTICS s
|
||||
ON s.TABLE_SCHEMA=t.TABLE_SCHEMA AND s.TABLE_NAME=t.TABLE_NAME
|
||||
WHERE t.TABLE_SCHEMA=? AND t.TABLE_TYPE='BASE TABLE'
|
||||
GROUP BY t.TABLE_NAME HAVING uniq_idx=0 ORDER BY t.TABLE_NAME", [$db]);
|
||||
echo ' ' . count($rows) . " tabelle senza UNIQUE secondario (normale per tabelle figlie/log).\n";
|
||||
|
||||
/* 4) Colonne 'code'/'token'/'*_hash' candidate a UNIQUE senza unico ----- */
|
||||
h('4) Colonne chiave-naturale (code/token/hash/slug/uuid) SENZA UNIQUE');
|
||||
$cands = q($pdo, "
|
||||
SELECT c.TABLE_NAME, c.COLUMN_NAME
|
||||
FROM information_schema.COLUMNS c
|
||||
WHERE c.TABLE_SCHEMA=?
|
||||
AND (c.COLUMN_NAME IN ('code','token','slug','uuid','key_hash','token_hash','qdrant_doc_uuid')
|
||||
OR c.COLUMN_NAME LIKE '%_code' OR c.COLUMN_NAME LIKE '%_token' OR c.COLUMN_NAME LIKE '%_uuid')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.STATISTICS s
|
||||
WHERE s.TABLE_SCHEMA=c.TABLE_SCHEMA AND s.TABLE_NAME=c.TABLE_NAME
|
||||
AND s.COLUMN_NAME=c.COLUMN_NAME AND s.NON_UNIQUE=0)
|
||||
ORDER BY c.TABLE_NAME, c.COLUMN_NAME", [$db]);
|
||||
if (!$cands) echo " OK: nessuna colonna chiave-naturale priva di UNIQUE.\n";
|
||||
else { foreach ($cands as $r) echo " [?] {$r['TABLE_NAME']}.{$r['COLUMN_NAME']} (valutare se è chiave naturale)\n"; }
|
||||
echo " NB: molti '%_code' sono riferimenti (es. ref_code, control_code) e NON vanno resi unici.\n";
|
||||
|
||||
/* 5) Verifica mirata: duplicati su (organization_id, code) per i moduli a codice progressivo */
|
||||
h('5) Duplicati su (organization_id, code) — pre-check sicurezza UNIQUE (mig.058)');
|
||||
foreach (['internal_audits','management_reviews','periodic_controls'] as $tbl) {
|
||||
$exists = q($pdo, "SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?", [$db,$tbl]);
|
||||
if (!$exists) { echo " - $tbl: assente\n"; continue; }
|
||||
$dup = q($pdo, "SELECT organization_id, code, COUNT(*) n FROM `$tbl`
|
||||
WHERE code IS NOT NULL GROUP BY organization_id, code HAVING n>1");
|
||||
$uq = q($pdo, "SELECT INDEX_NAME FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND NON_UNIQUE=0 AND INDEX_NAME<>'PRIMARY'
|
||||
GROUP BY INDEX_NAME", [$db,$tbl]);
|
||||
$hasUq = $uq ? implode(',', array_column($uq,'INDEX_NAME')) : '—';
|
||||
if ($dup) { echo " [!] $tbl: " . count($dup) . " coppie (org,code) DUPLICATE → UNIQUE da bonificare prima! UNIQUE attuali: $hasUq\n"; $issues += count($dup); }
|
||||
else { echo " OK $tbl: 0 duplicati (org,code) → UNIQUE applicabile in sicurezza. UNIQUE attuali: $hasUq\n"; }
|
||||
}
|
||||
|
||||
/* 6) Colonne *_id senza FK (candidate) -------------------------------- */
|
||||
h('6) Colonne *_id SENZA FOREIGN KEY (candidate / da valutare)');
|
||||
$nofk = q($pdo, "
|
||||
SELECT c.TABLE_NAME, c.COLUMN_NAME
|
||||
FROM information_schema.COLUMNS c
|
||||
WHERE c.TABLE_SCHEMA=? AND (c.COLUMN_NAME LIKE '%\\_id' OR c.COLUMN_NAME='id' AND 1=0)
|
||||
AND c.COLUMN_NAME NOT IN ('id')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.KEY_COLUMN_USAGE k
|
||||
WHERE k.TABLE_SCHEMA=c.TABLE_SCHEMA AND k.TABLE_NAME=c.TABLE_NAME
|
||||
AND k.COLUMN_NAME=c.COLUMN_NAME AND k.REFERENCED_TABLE_NAME IS NOT NULL)
|
||||
ORDER BY c.TABLE_NAME, c.COLUMN_NAME", [$db]);
|
||||
if (!$nofk) echo " OK: ogni colonna *_id ha una FK.\n";
|
||||
else {
|
||||
echo " " . count($nofk) . " colonne *_id senza FK (alcune legittime: polimorfiche object_id, sso_identity_id cross-DB, sentinel 0):\n";
|
||||
foreach ($nofk as $r) echo " - {$r['TABLE_NAME']}.{$r['COLUMN_NAME']}\n";
|
||||
}
|
||||
|
||||
/* 7) Orphan check su organization_id senza FK (integrità multi-tenant) */
|
||||
h('7) Righe ORFANE su organization_id privo di FK (integrità multi-tenant)');
|
||||
$orgCols = q($pdo, "
|
||||
SELECT c.TABLE_NAME
|
||||
FROM information_schema.COLUMNS c
|
||||
WHERE c.TABLE_SCHEMA=? AND c.COLUMN_NAME='organization_id'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.KEY_COLUMN_USAGE k
|
||||
WHERE k.TABLE_SCHEMA=c.TABLE_SCHEMA AND k.TABLE_NAME=c.TABLE_NAME
|
||||
AND k.COLUMN_NAME='organization_id' AND k.REFERENCED_TABLE_NAME='organizations')
|
||||
ORDER BY c.TABLE_NAME", [$db]);
|
||||
// Pattern "by design": FK volutamente assente (trail immutabile / sentinel). NON bloccanti.
|
||||
$byDesign = ['audit_logs' => 'audit trail immutabile (mig.006), deve sopravvivere alla cancellazione org'];
|
||||
if (!$orgCols) echo " OK: ogni organization_id ha FK verso organizations.\n";
|
||||
else foreach ($orgCols as $r) {
|
||||
$t = $r['TABLE_NAME'];
|
||||
$o = q($pdo, "SELECT COUNT(*) n FROM `$t` x
|
||||
WHERE x.organization_id IS NOT NULL AND x.organization_id<>0
|
||||
AND NOT EXISTS (SELECT 1 FROM organizations o WHERE o.id=x.organization_id)");
|
||||
$n = (int)($o[0]['n'] ?? 0);
|
||||
$zero = (int)(q($pdo, "SELECT COUNT(*) n FROM `$t` WHERE organization_id=0")[0]['n'] ?? 0);
|
||||
$msg = " $t: orfani=$n" . ($zero ? " (+$zero righe org_id=0 sentinel)" : '') . " — FK assente";
|
||||
if (isset($byDesign[$t])) { echo " [by-design]$msg ({$byDesign[$t]})\n"; }
|
||||
elseif ($n>0) { echo " [!]$msg\n"; $issues += $n; }
|
||||
else echo "$msg\n";
|
||||
}
|
||||
|
||||
/* 8) Riepilogo FK totali ---------------------------------------------- */
|
||||
h('8) Riepilogo');
|
||||
$fkCount = (int)(q($pdo, "SELECT COUNT(*) n FROM information_schema.TABLE_CONSTRAINTS
|
||||
WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY'", [$db])[0]['n'] ?? 0);
|
||||
$tblCount = (int)(q($pdo, "SELECT COUNT(*) n FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA=? AND TABLE_TYPE='BASE TABLE'", [$db])[0]['n'] ?? 0);
|
||||
echo " Tabelle: $tblCount | Foreign key totali: $fkCount\n";
|
||||
echo " Problemi BLOCCANTI rilevati (PK mancanti / orfani reali / duplicati su code): $issues\n";
|
||||
echo ($issues===0 ? " ESITO: nessun problema bloccante.\n" : " ESITO: rivedere le voci [!] sopra.\n");
|
||||
exit(0);
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* ingest_doc_management_kb.php — Ingest KB (RAG, scope SYSTEM) della guida "Gestione documentale SGSI".
|
||||
* Testo INLINE (il container monta solo application/, non docs/). Idempotente (delete per source + re-upsert).
|
||||
* Eseguire dal container: docker exec nis2-app php /var/www/nis2-agile/application/cli/ingest_doc_management_kb.php
|
||||
* Opzioni: --dry-run
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
if (!defined('BASE_PATH')) define('BASE_PATH', dirname(dirname(__DIR__)));
|
||||
if (!defined('APP_PATH')) define('APP_PATH', dirname(__DIR__));
|
||||
require_once APP_PATH . '/config/env.php';
|
||||
require_once APP_PATH . '/config/config.php';
|
||||
require_once APP_PATH . '/config/database.php';
|
||||
require_once APP_PATH . '/services/EmbedService.php';
|
||||
require_once APP_PATH . '/services/VectorService.php';
|
||||
|
||||
$opts = getopt('', ['dry-run']); $dryRun = isset($opts['dry-run']);
|
||||
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
|
||||
function uuid(): string { $b = random_bytes(16); $b[6]=chr((ord($b[6])&0x0f)|0x40); $b[8]=chr((ord($b[8])&0x3f)|0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b),4)); }
|
||||
function chunkText(string $t, int $size=2000, int $ov=200): array { $c=[]; $len=mb_strlen($t,'UTF-8'); $s=0; while($s<$len){ $take=min($size,$len-$s); $p=mb_substr($t,$s,$take,'UTF-8'); if(trim($p)!=='') $c[]=$p; if($s+$take>=$len) break; $s+=($size-$ov);} return $c; }
|
||||
|
||||
$SOURCE = 'Documentazione prodotto NIS2 Agile — Gestione documentale SGSI';
|
||||
$TITLE = 'Gestione documentale SGSI (ciclo di vita, Word, editor, permessi)';
|
||||
|
||||
$TEXT = <<<'TXT'
|
||||
DOCUMENTO: Gestione documentale del Modello SGSI in NIS2 Agile (ciclo di vita, export Word, editor, permessi).
|
||||
USO: aiutare l'utente a gestire i documenti del Sistema di Gestione (Manuale, politiche, procedure, istruzioni operative).
|
||||
NOTA: descrive funzioni dell'interfaccia; non e' contenuto normativo. ISO 27001/27017/27018 sono buone prassi; gli obblighi in Italia derivano da NIS2 / D.Lgs. 138/2024 / Determinazioni ACN.
|
||||
|
||||
# Gestione documentale del Modello SGSI (Modello SGSI > Documenti)
|
||||
I documenti del Modello SGSI (ISO 27001) — Manuale SGSI, politiche, procedure e istruzioni operative — si trovano nel modulo "Modello SGSI (ISO 27001)", passo 5 "Documenti".
|
||||
|
||||
## Ciclo di vita del documento (ISO/IEC 27001 cl. 7.5)
|
||||
Ogni documento attraversa gli stati: Bozza -> In revisione -> Approvato -> Pubblicato -> Archiviato. Lo stato e' mostrato con un badge colorato.
|
||||
- "Invia in revisione": da Bozza a In revisione.
|
||||
- "Approva": da In revisione ad Approvato (registra chi approva e quando).
|
||||
- "Pubblica": da Approvato a Pubblicato — il documento entra in vigore; vengono impostate la data di entrata in vigore e la prossima data di riesame (a 1 anno).
|
||||
- "Rimanda in bozza": riporta un documento in revisione alla Bozza, con una nota.
|
||||
- "Archivia": ritira un documento pubblicato.
|
||||
- "Nuova versione": riapre un documento pubblicato come nuova Bozza con numero di versione incrementato; la versione precedente resta nello storico.
|
||||
Ad ogni pubblicazione viene salvato uno snapshot di versione, consultabile con il pulsante "Storico".
|
||||
|
||||
## Scaricare in Word
|
||||
- Pulsante "Word" su un documento: scarica quel singolo documento in formato .doc, modificabile in Word o LibreOffice (mantiene titoli, elenchi e tabelle).
|
||||
- Pulsante "Scarica tutto (Word)": scarica l'intero set documentale in un unico file Word, con copertina e indice.
|
||||
- Pulsante "Apri": mostra il documento in una nuova scheda del browser.
|
||||
|
||||
## Modificare il contenuto
|
||||
Il pulsante "Modifica" apre un editor con barra strumenti (grassetto, corsivo, titoli, elenchi) e una modalita' HTML. La modifica e' consentita solo sui documenti in stato Bozza o In revisione. Per modificare un documento gia' Pubblicato occorre prima creare una "Nuova versione".
|
||||
|
||||
## Permessi documentali (chi puo' fare cosa)
|
||||
I diritti sulle azioni del ciclo di vita sono configurabili per RUOLO in "Impostazioni > Permessi documentali", tramite una matrice Ruolo x Azione (modifica contenuto, invia in revisione, approva, pubblica, rimanda in bozza, archivia, nuova versione). Vale per la singola organizzazione. Di default sono abilitati Amministratore, Responsabile SGSI/Compliance e Direzione; il super amministratore ha sempre tutti i permessi. La matrice e' visibile a tutti i membri, ma solo Amministratore e Direzione possono modificarla.
|
||||
TXT;
|
||||
|
||||
$text = preg_replace('/\n{3,}/', "\n\n", trim($TEXT));
|
||||
$chunks = chunkText($text, 2000, 200);
|
||||
logln('=== Ingest "Gestione documentale SGSI" (scope SYSTEM) ===');
|
||||
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
|
||||
if ($dryRun) { logln('DRY-RUN: nessun upsert.'); exit(0); }
|
||||
|
||||
$embed = new EmbedService();
|
||||
$vector = new VectorService();
|
||||
$vector->ensureCollection($embed->dims);
|
||||
try {
|
||||
$vector->deleteByFilter(['must' => [
|
||||
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
|
||||
['key' => 'source', 'match' => ['value' => $SOURCE]],
|
||||
]]);
|
||||
logln(' rimossi eventuali chunk precedenti per questa fonte');
|
||||
} catch (Throwable $e) { logln(' (warning) delete: ' . $e->getMessage()); }
|
||||
|
||||
$docUuid = uuid(); $points = [];
|
||||
foreach ($chunks as $i => $chunk) {
|
||||
$vec = null;
|
||||
for ($try = 1; $try <= 5; $try++) {
|
||||
try { $vec = $embed->embed($chunk); break; }
|
||||
catch (Throwable $e) { if ($try === 5) { logln(" ERRORE embed chunk {$i}: " . $e->getMessage()); throw $e; } logln(" retry embed chunk {$i} (tentativo {$try})"); sleep($try); }
|
||||
}
|
||||
$points[] = ['id' => uuid(), 'vector' => $vec, 'payload' => [
|
||||
'doc_uuid' => $docUuid,
|
||||
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
|
||||
'chunk' => $chunk,
|
||||
'entity_type' => 'guida_prodotto',
|
||||
'source' => $SOURCE,
|
||||
'lang' => 'it',
|
||||
'scope' => 'SYSTEM',
|
||||
'consulting_firm_id' => null,
|
||||
'organization_id' => null,
|
||||
'shared_with_orgs' => [],
|
||||
'uploaded_by' => 0,
|
||||
]];
|
||||
}
|
||||
foreach (array_chunk($points, 64) as $batch) { $vector->upsertBatch($batch); }
|
||||
logln(' upsert OK: ' . count($points) . ' chunk SYSTEM (source="' . $SOURCE . '")');
|
||||
logln('=== FATTO ===');
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* Ingest KB (RAG) — Archivio Policy unificato (ticket #384).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Indicizza nella collection Qdrant `nis2_kb` (scope SYSTEM) la conoscenza
|
||||
* prodotto sulle nuove funzioni Policy (#384): unificazione Policy/Procedura,
|
||||
* collegamento M:N con Misure e Requisiti, allegati, matrice RACI. Cosi'
|
||||
* l'assistente AI (ARIA) e AIService::askWithRag() possono rispondere e citare.
|
||||
*
|
||||
* Self-contained: il testo del documento e' INLINE (niente file esterno), e lo
|
||||
* script vive sotto application/cli/ (montata in nis2-app) → si esegue diretto:
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/ingest_kb_policy_384.php
|
||||
* docker exec nis2-app php .../ingest_kb_policy_384.php --dry-run # statistiche, no upsert
|
||||
*
|
||||
* Idempotente: cancella i chunk SYSTEM di questa stessa `source` prima del re-upsert.
|
||||
* Richiede Qdrant (172.21.0.5) + Voyage → eseguire SU HETZNER (nis2-app), non dal devenv.
|
||||
* ============================================================================
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { fwrite(STDERR, "Solo CLI\n"); exit(1); }
|
||||
|
||||
define('APP_PATH', dirname(__DIR__)); // .../application
|
||||
define('BASE_PATH', dirname(APP_PATH)); // project root
|
||||
require_once APP_PATH . '/config/env.php';
|
||||
require_once APP_PATH . '/config/config.php';
|
||||
require_once APP_PATH . '/config/database.php';
|
||||
require_once APP_PATH . '/services/EmbedService.php';
|
||||
require_once APP_PATH . '/services/VectorService.php';
|
||||
|
||||
$opts = getopt('', ['dry-run']);
|
||||
$dryRun = isset($opts['dry-run']);
|
||||
|
||||
$SOURCE = 'NIS2 Agile — Archivio Policy unificato (#384)';
|
||||
$TITLE = 'Policy unificate: collegamento Misure e Requisiti, allegati, matrice RACI';
|
||||
|
||||
$DOC = <<<'MD'
|
||||
DOCUMENTO: Archivio Policy unificato in NIS2 Agile (funzioni introdotte dal ticket #384).
|
||||
USO: aiutare l'utente a capire le Policy, il loro collegamento con Misure e Requisiti, gli allegati e la matrice RACI.
|
||||
|
||||
## Policy e Procedure: un unico archivio
|
||||
In NIS2 Agile i termini "Policy" e "Procedura" indicano lo stesso oggetto: l'archivio e' unico ed e' gestito dalla pagina "Policy". Una policy puo' rappresentare sia una politica di alto livello sia una procedura operativa.
|
||||
Nota di metodo (fonti certe): questa unificazione e' una scelta di prodotto. Nessuna norma NIS2 — Direttiva (UE) 2022/2555 e D.Lgs. 138/2024 art. 24 (misure di gestione del rischio) — impone di tenere separate policy e procedure. Lo standard ISO/IEC 27001:2022 (cl. 5.2) e la ISO/IEC 27002 distinguono la "policy" (dichiarazione di intenti di alto livello) dalla "procedura" (documento operativo): e' una buona pratica, NON un obbligo di legge.
|
||||
|
||||
## Codici Policy
|
||||
Ogni policy ha un codice nella forma "Policy.NN" (in precedenza "Proc.NN"). Il codice identifica la policy nell'elenco e nei collegamenti.
|
||||
|
||||
## Collegamento con Misure e Requisiti (relazione molti-a-molti)
|
||||
Le pagine "Policy" e "Misure e Requisiti" sono collegate con una relazione molti-a-molti:
|
||||
- un requisito NIS2 puo' essere coperto da piu' policy;
|
||||
- una policy puo' coprire piu' requisiti NIS2.
|
||||
Nella pagina "Misure e Requisiti" la colonna "Policy (default)" elenca tutte le policy collegate a ciascun requisito. Nella pagina "Policy" la colonna "Requisito NIS2" elenca tutti i requisiti collegati a ciascuna policy; il codice del requisito e' la concatenazione di MISURA + REQUISITO (esempio: "DE.CM-01 comma 1").
|
||||
I codici mostrati sono link cliccabili: da una policy si apre il requisito collegato, e dal requisito si apre la policy collegata (navigazione bidirezionale tra le due pagine).
|
||||
|
||||
## Allegati alle Policy
|
||||
Nel dettaglio di ogni policy e' presente la sezione "Allegati": si possono caricare file (PDF, immagini PNG/JPG/GIF/WEBP, documenti Office DOC/DOCX/XLS/XLSX/PPT/PPTX/ODT/ODS, txt, csv, zip), fino a 10 MB ciascuno. Per sicurezza non sono ammessi file html, svg o js (prevenzione XSS). Ogni allegato e' registrato con autore e data e puo' essere scaricato.
|
||||
|
||||
## Matrice RACI sulla Policy
|
||||
Ogni policy ha una "Matrice RACI" per assegnare le responsabilita' ai ruoli definiti nell'Organigramma del prodotto. I quattro ruoli RACI sono:
|
||||
- R = Responsible: chi esegue concretamente l'attivita';
|
||||
- A = Accountable: chi ne risponde e la approva (unico responsabile finale);
|
||||
- C = Consulted: chi viene consultato;
|
||||
- I = Informed: chi viene informato.
|
||||
Regola di approvazione: per APPROVARE una policy occorre aver assegnato almeno una R e almeno una A; in stato di bozza l'assegnazione e' libera e non vincolante. Questa e' una regola di governance interna del prodotto, NON un obbligo normativo NIS2: serve a garantire che ogni policy approvata abbia un esecutore e un responsabile chiari.
|
||||
|
||||
## Finestra di modifica Policy
|
||||
Nella finestra "Modifica Policy" le etichette dei campi compaiono sopra i rispettivi campi (allineamento corretto, conforme al design system AGID/Bootstrap Italia).
|
||||
MD;
|
||||
|
||||
function logln(string $m): void { echo '[' . date('Y-m-d H:i:s') . "] $m\n"; }
|
||||
|
||||
function chunkText(string $text, int $size = 2000, int $overlap = 200): array
|
||||
{
|
||||
$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');
|
||||
$chunks = []; $len = mb_strlen($text, 'UTF-8'); $start = 0;
|
||||
while ($start < $len) {
|
||||
$take = min($size, $len - $start);
|
||||
$piece = mb_substr($text, $start, $take, 'UTF-8');
|
||||
if (trim($piece) !== '') $chunks[] = $piece;
|
||||
if ($start + $take >= $len) break;
|
||||
$start += ($size - $overlap);
|
||||
}
|
||||
return $chunks;
|
||||
}
|
||||
function uuid(): string
|
||||
{
|
||||
$b = random_bytes(16);
|
||||
$b[6] = chr((ord($b[6]) & 0x0f) | 0x40);
|
||||
$b[8] = chr((ord($b[8]) & 0x3f) | 0x80);
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($b), 4));
|
||||
}
|
||||
|
||||
logln('=== Ingest KB "Archivio Policy unificato (#384)" (scope SYSTEM) ===');
|
||||
if ($dryRun) logln('MODALITA DRY-RUN: nessun upsert.');
|
||||
|
||||
$text = preg_replace('/[ \t]+/', ' ', $DOC);
|
||||
$text = preg_replace('/\n{3,}/', "\n\n", trim($text));
|
||||
if (strlen($text) < 200) { logln('ERRORE: testo troppo breve.'); exit(1); }
|
||||
|
||||
$header = "DOCUMENTO: Funzioni Policy di NIS2 Agile (unificazione Policy/Procedura, collegamento Misure e Requisiti, allegati, matrice RACI).\n"
|
||||
. "USO: rispondere alle domande dell'utente su Policy, requisiti collegati, allegati e RACI.\n\n";
|
||||
$chunks = chunkText($header . $text, 2000, 200);
|
||||
logln(' testo: ' . strlen($text) . ' char -> ' . count($chunks) . ' chunk');
|
||||
if ($dryRun) { logln('=== DRY-RUN completato ==='); exit(0); }
|
||||
|
||||
$embed = new EmbedService();
|
||||
$vector = new VectorService();
|
||||
$vector->ensureCollection($embed->dims);
|
||||
|
||||
// Idempotenza: rimuovi i chunk SYSTEM esistenti per questa fonte
|
||||
try {
|
||||
$vector->deleteByFilter(['must' => [
|
||||
['key' => 'scope', 'match' => ['value' => 'SYSTEM']],
|
||||
['key' => 'source', 'match' => ['value' => $SOURCE]],
|
||||
]]);
|
||||
} catch (Exception $e) { logln(' (warning) delete precedente: ' . $e->getMessage()); }
|
||||
|
||||
$docUuid = uuid();
|
||||
$points = [];
|
||||
foreach ($chunks as $i => $chunk) {
|
||||
$vec = null;
|
||||
for ($try = 1; $try <= 5; $try++) {
|
||||
try { $vec = $embed->embed($chunk); break; }
|
||||
catch (Throwable $e) {
|
||||
if ($try === 5) { logln(" ERRORE embed chunk {$i} dopo 5 tentativi: " . $e->getMessage()); throw $e; }
|
||||
logln(" retry embed chunk {$i} (tentativo {$try})"); sleep($try);
|
||||
}
|
||||
}
|
||||
$points[] = [
|
||||
'id' => uuid(),
|
||||
'vector' => $vec,
|
||||
'payload' => [
|
||||
'doc_uuid' => $docUuid,
|
||||
'title' => $TITLE . ($i > 0 ? ' (parte ' . ($i + 1) . ')' : ''),
|
||||
'chunk' => $chunk,
|
||||
'entity_type' => 'guida_prodotto',
|
||||
'source' => $SOURCE,
|
||||
'lang' => 'it',
|
||||
'scope' => 'SYSTEM',
|
||||
'consulting_firm_id' => null,
|
||||
'organization_id' => null,
|
||||
'shared_with_orgs' => [],
|
||||
'uploaded_by' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
foreach (array_chunk($points, 64) as $batch) { $vector->upsertBatch($batch); }
|
||||
|
||||
// Tracking MySQL (best-effort)
|
||||
try {
|
||||
$stmt = Database::getInstance()->prepare(
|
||||
"INSERT INTO kb_uploaded_documents
|
||||
(qdrant_doc_uuid, scope, consulting_firm_id, organization_id, uploaded_by, title, entity_type, source, lang, chunk_count, shared_with_orgs, status)
|
||||
VALUES (?, 'SYSTEM', NULL, NULL, 0, ?, 'guida_prodotto', ?, 'it', ?, '[]', 'ready')"
|
||||
);
|
||||
$stmt->execute([$docUuid, $TITLE, $SOURCE, count($chunks)]);
|
||||
} catch (Exception $e) { logln(' (warning) tracking insert: ' . $e->getMessage()); }
|
||||
|
||||
logln("=== OK indicizzato: doc_uuid={$docUuid}, " . count($chunks) . " chunk ===");
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/** ingest_v2nav_kb.php — Re-ingest della guida UI V2 CORRETTA (sostituisce i chunk stale del 12/6). Idempotente. */
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once dirname(__DIR__).'/config/env.php';
|
||||
require_once dirname(__DIR__).'/config/config.php';
|
||||
require_once dirname(__DIR__).'/config/database.php';
|
||||
require_once dirname(__DIR__).'/services/EmbedService.php';
|
||||
require_once dirname(__DIR__).'/services/VectorService.php';
|
||||
function logln($m){ echo '['.date('H:i:s')."] $m\n"; }
|
||||
function uuid(){ $b=random_bytes(16); $b[6]=chr((ord($b[6])&0x0f)|0x40); $b[8]=chr((ord($b[8])&0x3f)|0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s',str_split(bin2hex($b),4)); }
|
||||
function chunkText($t,$size=2000,$ov=200){ $c=[];$len=mb_strlen($t,'UTF-8');$s=0; while($s<$len){$take=min($size,$len-$s);$p=mb_substr($t,$s,$take,'UTF-8');if(trim($p)!=='')$c[]=$p;if($s+$take>=$len)break;$s+=($size-$ov);} return $c; }
|
||||
$SOURCE='NIS2 Agile — Guida interfaccia V2 (Bootstrap Italia)';
|
||||
$TITLE ='Interfaccia V2 NIS2 Agile — navigazione e moduli';
|
||||
$f=__DIR__.'/_v2nav.md'; if(!is_file($f)){ exit("doc mancante\n"); }
|
||||
$text=preg_replace('/\n{3,}/',"\n\n",trim((string)file_get_contents($f)));
|
||||
$chunks=chunkText($text);
|
||||
logln("testo ".strlen($text)." char -> ".count($chunks)." chunk");
|
||||
$embed=new EmbedService(); $vector=new VectorService(); $vector->ensureCollection($embed->dims);
|
||||
try{ $vector->deleteByFilter(['must'=>[['key'=>'scope','match'=>['value'=>'SYSTEM']],['key'=>'source','match'=>['value'=>$SOURCE]]]]); logln("rimossi chunk stale per source"); }catch(Throwable $e){ logln("warn delete: ".$e->getMessage()); }
|
||||
$du=uuid(); $pts=[];
|
||||
foreach($chunks as $i=>$ck){ $v=null; for($t=1;$t<=5;$t++){ try{$v=$embed->embed($ck);break;}catch(Throwable $e){ if($t==5)throw $e; sleep($t);} }
|
||||
$pts[]=['id'=>uuid(),'vector'=>$v,'payload'=>['doc_uuid'=>$du,'title'=>$TITLE.($i>0?' (parte '.($i+1).')':''),'chunk'=>$ck,'entity_type'=>'guida_prodotto','source'=>$SOURCE,'lang'=>'it','scope'=>'SYSTEM','consulting_firm_id'=>null,'organization_id'=>null,'shared_with_orgs'=>[],'uploaded_by'=>0]]; }
|
||||
foreach(array_chunk($pts,64) as $b) $vector->upsertBatch($b);
|
||||
logln("upsert OK: ".count($pts)." chunk SYSTEM");
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_058_integrity_keys.php — Hardening integrità chiavi (estende mig.039
|
||||
* alle tabelle aggiunte dopo: 040-057). Runner IDEMPOTENTE e GUARDATO.
|
||||
*
|
||||
* Verificato sul dato reale (db_integrity_probe.php/probe2.php, 2026-06-18):
|
||||
* - 0 duplicati (org,code) su internal_audits/management_reviews
|
||||
* - 0 valori duplicati su kb_uploaded_documents.qdrant_doc_uuid (16 righe)
|
||||
* - whistleblowing_reports vuota; anonymous_token = chiave retrieval
|
||||
* - 0 orfani su consulting_firm_id (organizations/users/kb), linked_control_id,
|
||||
* linked_policy_id, capa_id
|
||||
*
|
||||
* TIER 1 (additivo, default): 4 UNIQUE + 6 FK. Nessuna modifica di dato.
|
||||
* TIER 2 (--with-cleanup): bonifica righe stale (orfane) + 2 FK. CANCELLA righe.
|
||||
*
|
||||
* Uso:
|
||||
* docker exec nis2-app php .../application/cli/migrate_058_integrity_keys.php # Tier1
|
||||
* docker exec nis2-app php .../application/cli/migrate_058_integrity_keys.php --with-cleanup
|
||||
* ... --dry-run # mostra solo cosa farebbe, non esegue
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$DRY = in_array('--dry-run', $argv, true);
|
||||
$CLEANUP = in_array('--with-cleanup', $argv, true);
|
||||
$pdo = Database::getInstance();
|
||||
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
echo "mig.058 — schema=$db | dry-run=" . ($DRY?'SI':'no') . " | cleanup=" . ($CLEANUP?'SI':'no') . "\n\n";
|
||||
|
||||
function col1(PDO $p,$s,$a=[]){ $st=$p->prepare($s); $st->execute($a); return $st->fetchColumn(); }
|
||||
function idxExists(PDO $p,$db,$t,$idx){ return (int)col1($p,"SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND INDEX_NAME=?",[$db,$t,$idx])>0; }
|
||||
function fkExists(PDO $p,$db,$name){ return (int)col1($p,"SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA=? AND CONSTRAINT_TYPE='FOREIGN KEY' AND CONSTRAINT_NAME=?",[$db,$name])>0; }
|
||||
function tblExists(PDO $p,$db,$t){ return (int)col1($p,"SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?",[$db,$t])>0; }
|
||||
|
||||
$applied=0; $skipped=0; $failed=0;
|
||||
function run(PDO $pdo,bool $DRY,string $label,string $sql){
|
||||
global $applied,$skipped,$failed;
|
||||
if($DRY){ echo " [DRY] $label\n $sql\n"; return; }
|
||||
try { $pdo->exec($sql); echo " [OK ] $label\n"; $applied++; }
|
||||
catch(\PDOException $e){
|
||||
$c=(int)($e->errorInfo[1]??0);
|
||||
// 1061 dup key, 1826/1022 dup FK, 1062 dup entry (già unico) → idempotente
|
||||
if(in_array($c,[1061,1826,1022,1062],true)){ echo " [SKIP] $label (già presente, code=$c)\n"; $skipped++; }
|
||||
else { echo " [FAIL] $label → ".$e->getMessage()."\n"; $failed++; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ───────── TIER 1: UNIQUE su chiavi naturali (pre-check duplicati) ───────── */
|
||||
echo "── TIER 1: UNIQUE ──\n";
|
||||
|
||||
// U1 internal_audits (organization_id, code)
|
||||
if(!idxExists($pdo,$db,'internal_audits','uk_intaud_code')){
|
||||
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM internal_audits WHERE code IS NOT NULL GROUP BY organization_id,code HAVING COUNT(*)>1) x");
|
||||
if($dup>0){ echo " [FAIL] internal_audits: $dup duplicati (org,code) — bonificare prima\n"; $failed++; }
|
||||
else run($pdo,$DRY,'internal_audits ADD UNIQUE uk_intaud_code(organization_id,code)',
|
||||
"ALTER TABLE internal_audits ADD UNIQUE KEY uk_intaud_code (organization_id, code)");
|
||||
} else { echo " [SKIP] internal_audits.uk_intaud_code già presente\n"; $skipped++; }
|
||||
|
||||
// U2 management_reviews (organization_id, code)
|
||||
if(!idxExists($pdo,$db,'management_reviews','uk_mgr_code')){
|
||||
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM management_reviews WHERE code IS NOT NULL GROUP BY organization_id,code HAVING COUNT(*)>1) x");
|
||||
if($dup>0){ echo " [FAIL] management_reviews: $dup duplicati (org,code)\n"; $failed++; }
|
||||
else run($pdo,$DRY,'management_reviews ADD UNIQUE uk_mgr_code(organization_id,code)',
|
||||
"ALTER TABLE management_reviews ADD UNIQUE KEY uk_mgr_code (organization_id, code)");
|
||||
} else { echo " [SKIP] management_reviews.uk_mgr_code già presente\n"; $skipped++; }
|
||||
|
||||
// U3 kb_uploaded_documents.qdrant_doc_uuid
|
||||
if(!idxExists($pdo,$db,'kb_uploaded_documents','uk_kbdoc_qdrant')){
|
||||
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM kb_uploaded_documents WHERE qdrant_doc_uuid IS NOT NULL AND qdrant_doc_uuid<>'' GROUP BY qdrant_doc_uuid HAVING COUNT(*)>1) x");
|
||||
if($dup>0){ echo " [FAIL] kb_uploaded_documents: $dup uuid duplicati\n"; $failed++; }
|
||||
else run($pdo,$DRY,'kb_uploaded_documents ADD UNIQUE uk_kbdoc_qdrant(qdrant_doc_uuid)',
|
||||
"ALTER TABLE kb_uploaded_documents ADD UNIQUE KEY uk_kbdoc_qdrant (qdrant_doc_uuid)");
|
||||
} else { echo " [SKIP] kb_uploaded_documents.uk_kbdoc_qdrant già presente\n"; $skipped++; }
|
||||
|
||||
// U4 whistleblowing_reports.anonymous_token (DROP idx_token ridondante + UNIQUE)
|
||||
if(!idxExists($pdo,$db,'whistleblowing_reports','uq_wb_anon_token')){
|
||||
$dup=(int)col1($pdo,"SELECT COUNT(*) FROM (SELECT 1 FROM whistleblowing_reports WHERE anonymous_token IS NOT NULL AND anonymous_token<>'' GROUP BY anonymous_token HAVING COUNT(*)>1) x");
|
||||
if($dup>0){ echo " [FAIL] whistleblowing_reports: $dup token duplicati\n"; $failed++; }
|
||||
else {
|
||||
if(idxExists($pdo,$db,'whistleblowing_reports','idx_token'))
|
||||
run($pdo,$DRY,'whistleblowing_reports DROP INDEX idx_token (ridondante)',
|
||||
"ALTER TABLE whistleblowing_reports DROP INDEX idx_token");
|
||||
run($pdo,$DRY,'whistleblowing_reports ADD UNIQUE uq_wb_anon_token(anonymous_token)',
|
||||
"ALTER TABLE whistleblowing_reports ADD UNIQUE KEY uq_wb_anon_token (anonymous_token)");
|
||||
}
|
||||
} else { echo " [SKIP] whistleblowing_reports.uq_wb_anon_token già presente\n"; $skipped++; }
|
||||
|
||||
/* ───────── TIER 1: FOREIGN KEY mancanti (pre-check orfani) ───────── */
|
||||
echo "\n── TIER 1: FOREIGN KEY ──\n";
|
||||
// [constraint, tabella, colonna, ref_table, on_delete]
|
||||
$fks = [
|
||||
['fk_org_consulting_firm','organizations','consulting_firm_id','consulting_firms','SET NULL'],
|
||||
['fk_users_consulting_firm','users','consulting_firm_id','consulting_firms','SET NULL'],
|
||||
['fk_kbdoc_consulting_firm','kb_uploaded_documents','consulting_firm_id','consulting_firms','SET NULL'],
|
||||
['fk_isms_soa_control','isms_soa','linked_control_id','compliance_controls','SET NULL'],
|
||||
['fk_isms_doc_policy','isms_documents','linked_policy_id','policies','SET NULL'],
|
||||
['fk_mrd_capa','management_review_decisions','capa_id','capa_actions','SET NULL'],
|
||||
];
|
||||
foreach($fks as [$name,$t,$c,$ref,$od]){
|
||||
if(!tblExists($pdo,$db,$t) || !tblExists($pdo,$db,$ref)){ echo " [SKIP] $name (tabella assente)\n"; $skipped++; continue; }
|
||||
if(fkExists($pdo,$db,$name)){ echo " [SKIP] $name già presente\n"; $skipped++; continue; }
|
||||
$orf=(int)col1($pdo,"SELECT COUNT(*) FROM `$t` x WHERE x.`$c` IS NOT NULL AND x.`$c`<>0 AND NOT EXISTS(SELECT 1 FROM `$ref` r WHERE r.id=x.`$c`)");
|
||||
if($orf>0){ echo " [FAIL] $name: $orf righe orfane su $t.$c → bonificare prima\n"; $failed++; continue; }
|
||||
run($pdo,$DRY,"$t ADD FK $name($c)->$ref ON DELETE $od",
|
||||
"ALTER TABLE `$t` ADD CONSTRAINT `$name` FOREIGN KEY (`$c`) REFERENCES `$ref`(id) ON DELETE $od ON UPDATE CASCADE");
|
||||
}
|
||||
|
||||
/* ───────── TIER 2: bonifica righe stale + FK (solo con --with-cleanup) ───────── */
|
||||
echo "\n── TIER 2: cleanup orfani (".($CLEANUP?'ATTIVO':'saltato, usa --with-cleanup').") ──\n";
|
||||
if($CLEANUP){
|
||||
// C1 firm_org_assignments: 9 righe verso org 126-129 inesistenti (dogfooding, deferred-b 039)
|
||||
$n=(int)col1($pdo,"SELECT COUNT(*) FROM firm_org_assignments x WHERE NOT EXISTS(SELECT 1 FROM organizations o WHERE o.id=x.organization_id)");
|
||||
if($n>0){
|
||||
if($DRY) echo " [DRY] DELETE $n righe stale firm_org_assignments (org inesistente)\n";
|
||||
else { $pdo->exec("DELETE FROM firm_org_assignments WHERE organization_id NOT IN (SELECT id FROM organizations)"); echo " [OK ] cancellate $n righe stale firm_org_assignments\n"; $applied++; }
|
||||
} else echo " [SKIP] firm_org_assignments: 0 orfani\n";
|
||||
if(!fkExists($pdo,$db,'fk_firm_org_assignments_organization_id'))
|
||||
run($pdo,$DRY,'firm_org_assignments ADD FK organization_id->organizations CASCADE',
|
||||
"ALTER TABLE firm_org_assignments ADD CONSTRAINT fk_firm_org_assignments_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE");
|
||||
else { echo " [SKIP] fk_firm_org_assignments_organization_id già presente\n"; $skipped++; }
|
||||
|
||||
// C2 active_sessions: 7 sessioni verso org inesistenti
|
||||
$n2=(int)col1($pdo,"SELECT COUNT(*) FROM active_sessions x WHERE x.organization_id IS NOT NULL AND x.organization_id<>0 AND NOT EXISTS(SELECT 1 FROM organizations o WHERE o.id=x.organization_id)");
|
||||
if($n2>0){
|
||||
if($DRY) echo " [DRY] DELETE $n2 sessioni stale active_sessions\n";
|
||||
else { $pdo->exec("DELETE FROM active_sessions WHERE organization_id IS NOT NULL AND organization_id<>0 AND organization_id NOT IN (SELECT id FROM organizations)"); echo " [OK ] cancellate $n2 sessioni stale active_sessions\n"; $applied++; }
|
||||
} else echo " [SKIP] active_sessions: 0 orfani\n";
|
||||
if(!fkExists($pdo,$db,'fk_active_sessions_organization_id'))
|
||||
run($pdo,$DRY,'active_sessions ADD FK organization_id->organizations CASCADE',
|
||||
"ALTER TABLE active_sessions ADD CONSTRAINT fk_active_sessions_organization_id FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE ON UPDATE CASCADE");
|
||||
else { echo " [SKIP] fk_active_sessions_organization_id già presente\n"; $skipped++; }
|
||||
}
|
||||
|
||||
echo "\n── RIEPILOGO ── applicati=$applied skip=$skipped falliti=$failed\n";
|
||||
echo ($failed===0 ? "ESITO OK\n" : "ESITO: rivedere i [FAIL]\n");
|
||||
exit($failed===0?0:1);
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_059_requisito_policy.php — Ticket #384 (p.2.2 + p.3): modello
|
||||
* relazionale REQUISITO <-> POLICY. Runner IDEMPOTENTE e GUARDATO (PDO app, TLS).
|
||||
*
|
||||
* TUTTO ADDITIVO (nessun DROP/DELETE):
|
||||
* 1) policies.proc_code VARCHAR(16) NULL (chiave di join verso il catalogo)
|
||||
* 2) tabella requisito_policy (M:N org-scoped requisito<->policy)
|
||||
* 3) backfill SICURO: proc_code per match esatto title==proc_descr +
|
||||
* requisito_policy dalle associazioni di default esistenti (INSERT IGNORE).
|
||||
*
|
||||
* Uso:
|
||||
* docker exec nis2-app php .../application/cli/migrate_059_requisito_policy.php
|
||||
* ... --dry-run
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$DRY = in_array('--dry-run', $argv, true);
|
||||
$pdo = Database::getInstance();
|
||||
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
echo "mig.059 — schema=$db | dry-run=" . ($DRY ? 'SI' : 'no') . "\n\n";
|
||||
|
||||
function col1(PDO $p, $s, $a = []) { $st = $p->prepare($s); $st->execute($a); return $st->fetchColumn(); }
|
||||
function colExists(PDO $p, $db, $t, $c) { return (int)col1($p, "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?", [$db, $t, $c]) > 0; }
|
||||
function tblExists(PDO $p, $db, $t) { return (int)col1($p, "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=? AND TABLE_NAME=?", [$db, $t]) > 0; }
|
||||
|
||||
$applied = 0; $skipped = 0; $failed = 0;
|
||||
function run(PDO $pdo, bool $DRY, string $label, string $sql) {
|
||||
global $applied, $skipped, $failed;
|
||||
if ($DRY) { echo " [DRY] $label\n $sql\n"; return; }
|
||||
try { $pdo->exec($sql); echo " [OK ] $label\n"; $applied++; }
|
||||
catch (\PDOException $e) {
|
||||
$c = (int)($e->errorInfo[1] ?? 0);
|
||||
if (in_array($c, [1060, 1061, 1826, 1022, 1062, 1050], true)) { echo " [SKIP] $label (gia' presente, code=$c)\n"; $skipped++; }
|
||||
else { echo " [FAIL] $label -> " . $e->getMessage() . "\n"; $failed++; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ── 1) policies.proc_code ── */
|
||||
echo "── 1) policies.proc_code ──\n";
|
||||
if (colExists($pdo, $db, 'policies', 'proc_code')) { echo " [SKIP] policies.proc_code gia' presente\n"; $skipped++; }
|
||||
else run($pdo, $DRY, 'policies ADD COLUMN proc_code VARCHAR(16) NULL',
|
||||
"ALTER TABLE policies ADD COLUMN proc_code VARCHAR(16) NULL AFTER nis2_article");
|
||||
|
||||
/* ── 2) tabella requisito_policy ── */
|
||||
echo "\n── 2) tabella requisito_policy ──\n";
|
||||
if (tblExists($pdo, $db, 'requisito_policy')) { echo " [SKIP] requisito_policy gia' presente\n"; $skipped++; }
|
||||
else run($pdo, $DRY, 'CREATE TABLE requisito_policy',
|
||||
"CREATE TABLE requisito_policy (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
organization_id INT NOT NULL,
|
||||
requisito_id INT NOT NULL,
|
||||
policy_id INT NOT NULL,
|
||||
is_default TINYINT(1) NOT NULL DEFAULT 0,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_req_pol (organization_id, requisito_id, policy_id),
|
||||
KEY idx_rp_org (organization_id),
|
||||
KEY idx_rp_req (requisito_id),
|
||||
KEY idx_rp_pol (policy_id),
|
||||
CONSTRAINT fk_rp_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_rp_req FOREIGN KEY (requisito_id) REFERENCES cfg_nis2_requisiti (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_rp_pol FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_rp_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
|
||||
/* ── 3) backfill SICURO (additivo) ── */
|
||||
echo "\n── 3) backfill (additivo, idempotente) ──\n";
|
||||
if (!$DRY && colExists($pdo, $db, 'policies', 'proc_code') && tblExists($pdo, $db, 'cfg_nis2_procedure')) {
|
||||
// 3a) proc_code per match esatto del titolo (title = mb_substr(proc_descr,0,255))
|
||||
$n1 = $pdo->exec("UPDATE policies p
|
||||
JOIN cfg_nis2_procedure c ON p.title = c.proc_descr
|
||||
SET p.proc_code = c.proc_code
|
||||
WHERE p.proc_code IS NULL");
|
||||
echo " [OK ] proc_code valorizzato su $n1 policy (match esatto titolo)\n"; $applied++;
|
||||
|
||||
// 3b) requisito_policy dalle associazioni di default (per ogni proc_code)
|
||||
if (tblExists($pdo, $db, 'requisito_policy') && tblExists($pdo, $db, 'cfg_nis2_requisiti')) {
|
||||
$n2 = $pdo->exec("INSERT IGNORE INTO requisito_policy (organization_id, requisito_id, policy_id, is_default)
|
||||
SELECT p.organization_id, q.id, p.id, 1
|
||||
FROM policies p
|
||||
JOIN cfg_nis2_requisiti q ON q.proc_code = p.proc_code
|
||||
WHERE p.proc_code IS NOT NULL");
|
||||
echo " [OK ] requisito_policy popolata: $n2 associazioni di default\n"; $applied++;
|
||||
}
|
||||
} else {
|
||||
echo " [SKIP] backfill (dry-run o prerequisiti assenti)\n"; $skipped++;
|
||||
}
|
||||
|
||||
/* ── verifica finale ── */
|
||||
echo "\n── VERIFICA ──\n";
|
||||
if (!$DRY) {
|
||||
$cWith = (int)col1($pdo, "SELECT COUNT(*) FROM policies WHERE proc_code IS NOT NULL");
|
||||
$cTot = (int)col1($pdo, "SELECT COUNT(*) FROM policies");
|
||||
echo " policies con proc_code: $cWith / $cTot\n";
|
||||
if (tblExists($pdo, $db, 'requisito_policy')) {
|
||||
$cLinks = (int)col1($pdo, "SELECT COUNT(*) FROM requisito_policy");
|
||||
echo " requisito_policy righe: $cLinks\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n── RIEPILOGO ── applicati=$applied skip=$skipped falliti=$failed\n";
|
||||
echo ($failed === 0 ? "ESITO OK\n" : "ESITO: rivedere i [FAIL]\n");
|
||||
exit($failed === 0 ? 0 : 1);
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_060_isms_doc_lifecycle.php — Ciclo di vita documentale ISMS (ISO 27001 cl.7.5).
|
||||
* Estende isms_documents (stati pubblicato/archiviato + tracciamento + riesame) + tabella versioni.
|
||||
* IDEMPOTENTE: ALTER guardati da information_schema, CREATE TABLE IF NOT EXISTS.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
$db = $pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
function col_exists(PDO $p,string $db,string $t,string $c):bool{
|
||||
$s=$p->prepare("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?");
|
||||
$s->execute([$db,$t,$c]); return (bool)$s->fetchColumn();
|
||||
}
|
||||
function add_col(PDO $p,string $db,string $t,string $c,string $ddl):void{
|
||||
if(col_exists($p,$db,$t,$c)){ echo " = $t.$c\n"; return; }
|
||||
$p->exec("ALTER TABLE `$t` ADD COLUMN $ddl"); echo " + $t.$c\n";
|
||||
}
|
||||
|
||||
echo "Migrazione 060 — ciclo di vita documenti ISMS (db=$db)\n";
|
||||
|
||||
/* 1) Estensione enum stato (idempotente: MODIFY) */
|
||||
$pdo->exec("ALTER TABLE isms_documents MODIFY COLUMN status ENUM('draft','review','approved','published','archived') NOT NULL DEFAULT 'draft'");
|
||||
echo " enum status -> draft/review/approved/published/archived\n";
|
||||
|
||||
/* 2) Colonne di tracciamento del ciclo di vita */
|
||||
add_col($pdo,$db,'isms_documents','reviewed_by', "reviewed_by INT NULL");
|
||||
add_col($pdo,$db,'isms_documents','reviewed_at', "reviewed_at DATETIME NULL");
|
||||
add_col($pdo,$db,'isms_documents','approved_by', "approved_by INT NULL");
|
||||
add_col($pdo,$db,'isms_documents','approved_at', "approved_at DATETIME NULL");
|
||||
add_col($pdo,$db,'isms_documents','published_by', "published_by INT NULL");
|
||||
add_col($pdo,$db,'isms_documents','published_at', "published_at DATETIME NULL");
|
||||
add_col($pdo,$db,'isms_documents','archived_at', "archived_at DATETIME NULL");
|
||||
add_col($pdo,$db,'isms_documents','review_note', "review_note VARCHAR(500) NULL");
|
||||
add_col($pdo,$db,'isms_documents','effective_date',"effective_date DATE NULL");
|
||||
add_col($pdo,$db,'isms_documents','next_review_date',"next_review_date DATE NULL");
|
||||
|
||||
/* 3) Tabella storico versioni (snapshot ad ogni approvazione/pubblicazione) */
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS isms_document_versions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
document_id INT NOT NULL,
|
||||
isms_model_id INT NOT NULL,
|
||||
organization_id INT NOT NULL,
|
||||
version VARCHAR(20) NOT NULL,
|
||||
status VARCHAR(20) NOT NULL,
|
||||
body_html LONGTEXT NULL,
|
||||
change_note VARCHAR(255) NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME NULL,
|
||||
INDEX idx_docver_doc (document_id),
|
||||
INDEX idx_docver_org (organization_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
echo " tabella isms_document_versions OK\n";
|
||||
|
||||
echo "FATTO. Prossima mig=061.\n";
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_061_doc_permissions.php — Permessi documentali profilabili per ruolo (org-scoped).
|
||||
* Tabella matrice Ruolo x Azione del ciclo di vita documenti ISMS. IDEMPOTENTE.
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS isms_doc_permissions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
capability VARCHAR(20) NOT NULL,
|
||||
role VARCHAR(30) NOT NULL,
|
||||
allowed TINYINT(1) NOT NULL DEFAULT 1,
|
||||
updated_at DATETIME NULL,
|
||||
UNIQUE KEY uk_docperm (organization_id, capability, role),
|
||||
INDEX idx_docperm_org (organization_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
echo "Migrazione 061 — tabella isms_doc_permissions OK. Prossima mig=062.\n";
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/** migrate_062_feedback_provenance.php — version-provenance su feedback_reports (std feedback-version-provenance). IDEMPOTENTE. */
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__.'/../config/env.php'; require_once __DIR__.'/../config/database.php';
|
||||
$pdo=Database::getInstance(); $db=$pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
function addc($p,$db,$t,$c,$ddl){ $s=$p->prepare("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?"); $s->execute([$db,$t,$c]); if($s->fetchColumn()){echo " = $t.$c\n";return;} $p->exec("ALTER TABLE `$t` ADD COLUMN $ddl"); echo " + $t.$c\n"; }
|
||||
echo "Mig 062 — version-provenance feedback_reports\n";
|
||||
addc($pdo,$db,'feedback_reports','client_surface',"client_surface VARCHAR(16) NULL");
|
||||
addc($pdo,$db,'feedback_reports','client_version',"client_version VARCHAR(32) NULL");
|
||||
addc($pdo,$db,'feedback_reports','client_build', "client_build VARCHAR(64) NULL");
|
||||
echo "FATTO. Prossima mig=063.\n";
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_063_vocea_channel.php — Integrazione Vocea (whistleblowing zero-knowledge).
|
||||
* organizations += vocea_tenant_slug, vocea_channel_status, vocea_enrolled_at. IDEMPOTENTE.
|
||||
* SCAFFOLD DORMIENTE: nessun effetto funzionale finché VOCEA_ENABLED=false (default).
|
||||
*
|
||||
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_063_vocea_channel.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
|
||||
function colExists(PDO $pdo, string $table, string $col): bool {
|
||||
$st = $pdo->prepare(
|
||||
"SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
|
||||
);
|
||||
$st->execute([$table, $col]);
|
||||
return (bool)$st->fetchColumn();
|
||||
}
|
||||
|
||||
$adds = [
|
||||
'vocea_tenant_slug' => "ADD COLUMN vocea_tenant_slug VARCHAR(64) NULL COMMENT 'Slug canale Vocea (NULL=nessuno)'",
|
||||
'vocea_channel_status' => "ADD COLUMN vocea_channel_status ENUM('none','provisioning','active','suspended') NOT NULL DEFAULT 'none'",
|
||||
'vocea_enrolled_at' => "ADD COLUMN vocea_enrolled_at TIMESTAMP NULL COMMENT 'Enrollment canale + key holder'",
|
||||
];
|
||||
|
||||
foreach ($adds as $col => $clause) {
|
||||
if (colExists($pdo, 'organizations', $col)) { echo " - $col gia presente, skip\n"; continue; }
|
||||
$pdo->exec("ALTER TABLE organizations $clause");
|
||||
echo " + aggiunta colonna organizations.$col\n";
|
||||
}
|
||||
|
||||
echo "Migrazione 063 — organizations.vocea_* OK. Prossima mig=064.\n";
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_064_discovery_connectors.php — Connettori discovery + run + flussi di rete. IDEMPOTENTE.
|
||||
* Esegui: docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_064_discovery_connectors.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS discovery_connectors (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
connector_type ENUM('network','aws','azure','gcp','cmdb','agent','custom') NOT NULL DEFAULT 'network',
|
||||
config JSON NULL,
|
||||
api_key_id INT UNSIGNED NULL,
|
||||
status ENUM('active','paused','error') NOT NULL DEFAULT 'active',
|
||||
schedule ENUM('manual','hourly','daily','weekly') NOT NULL DEFAULT 'manual',
|
||||
last_run_at TIMESTAMP NULL,
|
||||
last_status VARCHAR(20) NULL,
|
||||
last_discovered INT NULL,
|
||||
last_message VARCHAR(255) NULL,
|
||||
created_by INT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_dc_org_name (organization_id, name),
|
||||
INDEX idx_dc_org (organization_id),
|
||||
INDEX idx_dc_key (api_key_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
echo " + discovery_connectors OK\n";
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS discovery_runs (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
connector_id INT UNSIGNED NOT NULL,
|
||||
organization_id INT NOT NULL,
|
||||
started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
finished_at TIMESTAMP NULL,
|
||||
status ENUM('running','ok','error') NOT NULL DEFAULT 'ok',
|
||||
discovered_assets INT NOT NULL DEFAULT 0,
|
||||
discovered_flows INT NOT NULL DEFAULT 0,
|
||||
message VARCHAR(255) NULL,
|
||||
INDEX idx_dr_conn (connector_id),
|
||||
INDEX idx_dr_org (organization_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
echo " + discovery_runs OK\n";
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS network_flows (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
src VARCHAR(190) NOT NULL,
|
||||
dst VARCHAR(190) NOT NULL,
|
||||
port INT NULL,
|
||||
protocol VARCHAR(12) NULL,
|
||||
direction ENUM('internal','inbound','outbound') NOT NULL DEFAULT 'internal',
|
||||
discovery_source VARCHAR(40) NOT NULL DEFAULT 'manual',
|
||||
external_ref VARCHAR(190) NULL,
|
||||
last_seen_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_flow (organization_id, external_ref),
|
||||
INDEX idx_flow_org (organization_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
echo " + network_flows OK\n";
|
||||
|
||||
echo "Migrazione 064 — connettori discovery OK. Prossima mig=065.\n";
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_065_institutional_documents.php — Documenti istituzionali per-org (dashboard). IDEMPOTENTE.
|
||||
* Ticket #499. Esegui: docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_065_institutional_documents.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS institutional_documents (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
doc_key VARCHAR(60) NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
file_url VARCHAR(500) NULL,
|
||||
is_standard TINYINT(1) NOT NULL DEFAULT 0,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
created_by INT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_instdoc_org_key (organization_id, doc_key),
|
||||
INDEX idx_instdoc_org (organization_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
echo " + institutional_documents OK\n";
|
||||
echo "Migrazione 065 — documenti istituzionali OK. Prossima mig=066.\n";
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_066_requisito_risk_scoring.php — Ticket #500 (opzione B, matrice).
|
||||
* org_requisito_state += likelihood TINYINT NULL, impact TINYINT NULL (scala 1-5).
|
||||
* Serve a posizionare i rischi-da-requisito (RSK-<misura>Com.N) sulla matrice 5x5.
|
||||
* Additiva e IDEMPOTENTE. Nessun dato esistente toccato (valori NULL = non ancora valutato).
|
||||
*
|
||||
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_066_requisito_risk_scoring.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
|
||||
function colExists(PDO $pdo, string $table, string $col): bool {
|
||||
$st = $pdo->prepare(
|
||||
"SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
|
||||
);
|
||||
$st->execute([$table, $col]);
|
||||
return (bool) $st->fetchColumn();
|
||||
}
|
||||
|
||||
$adds = [
|
||||
'likelihood' => "ADD COLUMN likelihood TINYINT NULL COMMENT 'Probabilita 1-5 (rischio da non conformita del requisito)' AFTER valutazione_rischio",
|
||||
'impact' => "ADD COLUMN impact TINYINT NULL COMMENT 'Impatto 1-5' AFTER likelihood",
|
||||
];
|
||||
|
||||
foreach ($adds as $col => $clause) {
|
||||
if (colExists($pdo, 'org_requisito_state', $col)) { echo " - $col gia presente, skip\n"; continue; }
|
||||
$pdo->exec("ALTER TABLE org_requisito_state $clause");
|
||||
echo " + aggiunta colonna org_requisito_state.$col\n";
|
||||
}
|
||||
|
||||
echo "Migrazione 066 — org_requisito_state.likelihood/impact OK. Prossima mig=067.\n";
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* migrate_067_requisito_residual_and_actions.php — Integrazione "Modulo Azioni" (Simon), Fase 1.
|
||||
* (1) org_requisito_state += residual_likelihood, residual_impact (TINYINT NULL, scala 1-5).
|
||||
* (2) requisito_actions (azioni collegate al rischio-da-requisito).
|
||||
* Additiva e IDEMPOTENTE. Nessun dato esistente toccato.
|
||||
*
|
||||
* Eseguire (host): docker exec nis2-app php /var/www/nis2-agile/application/cli/migrate_067_requisito_residual_and_actions.php
|
||||
*/
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("CLI only\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
|
||||
function colExists(PDO $pdo, string $table, string $col): bool {
|
||||
$st = $pdo->prepare(
|
||||
"SELECT 1 FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ?"
|
||||
);
|
||||
$st->execute([$table, $col]);
|
||||
return (bool) $st->fetchColumn();
|
||||
}
|
||||
function tblExists(PDO $pdo, string $table): bool {
|
||||
$st = $pdo->prepare(
|
||||
"SELECT 1 FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"
|
||||
);
|
||||
$st->execute([$table]);
|
||||
return (bool) $st->fetchColumn();
|
||||
}
|
||||
|
||||
$adds = [
|
||||
'residual_likelihood' => "ADD COLUMN residual_likelihood TINYINT NULL COMMENT 'Probabilita residua 1-5' AFTER impact",
|
||||
'residual_impact' => "ADD COLUMN residual_impact TINYINT NULL COMMENT 'Impatto residuo 1-5' AFTER residual_likelihood",
|
||||
];
|
||||
foreach ($adds as $col => $clause) {
|
||||
if (colExists($pdo, 'org_requisito_state', $col)) { echo " - $col gia presente, skip\n"; continue; }
|
||||
$pdo->exec("ALTER TABLE org_requisito_state $clause");
|
||||
echo " + aggiunta colonna org_requisito_state.$col\n";
|
||||
}
|
||||
|
||||
if (tblExists($pdo, 'requisito_actions')) {
|
||||
echo " - tabella requisito_actions gia presente, skip\n";
|
||||
} else {
|
||||
$pdo->exec(
|
||||
"CREATE TABLE requisito_actions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
organization_id INT NOT NULL,
|
||||
requisito_id INT NOT NULL,
|
||||
risk_code VARCHAR(64) NULL,
|
||||
description TEXT NOT NULL,
|
||||
status ENUM('bozza','in_corso','archiviata') NOT NULL DEFAULT 'bozza',
|
||||
created_by INT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_reqact_org_req (organization_id, requisito_id, created_at),
|
||||
INDEX idx_reqact_org (organization_id, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
|
||||
);
|
||||
echo " + creata tabella requisito_actions\n";
|
||||
}
|
||||
|
||||
echo "Migrazione 067 — residuo + requisito_actions OK. Prossima mig=068.\n";
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
/**
|
||||
* provision_testers.php — Client CLI sicuro per provisioning account tester/admin NIS2.
|
||||
*
|
||||
* DEVE girare DENTRO nis2-app (unico container che raggiunge nis2-db via TLS):
|
||||
* ssh root@172.18.0.1 "docker exec [-e PROV_PASSWORD=...] nis2-app \
|
||||
* php /var/www/nis2-agile/application/cli/provision_testers.php <args>"
|
||||
*
|
||||
* Sicurezza by design:
|
||||
* - DRY-RUN di default: senza --apply NON scrive nulla, stampa solo il piano.
|
||||
* - Target per email/cognome ESATTI (IN parametrico), mai UPDATE ciechi/LIKE.
|
||||
* - --expect=N: se il match non risolve ESATTAMENTE N utenti -> ABORT (anti grant accidentale).
|
||||
* - Password NON in argv: letta da env PROV_PASSWORD (non compare in `ps`).
|
||||
* - password_version lasciato invariato -> la sync SSO non sovrascrive (vedi sso-password-sync.sh).
|
||||
* - must_change_password=0; membership idempotente (UNIQUE user+org).
|
||||
* - Tutte le scritture in UNA transazione (rollback on error). Nessun segreto hardcoded.
|
||||
*
|
||||
* Args:
|
||||
* --match=Cognome1,Cognome2 cognomi da cercare (CSV) [opz.]
|
||||
* --emails=a@b,c@d email esatte (CSV) [opz.]
|
||||
* --role=super_admin users.role da impostare [opz.; se assente non tocca il ruolo]
|
||||
* --orgs=151:primary,152 membership da garantire (org[:primary]) CSV [opz.]
|
||||
* --set-password imposta password_hash da env PROV_PASSWORD [opz.]
|
||||
* --expect=N n. atteso di utenti risolti (safety) [opz. ma consigliato]
|
||||
* --apply esegue le scritture (default: dry-run)
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE);
|
||||
|
||||
/* --- prime DB_* da PID1 (creds vault-injected non presenti nell'env di docker exec) --- */
|
||||
$environ = @file_get_contents('/proc/1/environ');
|
||||
if ($environ) {
|
||||
foreach (explode("\0", $environ) as $kv) {
|
||||
if ($kv === '' || strpos($kv, '=') === false) continue;
|
||||
[$k, $v] = explode('=', $kv, 2);
|
||||
if (strncmp($k, 'DB_', 3) === 0) { putenv("$k=$v"); $_ENV[$k] = $v; $_SERVER[$k] = $v; }
|
||||
}
|
||||
}
|
||||
|
||||
require_once '/var/www/nis2-agile/application/config/env.php';
|
||||
require_once '/var/www/nis2-agile/application/config/database.php';
|
||||
|
||||
/* --- arg parsing --- */
|
||||
$args = [];
|
||||
foreach (array_slice($argv, 1) as $a) {
|
||||
if (preg_match('/^--([^=]+)(?:=(.*))?$/', $a, $m)) $args[$m[1]] = $m[2] ?? true;
|
||||
}
|
||||
$matchNames = isset($args['match']) ? array_filter(array_map('trim', explode(',', $args['match']))) : [];
|
||||
$emails = isset($args['emails']) ? array_filter(array_map('trim', explode(',', $args['emails']))) : [];
|
||||
$role = $args['role'] ?? null;
|
||||
$orgsRaw = isset($args['orgs']) ? array_filter(array_map('trim', explode(',', $args['orgs']))) : [];
|
||||
$setPw = isset($args['set-password']);
|
||||
$expect = isset($args['expect']) ? (int)$args['expect'] : null;
|
||||
$apply = isset($args['apply']);
|
||||
|
||||
if (!$matchNames && !$emails) { fwrite(STDERR, "ERRORE: specifica --match e/o --emails\n"); exit(2); }
|
||||
|
||||
$validRoles = ['super_admin','org_admin','compliance_manager','board_member','auditor','employee','consultant'];
|
||||
if ($role !== null && !in_array($role, $validRoles, true)) { fwrite(STDERR, "ERRORE: role '$role' non valido\n"); exit(2); }
|
||||
|
||||
$pw = null;
|
||||
if ($setPw) {
|
||||
$pw = getenv('PROV_PASSWORD') ?: '';
|
||||
if ($pw === '') { fwrite(STDERR, "ERRORE: --set-password ma env PROV_PASSWORD vuota\n"); exit(2); }
|
||||
}
|
||||
|
||||
/* parse orgs: ['151'=>true(primary), '152'=>false] */
|
||||
$orgs = [];
|
||||
foreach ($orgsRaw as $o) {
|
||||
$parts = explode(':', $o);
|
||||
$orgs[(int)$parts[0]] = (isset($parts[1]) && strtolower($parts[1]) === 'primary');
|
||||
}
|
||||
|
||||
echo "==================================================================\n";
|
||||
echo " provision_testers.php — " . ($apply ? "*** APPLY (scrive) ***" : "DRY-RUN (sola lettura)") . "\n";
|
||||
echo "==================================================================\n";
|
||||
|
||||
/* --- risoluzione utenti (esatti) --- */
|
||||
$conds = []; $params = [];
|
||||
if ($matchNames) {
|
||||
$like = [];
|
||||
foreach ($matchNames as $n) { $like[] = 'full_name LIKE ?'; $params[] = '%' . $n . '%'; }
|
||||
$conds[] = '(' . implode(' OR ', $like) . ')';
|
||||
}
|
||||
if ($emails) { $conds[] = 'email IN (' . implode(',', array_fill(0, count($emails), '?')) . ')'; $params = array_merge($params, $emails); }
|
||||
$sql = "SELECT id,email,full_name,role,is_active,sso_identity_id,password_version,must_change_password
|
||||
FROM users WHERE " . implode(' OR ', $conds) . " ORDER BY id";
|
||||
$users = Database::fetchAll($sql, $params);
|
||||
|
||||
if (!$users) { fwrite(STDERR, "Nessun utente risolto.\n"); exit(1); }
|
||||
|
||||
echo "\nUTENTI RISOLTI (" . count($users) . "):\n";
|
||||
foreach ($users as $u) {
|
||||
$mem = Database::fetchAll(
|
||||
"SELECT organization_id, role, is_primary FROM user_organizations WHERE user_id=?", [$u['id']]
|
||||
);
|
||||
$u['_mem'] = $mem;
|
||||
echo sprintf(" #%-4d %-38s %-22s role=%-16s active=%s sso=%-5s pwver=%-3s mcp=%s orgs=%s\n",
|
||||
$u['id'], $u['email'], $u['full_name'],
|
||||
$u['role'], $u['is_active'] ?? '-', $u['sso_identity_id'] ?? '-', $u['password_version'] ?? '-',
|
||||
$u['must_change_password'] ?? '-',
|
||||
$mem ? implode(',', array_map(fn($m)=>$m['organization_id'].($m['is_primary']?'(P)':''), $mem)) : 'NESSUNA(!! onboarding)');
|
||||
}
|
||||
|
||||
/* --- safety: expect --- */
|
||||
if ($expect !== null && count($users) !== $expect) {
|
||||
fwrite(STDERR, "\nABORT: risolti " . count($users) . " utenti ma --expect=$expect. Niente modifiche.\n");
|
||||
exit(3);
|
||||
}
|
||||
|
||||
/* --- piano --- */
|
||||
echo "\nPIANO:\n";
|
||||
echo $role ? " - role -> '$role' (dove diverso)\n" : " - role: invariato\n";
|
||||
echo $setPw ? " - password_hash -> (da PROV_PASSWORD), password_version INVARIATO, must_change_password=0\n" : " - password: invariata\n";
|
||||
echo $orgs ? " - membership garantita su org: " . implode(', ', array_map(fn($k)=>$k.($orgs[$k]?':primary':''), array_keys($orgs))) . " (role org_admin, idempotente)\n" : " - membership: invariata\n";
|
||||
|
||||
if (!$apply) { echo "\n[DRY-RUN] nessuna scrittura. Aggiungi --apply per eseguire.\n"; exit(0); }
|
||||
|
||||
/* --- APPLY (transazione) --- */
|
||||
$pwHash = $setPw ? password_hash($pw, PASSWORD_DEFAULT) : null;
|
||||
$changes = [];
|
||||
try {
|
||||
Database::beginTransaction();
|
||||
foreach ($users as $u) {
|
||||
$uid = (int)$u['id']; $line = [];
|
||||
if ($role !== null && $u['role'] !== $role) {
|
||||
Database::update('users', ['role'=>$role], 'id=?', [$uid]); $line[] = "role {$u['role']}->$role";
|
||||
}
|
||||
if ((int)$u['is_active'] !== 1) {
|
||||
Database::update('users', ['is_active'=>1], 'id=?', [$uid]); $line[] = "is_active 0->1";
|
||||
}
|
||||
if ($setPw) {
|
||||
Database::update('users', ['password_hash'=>$pwHash, 'must_change_password'=>0], 'id=?', [$uid]); $line[] = "password set (ver invariato)";
|
||||
}
|
||||
foreach ($orgs as $oid => $isPrimary) {
|
||||
// idempotente: UNIQUE (user_id, organization_id)
|
||||
Database::query(
|
||||
"INSERT INTO user_organizations (user_id, organization_id, role, is_primary)
|
||||
VALUES (?,?, 'org_admin', ?)
|
||||
ON DUPLICATE KEY UPDATE role=VALUES(role), is_primary=VALUES(is_primary)",
|
||||
[$uid, (int)$oid, $isPrimary ? 1 : 0]
|
||||
);
|
||||
$line[] = "org $oid".($isPrimary?'(P)':'');
|
||||
}
|
||||
$changes[$uid] = $line ? implode('; ', $line) : 'nessuna modifica (gia conforme)';
|
||||
}
|
||||
Database::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Database::rollback();
|
||||
fwrite(STDERR, "\nROLLBACK — errore: " . $e->getMessage() . "\n");
|
||||
exit(4);
|
||||
}
|
||||
|
||||
echo "\nAPPLICATO (commit ok):\n";
|
||||
foreach ($changes as $uid => $c) echo " #$uid: $c\n";
|
||||
|
||||
/* --- verifica post --- */
|
||||
echo "\nVERIFICA POST:\n";
|
||||
$ids = implode(',', array_map(fn($u)=>(int)$u['id'], $users));
|
||||
$after = Database::fetchAll("SELECT id,email,role,is_active,must_change_password,
|
||||
(SELECT COUNT(*) FROM user_organizations uo WHERE uo.user_id=users.id) AS org_count
|
||||
FROM users WHERE id IN ($ids) ORDER BY id");
|
||||
foreach ($after as $a) {
|
||||
echo sprintf(" #%-4d %-38s role=%-14s active=%s mcp=%s org_count=%d %s\n",
|
||||
$a['id'], $a['email'], $a['role'], $a['is_active'], $a['must_change_password'], $a['org_count'],
|
||||
$a['org_count'] > 0 ? 'OK (no onboarding)' : '!! ANCORA 0 ORG');
|
||||
}
|
||||
echo "\nFatto.\n";
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — Importer idempotente del framework canonico (Epic C / C1).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle di configurazione cfg_nis2_* (mig.046) e le
|
||||
* popola dal seed autoritativo application/data/nis2_framework_seed.json
|
||||
* (generato dai file docs/simon/*.xlsx — codifica NON variabile).
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS + INSERT ... ON DUPLICATE KEY UPDATE.
|
||||
* Rilanciabile senza duplicare né rompere le FK. Pensato per ricreare/riseedare
|
||||
* un ambiente in modo versionato (sostituisce i runner temporanei usa-e-getta).
|
||||
*
|
||||
* Uso (dentro il container app):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_framework.php
|
||||
*
|
||||
* NB: solo CLI. DB API via PDO singleton (Database::getInstance()), TLS gestito
|
||||
* dalla config dell'app.
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit("Solo da CLI.\n");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS cfg_nis2_ambiti (
|
||||
nist_code VARCHAR(16) NOT NULL, nist_descr TEXT NOT NULL,
|
||||
PRIMARY KEY (nist_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
"CREATE TABLE IF NOT EXISTS cfg_nis2_procedure (
|
||||
proc_code VARCHAR(16) NOT NULL, proc_descr TEXT NOT NULL,
|
||||
PRIMARY KEY (proc_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
"CREATE TABLE IF NOT EXISTS cfg_nis2_rischi (
|
||||
risk_code VARCHAR(16) NOT NULL, risk_descr TEXT NOT NULL,
|
||||
PRIMARY KEY (risk_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
"CREATE TABLE IF NOT EXISTS cfg_nis2_misure (
|
||||
misura_code VARCHAR(16) NOT NULL, nist_code VARCHAR(16) NOT NULL,
|
||||
area_politica VARCHAR(255) NOT NULL, misura_descr TEXT NOT NULL, ord INT NOT NULL,
|
||||
applies_important TINYINT(1) NOT NULL DEFAULT 1, applies_essential TINYINT(1) NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY (misura_code), KEY idx_misura_nist (nist_code), KEY idx_misura_ord (ord),
|
||||
CONSTRAINT fk_misura_ambito FOREIGN KEY (nist_code) REFERENCES cfg_nis2_ambiti (nist_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
"CREATE TABLE IF NOT EXISTS cfg_nis2_requisiti (
|
||||
id INT NOT NULL AUTO_INCREMENT, n INT NULL, misura_code VARCHAR(16) NOT NULL,
|
||||
requisito_code VARCHAR(32) NOT NULL, requisito_descr TEXT NOT NULL,
|
||||
proc_code VARCHAR(16) NULL, risk_code VARCHAR(16) NULL,
|
||||
applies_important TINYINT(1) NOT NULL DEFAULT 0, applies_essential TINYINT(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id), UNIQUE KEY uq_req (misura_code, requisito_code),
|
||||
KEY idx_req_misura (misura_code), KEY idx_req_proc (proc_code), KEY idx_req_risk (risk_code),
|
||||
CONSTRAINT fk_req_misura FOREIGN KEY (misura_code) REFERENCES cfg_nis2_misure (misura_code),
|
||||
CONSTRAINT fk_req_proc FOREIGN KEY (proc_code) REFERENCES cfg_nis2_procedure (proc_code),
|
||||
CONSTRAINT fk_req_risk FOREIGN KEY (risk_code) REFERENCES cfg_nis2_rischi (risk_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
$seedPath = __DIR__ . '/../data/nis2_framework_seed.json';
|
||||
$seed = json_decode(@file_get_contents($seedPath), true);
|
||||
if (!$seed) { fwrite(STDERR, "SEED non leggibile: $seedPath\n"); exit(1); }
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$st = $pdo->prepare("INSERT INTO cfg_nis2_ambiti (nist_code,nist_descr) VALUES (?,?)
|
||||
ON DUPLICATE KEY UPDATE nist_descr=VALUES(nist_descr)");
|
||||
foreach ($seed['ambiti_nist'] as $a) { $st->execute([$a['nist_code'], $a['nist_descr']]); }
|
||||
|
||||
$st = $pdo->prepare("INSERT INTO cfg_nis2_procedure (proc_code,proc_descr) VALUES (?,?)
|
||||
ON DUPLICATE KEY UPDATE proc_descr=VALUES(proc_descr)");
|
||||
foreach ($seed['procedure'] as $p) { $st->execute([$p['proc_code'], $p['proc_descr']]); }
|
||||
|
||||
$st = $pdo->prepare("INSERT INTO cfg_nis2_rischi (risk_code,risk_descr) VALUES (?,?)
|
||||
ON DUPLICATE KEY UPDATE risk_descr=VALUES(risk_descr)");
|
||||
foreach ($seed['rischi'] as $r) { $st->execute([$r['risk_code'], $r['risk_descr']]); }
|
||||
|
||||
$st = $pdo->prepare("INSERT INTO cfg_nis2_misure
|
||||
(misura_code,nist_code,area_politica,misura_descr,ord,applies_important,applies_essential)
|
||||
VALUES (?,?,?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE nist_code=VALUES(nist_code),area_politica=VALUES(area_politica),
|
||||
misura_descr=VALUES(misura_descr),ord=VALUES(ord),
|
||||
applies_important=VALUES(applies_important),applies_essential=VALUES(applies_essential)");
|
||||
foreach ($seed['misure'] as $m) {
|
||||
$st->execute([$m['misura_code'], $m['nist_code'], $m['area_politica'], $m['misura_descr'],
|
||||
$m['ord'], $m['applies_important'], $m['applies_essential']]);
|
||||
}
|
||||
|
||||
$st = $pdo->prepare("INSERT INTO cfg_nis2_requisiti
|
||||
(n,misura_code,requisito_code,requisito_descr,proc_code,risk_code,applies_important,applies_essential)
|
||||
VALUES (?,?,?,?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE n=VALUES(n),requisito_descr=VALUES(requisito_descr),
|
||||
proc_code=VALUES(proc_code),risk_code=VALUES(risk_code),
|
||||
applies_important=VALUES(applies_important),applies_essential=VALUES(applies_essential)");
|
||||
foreach ($seed['requisiti'] as $q) {
|
||||
$st->execute([$q['n'], $q['misura_code'], $q['requisito_code'], $q['requisito_descr'],
|
||||
$q['proc_code'], $q['risk_code'], $q['applies_important'], $q['applies_essential']]);
|
||||
}
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
fwrite(STDERR, "SEED FALLITO: " . $e->getMessage() . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counts = [
|
||||
'ambiti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_ambiti")->fetchColumn(),
|
||||
'procedure' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_procedure")->fetchColumn(),
|
||||
'rischi' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_rischi")->fetchColumn(),
|
||||
'misure' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_misure")->fetchColumn(),
|
||||
'requisiti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_nis2_requisiti")->fetchColumn(),
|
||||
];
|
||||
echo "OK seed-framework — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — DDL idempotente Audit interni (Modulo A, mig.055).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle del modulo Audit interni (ISO 27001 §9.2):
|
||||
* - internal_audits (programma/sessioni di audit)
|
||||
* - internal_audit_items (checklist: clausole 4-10, Annex A, NIS2, custom)
|
||||
* NESSUN dato di sistema: le checklist sono pre-popolate per-org dal controller
|
||||
* al create (clausole statiche + Annex A dal SoA dell'org).
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS (re-eseguibile senza errore).
|
||||
* Allineato a docs/sql/055_internal_audits.sql.
|
||||
*
|
||||
* Uso (dentro il container app):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_internal_audits.php
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
|
||||
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS internal_audits (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
code VARCHAR(20) NULL, title VARCHAR(255) NOT NULL,
|
||||
scope TEXT NULL, criteria TEXT NULL,
|
||||
planned_date DATE NULL, executed_date DATE NULL,
|
||||
status ENUM('planned','in_progress','completed','cancelled') NOT NULL DEFAULT 'planned',
|
||||
lead_auditor_user_id INT NULL, lead_auditor_role_id INT NULL,
|
||||
conclusion TEXT NULL, created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_intaud_org (organization_id),
|
||||
CONSTRAINT fk_intaud_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_intaud_lead_user FOREIGN KEY (lead_auditor_user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_intaud_lead_role FOREIGN KEY (lead_auditor_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_intaud_creator FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS internal_audit_items (
|
||||
id INT NOT NULL AUTO_INCREMENT, audit_id INT NOT NULL,
|
||||
ref_type ENUM('clause','annex_control','nis2_measure','custom') NOT NULL DEFAULT 'clause',
|
||||
ref_code VARCHAR(32) NULL, checkpoint TEXT NOT NULL,
|
||||
result ENUM('da_verificare','conforme','non_conforme','osservazione','opportunita','non_applicabile') NOT NULL DEFAULT 'da_verificare',
|
||||
note TEXT NULL, ord INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id), KEY idx_intauditem_audit (audit_id),
|
||||
CONSTRAINT fk_intauditem_audit FOREIGN KEY (audit_id) REFERENCES internal_audits (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
// Estende l'ENUM del calendario (review_schedule) con 'internal_audit' così la
|
||||
// data pianificata dell'audit compare nel calendario NIS2. Idempotente in effetto.
|
||||
// Include i valori già introdotti da seeder precedenti (stakeholder_activity) per
|
||||
// non regredirli; se 'internal_audit' è già presente l'ALTER è un no-op.
|
||||
try {
|
||||
$cur = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
|
||||
if (!str_contains($cur, "'internal_audit'")) {
|
||||
$vals = ['role','skill','inventory','procedure','risk','supplier','measure','custom','stakeholder_activity','internal_audit'];
|
||||
// conserva eventuali valori extra già presenti nell'ENUM corrente
|
||||
if (preg_match_all("/'([^']+)'/", $cur, $m)) {
|
||||
foreach ($m[1] as $v) { if (!in_array($v, $vals, true)) { $vals[] = $v; } }
|
||||
}
|
||||
$enum = "'" . implode("','", $vals) . "'";
|
||||
$pdo->exec("ALTER TABLE review_schedule MODIFY COLUMN entity_type ENUM($enum) NOT NULL");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
fwrite(STDERR, "WARN ALTER review_schedule: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['internal_audits', 'internal_audit_items'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
$enum = $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
|
||||
echo "OK seed-internal-audits — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "review_schedule.entity_type has internal_audit: " . (str_contains((string) $enum, 'internal_audit') ? 'YES' : 'NO') . "\n";
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — DDL idempotente Riesame di Direzione (ISO 27001 §9.3, mig.056).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle del Modulo B "Riesame di Direzione":
|
||||
* - management_reviews (il verbale: input aggregati congelati + esiti)
|
||||
* - management_review_decisions (decisioni/output → owner ruolo, scadenza, CAPA)
|
||||
*
|
||||
* NESSUN dato di sistema: i riesami sono per-org (creati dall'utente).
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS (re-eseguibile senza errore; in caso
|
||||
* di tabella già presente ignora gli errno 1050/1061). Allineato a
|
||||
* docs/sql/056_management_reviews.sql.
|
||||
*
|
||||
* Uso (dentro il container app, o sull'host con la stessa connessione PDO):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_management_reviews.php
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
|
||||
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS management_reviews (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
organization_id INT NOT NULL,
|
||||
code VARCHAR(20) NULL,
|
||||
review_date DATE NULL,
|
||||
period_label VARCHAR(100) NULL,
|
||||
chair_user_id INT NULL,
|
||||
attendees JSON NULL,
|
||||
status ENUM('draft','approved') NOT NULL DEFAULT 'draft',
|
||||
approved_by INT NULL,
|
||||
approved_at DATETIME NULL,
|
||||
snapshot JSON NULL,
|
||||
conclusions TEXT NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_mgr_org (organization_id),
|
||||
CONSTRAINT fk_mgr_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_mgr_chair FOREIGN KEY (chair_user_id) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_mgr_approved FOREIGN KEY (approved_by) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_mgr_creator FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS management_review_decisions (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
review_id INT NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
owner_role_id INT NULL,
|
||||
due_date DATE NULL,
|
||||
status ENUM('open','in_progress','done') NOT NULL DEFAULT 'open',
|
||||
capa_id INT NULL,
|
||||
ord INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_mrd_review (review_id),
|
||||
CONSTRAINT fk_mrd_review FOREIGN KEY (review_id) REFERENCES management_reviews (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_mrd_owner FOREIGN KEY (owner_role_id) REFERENCES org_roles (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['management_reviews', 'management_review_decisions'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
echo "OK seed-management-reviews — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/** DDL idempotente Controlli periodici (mig.057). Crea le tabelle ed estende
|
||||
* l'ENUM non_conformities.source con 'monitoring' (preservando i valori).
|
||||
* Uso: docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_periodic_controls.php */
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS periodic_controls (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
code VARCHAR(20) NULL, title VARCHAR(255) NOT NULL, description TEXT NULL, category VARCHAR(100) NULL,
|
||||
control_ref VARCHAR(32) NULL, owner_role_id INT NULL,
|
||||
frequency ENUM('giornaliero','settimanale','mensile','trimestrale','semestrale','annuale','custom') NOT NULL DEFAULT 'mensile',
|
||||
frequency_days INT NULL, method TEXT NULL, next_due_date DATE NULL, last_executed_at DATE NULL,
|
||||
status ENUM('active','suspended') NOT NULL DEFAULT 'active', created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), UNIQUE KEY uk_pctl_code (organization_id, code),
|
||||
KEY idx_pctl_org (organization_id), KEY idx_pctl_due (organization_id, next_due_date),
|
||||
CONSTRAINT fk_pctl_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_pctl_role FOREIGN KEY (owner_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_pctl_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS periodic_control_executions (
|
||||
id INT NOT NULL AUTO_INCREMENT, control_id INT NOT NULL, executed_at DATE NOT NULL, executed_by INT NULL,
|
||||
outcome ENUM('conforme','non_conforme','parziale','non_applicabile') NOT NULL DEFAULT 'conforme',
|
||||
notes TEXT NULL, ncr_id INT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_pcexec_control (control_id),
|
||||
CONSTRAINT fk_pcexec_control FOREIGN KEY (control_id) REFERENCES periodic_controls (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_pcexec_user FOREIGN KEY (executed_by) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_pcexec_ncr FOREIGN KEY (ncr_id) REFERENCES non_conformities (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
// UNIQUE (organization_id, code) idempotente — la tabella su prod può preesistere senza
|
||||
// l'indice (creata prima del fix). Anti-doppione su CTL-NNN concorrenti (vedi retry in create()).
|
||||
try { $pdo->exec("ALTER TABLE periodic_controls ADD UNIQUE KEY uk_pctl_code (organization_id, code)"); }
|
||||
catch (PDOException $e) {
|
||||
$c = $e->errorInfo[1] ?? 0;
|
||||
if ($c === 1062) { fwrite(STDERR, "WARN uk_pctl_code: doppioni esistenti, indice NON aggiunto.\n"); }
|
||||
elseif (!in_array($c, [1061], true)) { throw $e; } // 1061 = indice già presente
|
||||
}
|
||||
|
||||
// Estende non_conformities.source con 'monitoring' preservando i valori esistenti.
|
||||
try {
|
||||
$cur = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'non_conformities' AND COLUMN_NAME = 'source'")->fetchColumn();
|
||||
if (stripos($cur, "'monitoring'") === false) {
|
||||
$vals = [];
|
||||
if (preg_match_all("/'([^']+)'/", $cur, $m)) { $vals = $m[1]; }
|
||||
if (!in_array('monitoring', $vals, true)) { $vals[] = 'monitoring'; }
|
||||
if (!$vals) { $vals = ['assessment','audit','incident','supplier_review','management_review','external_audit','monitoring','other']; }
|
||||
$enum = implode(',', array_map(fn($v) => "'" . $v . "'", $vals));
|
||||
$pdo->exec("ALTER TABLE non_conformities MODIFY COLUMN source ENUM($enum) NOT NULL DEFAULT 'assessment'");
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
fwrite(STDERR, "WARN ALTER non_conformities.source: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['periodic_controls', 'periodic_control_executions'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
$src = (string) $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'non_conformities' AND COLUMN_NAME = 'source'")->fetchColumn();
|
||||
echo "OK seed-periodic-controls — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "non_conformities.source has monitoring: " . (str_contains($src, 'monitoring') ? 'YES' : 'NO') . "\n";
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — DDL idempotente Attività stakeholder (Epic C / C5.2a, mig.052).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle del modulo Attività stakeholder ed estende
|
||||
* l'ENUM entity_type di review_schedule con 'stakeholder_activity' (calendario NIS2).
|
||||
* NESSUN dato di sistema: i questionari tipo sono per-org.
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS + ALTER MODIFY (re-eseguibile senza
|
||||
* errore). Allineato a docs/sql/052_stakeholder_activities.sql.
|
||||
*
|
||||
* Uso (dentro il container app):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_stakeholder_activities.php
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da CLI.\n"); }
|
||||
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS stk_questionnaire_templates (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL, kind ENUM('questionnaire','read_ack') NOT NULL DEFAULT 'questionnaire',
|
||||
description TEXT NULL, content TEXT NULL, questions JSON NULL,
|
||||
status ENUM('draft','active','archived') NOT NULL DEFAULT 'active',
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_stkqt_org (organization_id),
|
||||
CONSTRAINT fk_stkqt_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stkqt_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_template_procedures (
|
||||
template_id INT NOT NULL, policy_id INT NOT NULL,
|
||||
PRIMARY KEY (template_id, policy_id), KEY idx_stktp_policy (policy_id),
|
||||
CONSTRAINT fk_stktp_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stktp_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_template_misure (
|
||||
template_id INT NOT NULL, misura_code VARCHAR(16) NOT NULL,
|
||||
PRIMARY KEY (template_id, misura_code), KEY idx_stktm_misura (misura_code),
|
||||
CONSTRAINT fk_stktm_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stktm_misura FOREIGN KEY (misura_code) REFERENCES cfg_nis2_misure (misura_code) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_template_requisiti (
|
||||
template_id INT NOT NULL, requisito_id INT NOT NULL,
|
||||
PRIMARY KEY (template_id, requisito_id), KEY idx_stktr_req (requisito_id),
|
||||
CONSTRAINT fk_stktr_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stktr_req FOREIGN KEY (requisito_id) REFERENCES cfg_nis2_requisiti (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_activities (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
title VARCHAR(255) NOT NULL, type ENUM('questionnaire','read_ack','action') NOT NULL DEFAULT 'questionnaire',
|
||||
template_id INT NULL, description TEXT NULL,
|
||||
assign_mode ENUM('by_code','individual') NOT NULL DEFAULT 'individual', stak_code VARCHAR(16) NULL,
|
||||
planned_date DATE NULL, due_date DATE NULL,
|
||||
status ENUM('draft','scheduled','sent','in_progress','completed','cancelled') NOT NULL DEFAULT 'draft',
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_stka_org (organization_id), KEY idx_stka_tpl (template_id),
|
||||
CONSTRAINT fk_stka_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stka_tpl FOREIGN KEY (template_id) REFERENCES stk_questionnaire_templates (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_stka_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_activity_targets (
|
||||
id INT NOT NULL AUTO_INCREMENT, activity_id INT NOT NULL, stakeholder_id INT NOT NULL,
|
||||
access_token_hash CHAR(64) NULL,
|
||||
state ENUM('pending','sent','responded','acknowledged','expired') NOT NULL DEFAULT 'pending',
|
||||
sent_at DATETIME NULL, responded_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), UNIQUE KEY uq_stkat (activity_id, stakeholder_id),
|
||||
KEY idx_stkat_stk (stakeholder_id), KEY idx_stkat_token (access_token_hash),
|
||||
CONSTRAINT fk_stkat_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stkat_stk FOREIGN KEY (stakeholder_id) REFERENCES stakeholders (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_activity_procedures (
|
||||
activity_id INT NOT NULL, policy_id INT NOT NULL,
|
||||
PRIMARY KEY (activity_id, policy_id), KEY idx_stkap_policy (policy_id),
|
||||
CONSTRAINT fk_stkap_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stkap_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
// C5.2b (mig.053) — feedback: risposte + commenti
|
||||
"CREATE TABLE IF NOT EXISTS stk_activity_responses (
|
||||
id INT NOT NULL AUTO_INCREMENT, target_id INT NOT NULL,
|
||||
answers JSON NULL, acknowledged_at DATETIME NULL,
|
||||
respondent_name VARCHAR(255) NULL, respondent_email VARCHAR(255) NULL, ip_address VARCHAR(45) NULL,
|
||||
submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), UNIQUE KEY uq_stkar_target (target_id),
|
||||
CONSTRAINT fk_stkar_target FOREIGN KEY (target_id) REFERENCES stk_activity_targets (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stk_activity_comments (
|
||||
id INT NOT NULL AUTO_INCREMENT, activity_id INT NOT NULL, body TEXT NOT NULL,
|
||||
author_kind ENUM('internal','external') NOT NULL DEFAULT 'internal',
|
||||
author_user_id INT NULL, author_label VARCHAR(255) NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id), KEY idx_stkac_act (activity_id),
|
||||
CONSTRAINT fk_stkac_act FOREIGN KEY (activity_id) REFERENCES stk_activities (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stkac_user FOREIGN KEY (author_user_id) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
// Estende l'ENUM del calendario (review_schedule). Idempotente in effetto.
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE review_schedule
|
||||
MODIFY COLUMN entity_type ENUM('role','skill','inventory','procedure','risk','supplier','measure','custom','stakeholder_activity') NOT NULL");
|
||||
} catch (PDOException $e) {
|
||||
fwrite(STDERR, "WARN ALTER review_schedule: " . $e->getMessage() . "\n");
|
||||
}
|
||||
|
||||
// mig.054 — scadenza magic-link (idempotente: ignora 1060 colonna già esistente).
|
||||
try {
|
||||
$pdo->exec("ALTER TABLE stk_activity_targets ADD COLUMN token_expires_at DATETIME NULL DEFAULT NULL AFTER access_token_hash");
|
||||
} catch (PDOException $e) {
|
||||
if (($e->errorInfo[1] ?? 0) !== 1060) { fwrite(STDERR, "WARN ALTER stk_activity_targets: " . $e->getMessage() . "\n"); }
|
||||
}
|
||||
|
||||
$counts = [];
|
||||
foreach (['stk_questionnaire_templates','stk_template_procedures','stk_template_misure','stk_template_requisiti',
|
||||
'stk_activities','stk_activity_targets','stk_activity_procedures',
|
||||
'stk_activity_responses','stk_activity_comments'] as $t) {
|
||||
$counts[$t] = (int) $pdo->query("SELECT COUNT(*) FROM $t")->fetchColumn();
|
||||
}
|
||||
$enum = $pdo->query("SELECT COLUMN_TYPE FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'review_schedule' AND COLUMN_NAME = 'entity_type'")->fetchColumn();
|
||||
echo "OK seed-stakeholder-activities — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
echo "review_schedule.entity_type has stakeholder_activity: " . (str_contains((string) $enum, 'stakeholder_activity') ? 'YES' : 'NO') . "\n";
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile — Importer idempotente del registro Stakeholder (Epic C / C5.1).
|
||||
* ----------------------------------------------------------------------------
|
||||
* Crea (se mancanti) le tabelle del modulo Stakeholder (mig.051) e popola i dati
|
||||
* di SISTEMA: i 4 quadranti della matrice di Mendelow (range corretti, Q4 potere
|
||||
* 0-2) e i 30 tipi di stakeholder Stak.01-30 (6 Interni / 24 Esterni) dal seed
|
||||
* autoritativo application/data/nis2_framework_seed.json (chiave stakeholder_types,
|
||||
* generata dai file docs/simon/*.xlsx — codifica NON variabile).
|
||||
*
|
||||
* Idempotente: CREATE TABLE IF NOT EXISTS + INSERT ... ON DUPLICATE KEY UPDATE
|
||||
* sul PK `code` (nessuna trappola NULL-dedupe: il codice E' la chiave primaria).
|
||||
* Rilanciabile senza duplicare ne' rompere le FK. Sostituisce i runner usa-e-getta.
|
||||
*
|
||||
* NB: la matrice di Mendelow (potere/interesse) e' BUONA PRASSI, NON un obbligo
|
||||
* NIS2; l'obbligo e' GV.SC-02 (ruoli/responsabilita verso fornitori/clienti/partner).
|
||||
*
|
||||
* Uso (dentro il container app):
|
||||
* docker exec nis2-app php /var/www/nis2-agile/application/cli/seed_stakeholders.php
|
||||
*
|
||||
* NB: solo CLI. DB API via PDO singleton (Database::getInstance()), TLS gestito
|
||||
* dalla config dell'app.
|
||||
*/
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
http_response_code(403);
|
||||
exit("Solo da CLI.\n");
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
$pdo = Database::getInstance();
|
||||
$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
// --- DDL (idempotente, FK dentro il CREATE; allineato a docs/sql/051_*.sql) ---
|
||||
$ddl = [
|
||||
"CREATE TABLE IF NOT EXISTS cfg_stakeholder_types (
|
||||
code VARCHAR(16) NOT NULL, organization_id INT NULL,
|
||||
tipo ENUM('Interno','Esterno') NOT NULL, descr TEXT NOT NULL, ord INT NOT NULL DEFAULT 0,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (code), KEY idx_cfg_stk_type_org (organization_id),
|
||||
CONSTRAINT fk_cfg_stk_type_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_cfg_stk_type_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS cfg_stakeholder_quadrants (
|
||||
code VARCHAR(4) NOT NULL, organization_id INT NULL,
|
||||
label VARCHAR(64) NOT NULL, strategy TEXT NULL,
|
||||
power_min TINYINT NOT NULL, power_max TINYINT NOT NULL,
|
||||
interest_min TINYINT NOT NULL, interest_max TINYINT NOT NULL, ord INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (code), KEY idx_cfg_stk_quad_org (organization_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stakeholders (
|
||||
id INT NOT NULL AUTO_INCREMENT, organization_id INT NOT NULL,
|
||||
stak_code VARCHAR(16) NOT NULL, name VARCHAR(255) NOT NULL,
|
||||
org_role_id INT NULL, supplier_id INT NULL,
|
||||
power TINYINT NULL, interest TINYINT NULL,
|
||||
contact_name VARCHAR(255) NULL, contact_email VARCHAR(255) NULL, notes TEXT NULL,
|
||||
created_by INT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_stk_org (organization_id), KEY idx_stk_org_code (organization_id, stak_code),
|
||||
KEY idx_stk_role (org_role_id), KEY idx_stk_supplier (supplier_id),
|
||||
CONSTRAINT fk_stk_org FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stk_code FOREIGN KEY (stak_code) REFERENCES cfg_stakeholder_types (code),
|
||||
CONSTRAINT fk_stk_role FOREIGN KEY (org_role_id) REFERENCES org_roles (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_stk_supplier FOREIGN KEY (supplier_id) REFERENCES suppliers (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_stk_user FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
|
||||
"CREATE TABLE IF NOT EXISTS stakeholder_procedures (
|
||||
stakeholder_id INT NOT NULL, policy_id INT NOT NULL,
|
||||
PRIMARY KEY (stakeholder_id, policy_id), KEY idx_stk_proc_policy (policy_id),
|
||||
CONSTRAINT fk_stk_proc_stk FOREIGN KEY (stakeholder_id) REFERENCES stakeholders (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_stk_proc_policy FOREIGN KEY (policy_id) REFERENCES policies (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci",
|
||||
];
|
||||
foreach ($ddl as $stmt) {
|
||||
try { $pdo->exec($stmt); }
|
||||
catch (PDOException $e) { if (!in_array($e->errorInfo[1] ?? 0, [1050, 1061], true)) { throw $e; } }
|
||||
}
|
||||
|
||||
// --- Dati di sistema ---
|
||||
$seedPath = __DIR__ . '/../data/nis2_framework_seed.json';
|
||||
$seed = json_decode(@file_get_contents($seedPath), true);
|
||||
if (!$seed || empty($seed['stakeholder_types'])) {
|
||||
fwrite(STDERR, "SEED non leggibile o privo di stakeholder_types: $seedPath\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// 4 quadranti di sistema — matrice di Mendelow (x=interesse, y=potere).
|
||||
// REFUSO corretto: Q4 (basso-sinistra, Monitorare) ha potere 0-2 (non 3-5).
|
||||
$quadrants = [
|
||||
// code, label, strategy, power_min, power_max, interest_min, interest_max, ord
|
||||
['Q1', 'Tenere soddisfatti (Keep Satisfied)',
|
||||
'Stakeholder istituzionali: forte potere di blocco, scarso interesse quotidiano. Tenerli soddisfatti ed evitare scontenti improvvisi.',
|
||||
3, 5, 0, 2, 1],
|
||||
['Q2', 'Gestire attivamente (Manage Closely)',
|
||||
'Stakeholder chiave: decisori principali, supporto vitale. Gestione attiva e coinvolgimento costante.',
|
||||
3, 5, 3, 5, 2],
|
||||
['Q3', 'Tenere informati (Keep Informed)',
|
||||
'Stakeholder operativi: molto coinvolti ma scarso peso decisionale. Tenerli informati.',
|
||||
0, 2, 3, 5, 3],
|
||||
['Q4', 'Monitorare (Monitor)',
|
||||
'Stakeholder marginali: basso potere e basso interesse. Monitoraggio costante col minimo sforzo, comunicazioni periodiche non dispendiose.',
|
||||
0, 2, 0, 2, 4],
|
||||
];
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stQ = $pdo->prepare(
|
||||
"INSERT INTO cfg_stakeholder_quadrants
|
||||
(code,label,strategy,power_min,power_max,interest_min,interest_max,ord,organization_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,NULL)
|
||||
ON DUPLICATE KEY UPDATE label=VALUES(label),strategy=VALUES(strategy),
|
||||
power_min=VALUES(power_min),power_max=VALUES(power_max),
|
||||
interest_min=VALUES(interest_min),interest_max=VALUES(interest_max),ord=VALUES(ord)"
|
||||
);
|
||||
foreach ($quadrants as $q) { $stQ->execute($q); }
|
||||
|
||||
// 30 tipi di sistema: code PK, organization_id NULL, tipo Interno/Esterno, descr verbatim.
|
||||
$stT = $pdo->prepare(
|
||||
"INSERT INTO cfg_stakeholder_types (code,organization_id,tipo,descr,ord)
|
||||
VALUES (?,NULL,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE tipo=VALUES(tipo),descr=VALUES(descr),ord=VALUES(ord)"
|
||||
);
|
||||
$ord = 0;
|
||||
foreach ($seed['stakeholder_types'] as $t) {
|
||||
$ord++;
|
||||
$tipo = ($t['tipo'] === 'Interno') ? 'Interno' : 'Esterno';
|
||||
$stT->execute([$t['code'], $tipo, $t['descr'], $ord]);
|
||||
}
|
||||
$pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
$pdo->rollBack();
|
||||
fwrite(STDERR, "SEED FALLITO: " . $e->getMessage() . "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$counts = [
|
||||
'quadranti' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_quadrants WHERE organization_id IS NULL")->fetchColumn(),
|
||||
'tipi_sistema' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_types WHERE organization_id IS NULL")->fetchColumn(),
|
||||
'tipi_interni' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_types WHERE organization_id IS NULL AND tipo='Interno'")->fetchColumn(),
|
||||
'tipi_esterni' => (int) $pdo->query("SELECT COUNT(*) FROM cfg_stakeholder_types WHERE organization_id IS NULL AND tipo='Esterno'")->fetchColumn(),
|
||||
];
|
||||
echo "OK seed-stakeholders — " . json_encode($counts, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
@@ -14,6 +14,21 @@ define('APP_NAME', Env::get('APP_NAME', 'NIS2 Agile'));
|
||||
define('APP_VERSION', Env::get('APP_VERSION', '1.0.0'));
|
||||
define('APP_URL', Env::get('APP_URL', 'http://localhost:8080'));
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// KILL-SWITCH INVIO EMAIL (ambiente con soli dati demo)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Quando false (default), EmailService NON effettua NESSUNA chiamata HTTP al relay:
|
||||
// ogni invio (incidenti, training, inviti, reminder, OTP portale fornitori, ecc.)
|
||||
// viene registrato a log e scartato.
|
||||
// FAIL-SAFE: l'invio email e' DISABILITATO di default a prescindere da APP_ENV
|
||||
// (l'ambiente contiene solo dati DEMO). Le email partono SOLO con override
|
||||
// esplicito EMAIL_SENDING_ENABLED=true (env/vault). Cosi nessun invio accidentale
|
||||
// a indirizzi reali finche' non si decide consapevolmente di attivarli.
|
||||
define('EMAIL_SENDING_ENABLED', filter_var(
|
||||
Env::get('EMAIL_SENDING_ENABLED', 'false'),
|
||||
FILTER_VALIDATE_BOOLEAN
|
||||
));
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PERCORSI
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -33,6 +48,13 @@ define('JWT_ALGORITHM', 'HS256');
|
||||
define('JWT_EXPIRES_IN', Env::int('JWT_EXPIRES_IN', 7200));
|
||||
define('JWT_REFRESH_EXPIRES_IN', Env::int('JWT_REFRESH_EXPIRES_IN', 604800));
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PROVISIONING (B2B — lg231 e altri sistemi Agile)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Secret master per provisioning automatico da sistemi Agile partner.
|
||||
// lg231 lo usa per POST /api/services/provision (onboarding automatico tenant).
|
||||
define('PROVISION_SECRET', Env::get('PROVISION_SECRET', 'nis2_prov_dev_secret'));
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// PASSWORD POLICY
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
@@ -63,6 +85,12 @@ define('RATE_LIMIT_AUTH_LOGIN', [
|
||||
define('RATE_LIMIT_AUTH_REGISTER', [
|
||||
['max' => 3, 'window_seconds' => 600],
|
||||
]);
|
||||
// Password reset (Fase 3 / G08): 3 richieste/h per IP+email
|
||||
define('RATE_LIMIT_AUTH_FORGOT', [
|
||||
['max' => 3, 'window_seconds' => 3600],
|
||||
]);
|
||||
// TTL token reset password (decisione utente §10.4: 30 min)
|
||||
define('PASSWORD_RESET_TTL_SECONDS', 1800);
|
||||
define('RATE_LIMIT_AI', [
|
||||
['max' => 10, 'window_seconds' => 60],
|
||||
['max' => 100, 'window_seconds' => 3600],
|
||||
@@ -75,6 +103,38 @@ define('ANTHROPIC_API_KEY', Env::get('ANTHROPIC_API_KEY', ''));
|
||||
define('ANTHROPIC_MODEL', Env::get('ANTHROPIC_MODEL', 'claude-sonnet-4-5-20250929'));
|
||||
define('ANTHROPIC_MAX_TOKENS', Env::int('ANTHROPIC_MAX_TOKENS', 4096));
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// VOCEA (Whistleblowing zero-knowledge — integrazione, DISABILITATA di default)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// SCAFFOLD DORMIENTE: con VOCEA_ENABLED=false (default) VoceaService non effettua
|
||||
// nessuna chiamata di rete. Attivazione a MS live: applicare migrate_063 +
|
||||
// VOCEA_ENABLED=true + VOCEA_API_KEY (vault tier1__nis2-app__vocea__*) +
|
||||
// slug del canale su organizations.vocea_tenant_slug (status='active').
|
||||
define('VOCEA_ENABLED', filter_var(Env::get('VOCEA_ENABLED', 'false'), FILTER_VALIDATE_BOOLEAN));
|
||||
define('VOCEA_BASE_URL', Env::get('VOCEA_BASE_URL', 'https://api.vocea.cloud/api/wb/v1')); // API integratori (X-API-Key)
|
||||
define('VOCEA_API_KEY', Env::get('VOCEA_API_KEY', '')); // wbk_... per-tenant (da vault)
|
||||
define('VOCEA_PORTAL_URL', Env::get('VOCEA_PORTAL_URL', 'https://app.vocea.cloud')); // portale embed via wb-link.js
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// VOICE / TTS (ARIA voce naturale — proxy same-origin a nexus-voice-ms AgileHub)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
define('VOICE_MS_URL', Env::get('VOICE_MS_URL', 'http://172.21.0.1:4215/tts/speak')); // gateway nis2-network → host:4215
|
||||
define('TTS_VOICE_ID', Env::get('TTS_VOICE_ID', 'EXAVITQu4vr4xnSDxMaL')); // voce "Sarah" (come AllTax/TRPG)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// CERTISOURCE (atti-service.php)
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
define('CERTISOURCE_API_URL', Env::get('CERTISOURCE_API_URL', 'https://certisource.it/atti-service.php'));
|
||||
define('CERTISOURCE_API_KEY', Env::get('CERTISOURCE_API_KEY', '')); // cs_pat_...
|
||||
define('CERTISOURCE_POLL_MAX', Env::int('CERTISOURCE_POLL_MAX', 30)); // max tentativi polling
|
||||
define('CERTISOURCE_POLL_SEC', Env::int('CERTISOURCE_POLL_SEC', 3)); // secondi tra poll
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// FEEDBACK & SEGNALAZIONI
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
define('FEEDBACK_RESOLVE_PASSWORD', Env::get('FEEDBACK_RESOLVE_PASSWORD', ''));
|
||||
define('FEEDBACK_WORKER_LOG', Env::get('FEEDBACK_WORKER_LOG', '/var/log/nis2/feedback-worker.log'));
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// TIMEZONE
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -41,6 +41,11 @@ class Database
|
||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
|
||||
];
|
||||
|
||||
// TLS-in-transito (VIGILE 2026-06-10) — pre-equip, GATED, DEFAULT OFF.
|
||||
// Unione con '+' (NON array_merge): preserva le chiavi-intere delle
|
||||
// opzioni PDO (array_merge le re-indicizzerebbe). Vedi doc VIGILE §14.
|
||||
$options = $options + self::sslOptions();
|
||||
|
||||
try {
|
||||
self::$instance = new PDO($dsn, DB_USER, DB_PASS, $options);
|
||||
} catch (PDOException $e) {
|
||||
@@ -55,6 +60,55 @@ class Database
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opzioni PDO per TLS-in-transito verso MySQL (VIGILE 2026-06-10, pre-equip).
|
||||
*
|
||||
* GATED, DEFAULT OFF: ritorna [] (nessuna modifica al comportamento attuale)
|
||||
* a meno che NON sia attivato esplicitamente tramite:
|
||||
* - env DB_SSL=true (canonico)
|
||||
* - OPPURE flag-file application/config/.db_ssl_on (robusto anche in
|
||||
* PHP-FPM dove getenv() puo' non propagare; nessun recreate necessario)
|
||||
*
|
||||
* IMPORTANTE (doc VIGILE §11): PHP PDO/mysqlnd NON cifra senza un CA file.
|
||||
* MYSQL_ATTR_SSL_VERIFY_SERVER_CERT=false DA SOLO connette IN CHIARO. Quindi
|
||||
* il TLS si attiva solo se il CA e' presente e leggibile; altrimenti si
|
||||
* resta in chiaro (come oggi) e si logga — MAI una connessione "a meta'".
|
||||
* CA host self-signed => VERIFY_SERVER_CERT=false (cifra il canale; la
|
||||
* verifica della CA e' hardening successivo, coordinato con VIGILE).
|
||||
*
|
||||
* @return array opzioni PDO SSL (vuoto se disattivato o CA mancante)
|
||||
*/
|
||||
private static function sslOptions(): array
|
||||
{
|
||||
$enabled = Env::bool('DB_SSL', false) || is_file(__DIR__ . '/.db_ssl_on');
|
||||
if (!$enabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// GUARD anti-outage (lezione 2026-06-10, 3 outage): il TLS NON si applica a
|
||||
// una connessione via UNIX socket (DB_HOST 'localhost', vuoto o path '/...').
|
||||
// Forzarlo da' PDOException [2002] "Cannot connect to MySQL using SSL" su OGNI
|
||||
// richiesta. Il socket e' IPC locale (nessun transito di rete da cifrare):
|
||||
// restiamo in chiaro + log, anche se il flag/env e' attivo. SSL si applica solo
|
||||
// alle connessioni TCP (es. 127.0.0.1 o un host di rete).
|
||||
$host = defined('DB_HOST') ? (string) DB_HOST : '';
|
||||
if ($host === '' || strcasecmp($host, 'localhost') === 0 || $host[0] === '/') {
|
||||
error_log('[Database] DB_SSL attivo ma connessione via UNIX socket (DB_HOST=' . $host . '): SSL non applicabile, resto in chiaro (socket = IPC locale, nessun transito da cifrare).');
|
||||
return [];
|
||||
}
|
||||
|
||||
$ca = Env::get('DB_SSL_CA', __DIR__ . '/db-ca.pem');
|
||||
if (!$ca || !is_file($ca) || !is_readable($ca)) {
|
||||
error_log('[Database] DB_SSL attivo ma CA non trovato/leggibile (' . $ca . '): connessione NON cifrata (serve il CA file).');
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
PDO::MYSQL_ATTR_SSL_CA => $ca,
|
||||
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Esegue una query con parametri
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - Registro Fonti Normative Certe
|
||||
* ----------------------------------------------------------------------------
|
||||
* SINGLE SOURCE OF TRUTH per le fonti normative autoritative citabili.
|
||||
*
|
||||
* Principio (richiesta utente 2026-05-29): ogni scelta/risposta della
|
||||
* piattaforma — sia nell'AI sia nell'help — DEVE riferirsi a fonti certe e
|
||||
* citarle esplicitamente. Vietato inventare riferimenti normativi.
|
||||
*
|
||||
* I PDF originali risiedono in docs/nis2/ (repo) e sono ingeriti nella
|
||||
* Knowledge Base (collection Qdrant nis2_kb, scope SYSTEM) per il grounding RAG.
|
||||
*
|
||||
* Usato da:
|
||||
* - AIService (iniezione nei system prompt: "cita queste fonti")
|
||||
* - public/js/help.js (riferimenti normativi nell'help contestuale, via /api/... o statico)
|
||||
* - scripts/ingest-nis2-sources.php (ingest PDF -> KB)
|
||||
*/
|
||||
|
||||
return [
|
||||
'nis2_directive' => [
|
||||
'key' => 'nis2_directive',
|
||||
'short' => 'Direttiva NIS2',
|
||||
'citation' => 'Direttiva (UE) 2022/2555 (NIS2)',
|
||||
'full' => 'Direttiva (UE) 2022/2555 del Parlamento europeo e del Consiglio del 14 dicembre 2022 relativa a misure per un livello comune elevato di cibersicurezza nell\'Unione (NIS2)',
|
||||
'file' => 'docs/nis2/Dir2022_2555_UE_NIS2_ITA.pdf',
|
||||
'authority' => 'Parlamento europeo e Consiglio UE',
|
||||
'url' => 'https://eur-lex.europa.eu/legal-content/IT/TXT/?uri=CELEX:32022L2555',
|
||||
],
|
||||
'cer_directive' => [
|
||||
'key' => 'cer_directive',
|
||||
'short' => 'Direttiva CER',
|
||||
'citation' => 'Direttiva (UE) 2022/2557 (CER)',
|
||||
'full' => 'Direttiva (UE) 2022/2557 del Parlamento europeo e del Consiglio del 14 dicembre 2022 relativa alla resilienza dei soggetti critici (Critical Entities Resilience)',
|
||||
'file' => 'docs/nis2/Dir2022_2557_UE_ITA.pdf',
|
||||
'authority' => 'Parlamento europeo e Consiglio UE',
|
||||
'url' => 'https://eur-lex.europa.eu/legal-content/IT/TXT/?uri=CELEX:32022L2557',
|
||||
],
|
||||
'dlgs_138_2024' => [
|
||||
'key' => 'dlgs_138_2024',
|
||||
'short' => 'D.Lgs. 138/2024',
|
||||
'citation' => 'D.Lgs. 4 settembre 2024, n. 138',
|
||||
'full' => 'Decreto legislativo 4 settembre 2024, n. 138 - Recepimento della direttiva (UE) 2022/2555 (decreto NIS)',
|
||||
'file' => null,
|
||||
'authority' => 'Repubblica Italiana',
|
||||
'url' => 'https://www.gazzettaufficiale.it/eli/id/2024/09/01/24G00155/sg',
|
||||
],
|
||||
'determina_164179_2025' => [
|
||||
'key' => 'determina_164179_2025',
|
||||
'short' => 'Determina ACN 164179/2025',
|
||||
'citation' => 'Determinazione ACN n. 164179 del 14 aprile 2025',
|
||||
'full' => 'Determinazione del Direttore generale dell\'Agenzia per la Cybersicurezza Nazionale n. 164179 del 14 aprile 2025, in attuazione del D.Lgs. 138/2024, che stabilisce modalita e specifiche di base per gli obblighi di cui agli articoli 23, 24, 25, 29 e 32 (incluse classificazione e notifica degli incidenti significativi: Allegato 3 = soggetti IMPORTANTI, Allegato 4 = soggetti ESSENZIALI).',
|
||||
'file' => 'docs/nis2/Determina164179_apr2025.pdf',
|
||||
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
||||
'url' => 'https://www.acn.gov.it/portale/w/nis-avviata-la-seconda-fase',
|
||||
],
|
||||
'determina_333017_2025' => [
|
||||
'key' => 'determina_333017_2025',
|
||||
'short' => 'Determina ACN 333017/2025 (storica, superata)',
|
||||
'citation' => 'Determinazione ACN n. 333017 del 22 settembre 2025 (SUPERATA dalla n. 379887/2025)',
|
||||
'full' => 'Determinazione del Direttore generale ACN n. 333017 del 22 settembre 2025 [data-giorno da confermare sul testo ufficiale]: termini, modalita e procedimenti di utilizzo e accesso alla piattaforma digitale ACN, ulteriori informazioni che i soggetti devono fornire e designazione dei rappresentanti NIS sul territorio nazionale. NOTA: citazione STORICA, SUPERATA dalla Determinazione ACN n. 379887/2025 (efficace dal 31 dicembre 2025); per piattaforma/registrazione/designazioni fare riferimento alla 379887.',
|
||||
'file' => 'docs/nis2/Determina333017_sett2025.pdf',
|
||||
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
||||
'url' => 'https://www.acn.gov.it/portale/w/nis-avviata-la-seconda-fase',
|
||||
],
|
||||
'determina_379887_2025' => [
|
||||
'key' => 'determina_379887_2025',
|
||||
'short' => 'Determina ACN 379887/2025 (vigente)',
|
||||
'citation' => 'Determinazione ACN n. 379887/2025, efficace dal 31 dicembre 2025',
|
||||
'full' => 'Determinazione del Direttore generale ACN n. 379887/2025 (adozione 19 dicembre 2025 [da confermare sul testo ufficiale], efficacia dal 31 dicembre 2025) che SOSTITUISCE la n. 333017/2025: disciplina il Portale ACN e i Servizi NIS per il ciclo di registrazione 2026 (registrazione, aggiornamento dei dati entro 10 giorni dalla presentazione, designazioni incluso il referente CSIRT). Fonte VIGENTE per piattaforma/registrazione/designazioni.',
|
||||
'file' => null,
|
||||
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
||||
'url' => 'https://www.acn.gov.it/portale/w/nis-avviata-la-seconda-fase',
|
||||
],
|
||||
'ambiti_nis2' => [
|
||||
'key' => 'ambiti_nis2',
|
||||
'short' => 'Ambiti NIS2 (Allegati I/II)',
|
||||
'citation' => 'Allegati I e II - Settori NIS2',
|
||||
'full' => 'Ambiti di applicazione NIS2 - Allegato I (Settori ad alta criticita: energia, trasporti, bancario, infrastrutture dei mercati finanziari, sanitario, acqua potabile, acque reflue, infrastrutture digitali, gestione servizi TIC B2B, PA, spazio) e Allegato II (Altri settori critici).',
|
||||
'file' => 'docs/nis2/AmbitiNIS2_ITA.pdf',
|
||||
'authority' => 'ACN / Allegati al D.Lgs. 138/2024',
|
||||
'url' => 'https://www.acn.gov.it/',
|
||||
],
|
||||
'acn_specifiche_base_2025' => [
|
||||
'key' => 'acn_specifiche_base_2025',
|
||||
'short' => 'Specifiche di base ACN (Framework Nazionale 2025)',
|
||||
'citation' => 'ACN, Misure di sicurezza di base NIS - Allegati 1 e 2 alla Determinazione n. 164179/2025',
|
||||
'full' => 'Misure di sicurezza di base (Determinazione ACN 164179/2025) articolate secondo il Framework Nazionale per la Cybersecurity e la Data Protection (ed. 2025): funzioni Governance (GV), Identificazione (ID), Protezione (PR), Rilevazione (DE), Risposta (RS), Ripristino (RC), poi categorie, sottocategorie e requisiti. Soggetti importanti: 37 misure / 92 requisiti (Allegato 1). Soggetti essenziali: 43 misure / 116 requisiti (Allegato 2), codifica identica con requisiti aggiuntivi. Esempi: GV.RR-04 (cybersicurezza nelle risorse umane, adeguatezza al ruolo), GV.PO-01 (policy di gestione del rischio). Adozione: 18 mesi dalla notifica di inserimento nell elenco NIS. NOTA DI PROVENIENZA: il prodotto adotta 92 requisiti per i soggetti importanti come da file del referente compliance (Simon); la pagina ufficiale ACN riporta 87 - divergenza nota e accettata, prevale il dato del referente; da riconciliare sul testo ufficiale.',
|
||||
'file' => 'docs/nis2/Determina164179_apr2025.pdf',
|
||||
'authority' => 'Agenzia per la Cybersicurezza Nazionale (ACN)',
|
||||
'url' => 'https://www.acn.gov.it/portale/nis/modalita-specifiche-base',
|
||||
],
|
||||
|
||||
// ── Standard ISO/IEC 27000 (best practice, NON fonti normative vincolanti) ──
|
||||
// Usate dal modulo Modello Organizzativo SGSI per il grounding di AI e help.
|
||||
'iso_27001_2022' => [
|
||||
'key' => 'iso_27001_2022',
|
||||
'short' => 'ISO/IEC 27001:2022',
|
||||
'citation' => 'ISO/IEC 27001:2022',
|
||||
'full' => 'ISO/IEC 27001:2022 - Information security, cybersecurity and privacy protection - Information security management systems - Requirements. Definisce i requisiti del SGSI (clausole 4-10) e l\'Annex A con 93 controlli su 4 temi. BEST PRACTICE internazionale, NON obbligo normativo.',
|
||||
'file' => null,
|
||||
'authority' => 'ISO/IEC (best practice, non vincolante)',
|
||||
'url' => 'https://www.iso.org/standard/27001',
|
||||
],
|
||||
'iso_27002_2022' => [
|
||||
'key' => 'iso_27002_2022',
|
||||
'short' => 'ISO/IEC 27002:2022',
|
||||
'citation' => 'ISO/IEC 27002:2022',
|
||||
'full' => 'ISO/IEC 27002:2022 - Information security controls. Guida implementativa dei 93 controlli dell\'Annex A di ISO 27001:2022. BEST PRACTICE, non vincolante.',
|
||||
'file' => null,
|
||||
'authority' => 'ISO/IEC (best practice, non vincolante)',
|
||||
'url' => 'https://www.iso.org/standard/75652.html',
|
||||
],
|
||||
'iso_27017_2015' => [
|
||||
'key' => 'iso_27017_2015',
|
||||
'short' => 'ISO/IEC 27017:2015',
|
||||
'citation' => 'ISO/IEC 27017:2015',
|
||||
'full' => 'ISO/IEC 27017:2015 - Code of practice for information security controls based on ISO/IEC 27002 for cloud services. Aggiunge guida cloud-specifica e 7 controlli CLD.* (numerati su ISO 27002:2013). Applicabile a clienti e fornitori cloud. BEST PRACTICE, non vincolante.',
|
||||
'file' => null,
|
||||
'authority' => 'ISO/IEC (best practice, non vincolante)',
|
||||
'url' => 'https://www.iso.org/standard/43757.html',
|
||||
],
|
||||
'iso_27018_2019' => [
|
||||
'key' => 'iso_27018_2019',
|
||||
'short' => 'ISO/IEC 27018:2019',
|
||||
'citation' => 'ISO/IEC 27018:2019',
|
||||
'full' => 'ISO/IEC 27018:2019 - Code of practice for protection of personally identifiable information (PII) in public clouds acting as PII processors. Estende ISO 27002 con controlli privacy (principi ISO 29100). Sinergico con il GDPR per gli obblighi del responsabile del trattamento. BEST PRACTICE, non vincolante.',
|
||||
'file' => null,
|
||||
'authority' => 'ISO/IEC (best practice, non vincolante)',
|
||||
'url' => 'https://www.iso.org/standard/76559.html',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - AI Assistant Controller (ARIA)
|
||||
*
|
||||
* Endpoint dell'assistente conversazionale AI usato dal FAB "ARIA" (common.js).
|
||||
* Usa AIService::askWithRag() -> RAG su Knowledge Base (incl. fonti normative
|
||||
* certe ingerite, scope SYSTEM) + grounding con citazioni.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
require_once __DIR__ . '/../services/AIService.php';
|
||||
require_once __DIR__ . '/../services/RateLimitService.php';
|
||||
|
||||
class AiController extends BaseController
|
||||
{
|
||||
/**
|
||||
* POST /api/ai/ask
|
||||
* Body: { question: string, history?: array }
|
||||
* Risposta: { answer, sources, rag_used }
|
||||
*/
|
||||
public function ask(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$question = trim((string) $this->getParam('question', ''));
|
||||
if ($question === '') {
|
||||
$this->jsonError('Domanda mancante', 422, 'QUESTION_REQUIRED');
|
||||
}
|
||||
if (mb_strlen($question) > 2000) {
|
||||
$this->jsonError('Domanda troppo lunga (max 2000 caratteri)', 422, 'QUESTION_TOO_LONG');
|
||||
}
|
||||
|
||||
// Rate limit per utente (riusa la soglia AI configurata)
|
||||
$userId = $this->getCurrentUserId();
|
||||
$rlKey = "ai_ask:{$userId}";
|
||||
if (defined('RATE_LIMIT_AI')) {
|
||||
RateLimitService::check($rlKey, RATE_LIMIT_AI);
|
||||
RateLimitService::increment($rlKey);
|
||||
}
|
||||
|
||||
$user = $this->getCurrentUser() ?? [];
|
||||
// Pagina corrente (facoltativa) inviata dal frontend, per dare ad ARIA
|
||||
// contesto sulla schermata da cui l'utente sta scrivendo (ticket #424).
|
||||
$page = mb_substr(trim((string) $this->getParam('page', '')), 0, 120);
|
||||
// pageId canonico + testo dell'help "?" della pagina (allineamento ARIA↔Help).
|
||||
$pageId = mb_substr(trim((string) $this->getParam('page_id', '')), 0, 40);
|
||||
$pageHelp = mb_substr(trim((string) $this->getParam('page_help', '')), 0, 2500);
|
||||
// Org attiva per lo snapshot dati di ARIA. ask() chiama requireAuth() ma NON
|
||||
// requireOrgAccess(), quindi getCurrentOrgId() resta null e org_data_ok sarebbe
|
||||
// sempre falso (ARIA cieca sui dati org). Risolviamo a mano: header
|
||||
// X-Organization-Id → param org_id → currentOrgId → org primaria dell'utente.
|
||||
// Lo spoofing è neutralizzato dal controllo membership qui sotto.
|
||||
$orgId = (int) ($_SERVER['HTTP_X_ORGANIZATION_ID'] ?? $this->getParam('org_id') ?? 0);
|
||||
if ($orgId <= 0) $orgId = (int) ($this->getCurrentOrgId() ?? 0);
|
||||
if ($orgId <= 0) {
|
||||
$primary = Database::fetchOne(
|
||||
'SELECT organization_id FROM user_organizations WHERE user_id = ? ORDER BY is_primary DESC, id ASC LIMIT 1',
|
||||
[$userId]
|
||||
);
|
||||
$orgId = (int) ($primary['organization_id'] ?? 0);
|
||||
}
|
||||
$userContext = [
|
||||
'user_id' => $userId,
|
||||
'organization_id' => $orgId ?: null,
|
||||
'consulting_firm_id' => $user['consulting_firm_id'] ?? null,
|
||||
'page' => $page,
|
||||
'page_id' => $pageId,
|
||||
'page_help' => $pageHelp,
|
||||
];
|
||||
|
||||
// Membership verificata → ARIA può iniettare lo snapshot DATI dell'org
|
||||
// (anti-spoof X-Organization-Id; super_admin bypassa, come da modello ruoli).
|
||||
$orgId = (int) ($userContext['organization_id'] ?? 0);
|
||||
$userContext['org_data_ok'] = false;
|
||||
if ($orgId > 0) {
|
||||
if (($user['role'] ?? '') === 'super_admin') {
|
||||
$userContext['org_data_ok'] = true;
|
||||
} else {
|
||||
$member = Database::fetchOne(
|
||||
'SELECT 1 FROM user_organizations WHERE user_id = ? AND organization_id = ?',
|
||||
[$userId, $orgId]
|
||||
);
|
||||
$userContext['org_data_ok'] = (bool) $member;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$aiService = new AIService();
|
||||
$result = $aiService->askWithRag($question, $userContext);
|
||||
|
||||
// Audit best-effort (non blocca la risposta)
|
||||
try {
|
||||
$aiService->logInteraction(
|
||||
(int) ($userContext['organization_id'] ?? 0),
|
||||
(int) ($userId ?? 0),
|
||||
'qa',
|
||||
mb_substr($question, 0, 200),
|
||||
mb_substr((string) ($result['answer'] ?? ''), 0, 500),
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[AiController::ask] logInteraction failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->jsonSuccess([
|
||||
'answer' => $result['answer'] ?? '',
|
||||
'sources' => $result['sources'] ?? [],
|
||||
'rag_used' => $result['rag_used'] ?? false,
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[AiController::ask] ' . $e->getMessage());
|
||||
$this->jsonError('Assistente AI non disponibile in questo momento', 503, 'AI_UNAVAILABLE');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/ai/tts — Voce naturale di ARIA.
|
||||
* Proxy same-origin verso nexus-voice-ms (ElevenLabs): il voice-ms non espone CORS
|
||||
* per l'origin NIS2 → il browser non può chiamarlo diretto. Cache MP3 per hash del
|
||||
* testo (stesse frasi → niente costo ripetuto). Stream audio/mpeg.
|
||||
*/
|
||||
public function tts(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
$text = trim((string) $this->getParam('text', ''));
|
||||
if ($text === '') { $this->jsonError('Testo mancante', 422, 'TTS_EMPTY'); }
|
||||
if (mb_strlen($text) > 800) { $text = mb_substr($text, 0, 800); }
|
||||
|
||||
$lang = substr(strtolower(preg_replace('/[^a-z]/', '', (string) $this->getParam('lang', 'it')) ?: 'it'), 0, 5) ?: 'it';
|
||||
$voice = (string) $this->getParam('voice_id', defined('TTS_VOICE_ID') ? TTS_VOICE_ID : 'EXAVITQu4vr4xnSDxMaL');
|
||||
if (!preg_match('/^[A-Za-z0-9]{8,40}$/', $voice)) {
|
||||
$voice = defined('TTS_VOICE_ID') ? TTS_VOICE_ID : 'EXAVITQu4vr4xnSDxMaL';
|
||||
}
|
||||
|
||||
$cacheDir = sys_get_temp_dir() . '/nis2-tts-cache';
|
||||
$cacheFile = $cacheDir . '/' . hash('sha256', $voice . '|' . $lang . '|' . $text) . '.mp3';
|
||||
if (is_file($cacheFile) && filesize($cacheFile) > 256) {
|
||||
$this->streamMp3((string) file_get_contents($cacheFile), 'HIT');
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = json_encode([
|
||||
'text' => $text,
|
||||
'lang' => $lang,
|
||||
'voice_id' => $voice,
|
||||
'model' => 'eleven_turbo_v2_5',
|
||||
'stability' => 0.5,
|
||||
'similarity_boost' => 0.75,
|
||||
'style' => 0.0,
|
||||
'output_format' => 'mp3_44100_128',
|
||||
]);
|
||||
|
||||
$url = defined('VOICE_MS_URL') ? VOICE_MS_URL : 'http://172.21.0.1:4215/tts/speak';
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 6,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
]);
|
||||
$audio = curl_exec($ch);
|
||||
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$ctype = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($code !== 200 || !$audio || strlen($audio) < 256 || stripos($ctype, 'audio') === false) {
|
||||
$this->jsonError('Sintesi vocale non disponibile', 502, 'TTS_UPSTREAM');
|
||||
}
|
||||
|
||||
if (!is_dir($cacheDir)) { @mkdir($cacheDir, 0775, true); }
|
||||
@file_put_contents($cacheFile . '.tmp', $audio);
|
||||
@rename($cacheFile . '.tmp', $cacheFile);
|
||||
|
||||
$this->streamMp3($audio, 'MISS');
|
||||
}
|
||||
|
||||
private function streamMp3(string $audio, string $cache): void
|
||||
{
|
||||
header('Content-Type: audio/mpeg');
|
||||
header('Content-Length: ' . strlen($audio));
|
||||
header('Cache-Control: private, max-age=86400');
|
||||
header('X-TTS-Cache: ' . $cache);
|
||||
echo $audio;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
require_once __DIR__ . '/../services/AssetScoringService.php';
|
||||
|
||||
class AssetController extends BaseController
|
||||
{
|
||||
@@ -29,12 +30,19 @@ class AssetController extends BaseController
|
||||
$where .= ' AND status = ?';
|
||||
$params[] = $this->getParam('status');
|
||||
}
|
||||
if ($this->hasParam('nis2_relevant')) {
|
||||
$where .= ' AND is_nis2_relevant = ?';
|
||||
$params[] = $this->getParam('nis2_relevant') ? 1 : 0;
|
||||
}
|
||||
|
||||
$total = Database::count('assets', $where, $params);
|
||||
$assets = Database::fetchAll(
|
||||
"SELECT a.*, u.full_name as owner_name
|
||||
"SELECT a.*, u.full_name as owner_name,
|
||||
sc.label AS subclass_label, v.label AS voce_label
|
||||
FROM assets a
|
||||
LEFT JOIN users u ON u.id = a.owner_user_id
|
||||
LEFT JOIN inventory_subclassi sc ON sc.id = a.subclass_id
|
||||
LEFT JOIN cfg_inventory_voci v ON v.voce_code = a.voce_code
|
||||
WHERE a.{$where}
|
||||
ORDER BY a.criticality DESC, a.name
|
||||
LIMIT {$pagination['per_page']} OFFSET {$pagination['offset']}",
|
||||
@@ -47,12 +55,28 @@ class AssetController extends BaseController
|
||||
public function create(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
$this->validateRequired(['name', 'asset_type']);
|
||||
$this->validateRequired(['name']);
|
||||
|
||||
$voce = $this->getParam('voce_code');
|
||||
if (!$voce) {
|
||||
// retro-compatibilità: deriva la voce dal vecchio asset_type (UI non ancora aggiornata / import)
|
||||
$at = (string) $this->getParam('asset_type');
|
||||
if (in_array($at, ['software', 'service', 'data'], true)) { $voce = 'ID.AM-02'; }
|
||||
elseif (in_array($at, ['hardware', 'network', 'facility'], true)) { $voce = 'ID.AM-01'; }
|
||||
}
|
||||
if (!in_array($voce, ['ID.AM-01', 'ID.AM-02'], true)) {
|
||||
$this->jsonError('Voce inventario obbligatoria (ID.AM-01 o ID.AM-02)', 422, 'INVALID_VOCE');
|
||||
}
|
||||
$subclassId = $this->validateSubclass($this->getParam('subclass_id'), $voce);
|
||||
// asset_type resta come campo legacy (viste secondarie): usa quello passato o deriva dalla voce
|
||||
$assetType = $this->getParam('asset_type') ?: ($voce === 'ID.AM-01' ? 'hardware' : 'service');
|
||||
|
||||
$assetId = Database::insert('assets', [
|
||||
'organization_id' => $this->getCurrentOrgId(),
|
||||
'name' => trim($this->getParam('name')),
|
||||
'asset_type' => $this->getParam('asset_type'),
|
||||
'asset_type' => $assetType,
|
||||
'voce_code' => $voce,
|
||||
'subclass_id' => $subclassId,
|
||||
'category' => $this->getParam('category'),
|
||||
'description' => $this->getParam('description'),
|
||||
'criticality' => $this->getParam('criticality', 'medium'),
|
||||
@@ -71,6 +95,116 @@ class AssetController extends BaseController
|
||||
$this->jsonSuccess(['id' => $assetId], 'Asset registrato', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/assets/import (JWT, org_admin/compliance_manager)
|
||||
* Import bulk asset da CMDB/cloud/CSV con scoring automatico GV.OC-04.
|
||||
* Body: { "source":"cmdb|aws|azure|csv|manual", "assets":[ {...}, ... ] }
|
||||
*/
|
||||
public function import(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
$body = $this->getJsonBody();
|
||||
$source = strtolower((string) ($body['source'] ?? 'csv'));
|
||||
$items = $body['assets'] ?? null;
|
||||
if (!is_array($items) || !$items) {
|
||||
$this->jsonError('Campo "assets" (array) obbligatorio', 422, 'VALIDATION');
|
||||
}
|
||||
$result = self::bulkUpsert($this->getCurrentOrgId(), $items, $source, $this->getCurrentUserId());
|
||||
$this->logAudit('assets_imported', 'asset', null, [
|
||||
'source' => $source, 'imported' => $result['imported'], 'updated' => $result['updated'],
|
||||
]);
|
||||
$this->jsonSuccess($result, 'Import completato', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upsert bulk + scoring GV.OC-04. Condiviso fra import UI (JWT) e
|
||||
* ingestion connettori (API key, ServicesController). Riceve orgId esplicito.
|
||||
*
|
||||
* @return array{imported:int,updated:int,skipped:int,relevant:int,total:int,results:array}
|
||||
*/
|
||||
public static function bulkUpsert(int $orgId, array $items, string $source, ?int $userId): array
|
||||
{
|
||||
$validType = ['hardware', 'software', 'network', 'data', 'service', 'personnel', 'facility'];
|
||||
$imported = 0; $updated = 0; $skipped = 0; $relevant = 0; $results = [];
|
||||
|
||||
if (count($items) > 1000) {
|
||||
$items = array_slice($items, 0, 1000);
|
||||
}
|
||||
|
||||
foreach ($items as $i => $a) {
|
||||
if (!is_array($a)) { $skipped++; $results[] = ['index' => $i, 'ok' => false, 'error' => 'not_an_object']; continue; }
|
||||
$name = trim((string) ($a['name'] ?? ''));
|
||||
if ($name === '') { $skipped++; $results[] = ['index' => $i, 'ok' => false, 'error' => 'name mancante']; continue; }
|
||||
|
||||
$type = strtolower((string) ($a['asset_type'] ?? 'service'));
|
||||
if (!in_array($type, $validType, true)) $type = 'service';
|
||||
// C4: voce (2 valori) derivata dal tipo legacy per gli asset importati
|
||||
$voce = in_array($type, ['hardware', 'network', 'facility'], true) ? 'ID.AM-01' : 'ID.AM-02';
|
||||
|
||||
// Scoring automatico GV.OC-04 da euristica sui campi CMDB
|
||||
$criteria = AssetScoringService::inferCriteria($a);
|
||||
$sc = AssetScoringService::calculate($criteria);
|
||||
|
||||
$extRef = isset($a['external_ref']) ? substr(trim((string) $a['external_ref']), 0, 190) : null;
|
||||
|
||||
$row = [
|
||||
'organization_id' => $orgId,
|
||||
'name' => $name,
|
||||
'asset_type' => $type,
|
||||
'voce_code' => $voce,
|
||||
'category' => $a['category'] ?? null,
|
||||
'description' => $a['description'] ?? null,
|
||||
'criticality' => $sc['criticality'],
|
||||
'location' => $a['location'] ?? null,
|
||||
'ip_address' => $a['ip_address'] ?? null,
|
||||
'vendor' => $a['vendor'] ?? null,
|
||||
'discovery_source' => substr($source, 0, 40),
|
||||
'external_ref' => $extRef,
|
||||
'relevance_score' => $sc['score'],
|
||||
'relevance_criteria' => json_encode($criteria, JSON_UNESCAPED_UNICODE),
|
||||
'relevance_class' => $sc['class'],
|
||||
'is_nis2_relevant' => $sc['is_relevant'] ? 1 : 0,
|
||||
'relevance_assessed_at' => date('Y-m-d H:i:s'),
|
||||
'relevance_assessed_by' => $userId,
|
||||
];
|
||||
// Dipendenze scoperte dal connettore → popolano la Mappa Dipendenze.
|
||||
if (isset($a['dependencies']) && is_array($a['dependencies'])) {
|
||||
$row['dependencies'] = json_encode(array_values($a['dependencies']), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
try {
|
||||
$existing = $extRef !== null
|
||||
? Database::fetchOne('SELECT id FROM assets WHERE organization_id = ? AND external_ref = ?', [$orgId, $extRef])
|
||||
: null;
|
||||
if ($existing) {
|
||||
$sets = []; $vals = [];
|
||||
foreach ($row as $k => $v) { if ($k === 'organization_id') continue; $sets[] = "$k = ?"; $vals[] = $v; }
|
||||
$vals[] = $existing['id'];
|
||||
Database::query('UPDATE assets SET ' . implode(', ', $sets) . ' WHERE id = ?', $vals);
|
||||
$assetId = (int) $existing['id']; $updated++;
|
||||
} else {
|
||||
$assetId = Database::insert('assets', $row); $imported++;
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$skipped++; $results[] = ['index' => $i, 'ok' => false, 'error' => 'db_error'];
|
||||
error_log('[ASSET_IMPORT] ' . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($sc['is_relevant']) $relevant++;
|
||||
$results[] = [
|
||||
'index' => $i, 'ok' => true, 'id' => $assetId, 'name' => $name,
|
||||
'relevance_score' => $sc['score'], 'relevance_class' => $sc['class'],
|
||||
'nis2_relevant' => $sc['is_relevant'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'imported' => $imported, 'updated' => $updated, 'skipped' => $skipped,
|
||||
'relevant' => $relevant, 'total' => count($items), 'results' => $results,
|
||||
];
|
||||
}
|
||||
|
||||
public function get(int $id): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
@@ -108,6 +242,21 @@ class AssetController extends BaseController
|
||||
$updates['dependencies'] = json_encode($this->getParam('dependencies'));
|
||||
}
|
||||
|
||||
// Voce/sottoclasse (Epic C / C4) — con validazione (no loop generico)
|
||||
if ($this->hasParam('voce_code')) {
|
||||
$voce = $this->getParam('voce_code');
|
||||
if (!in_array($voce, ['ID.AM-01', 'ID.AM-02'], true)) {
|
||||
$this->jsonError('Voce inventario non valida', 422, 'INVALID_VOCE');
|
||||
}
|
||||
$updates['voce_code'] = $voce;
|
||||
if ($this->hasParam('subclass_id')) {
|
||||
$updates['subclass_id'] = $this->validateSubclass($this->getParam('subclass_id'), $voce);
|
||||
}
|
||||
} elseif ($this->hasParam('subclass_id')) {
|
||||
$cur = Database::fetchOne('SELECT voce_code FROM assets WHERE id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
|
||||
$updates['subclass_id'] = $this->validateSubclass($this->getParam('subclass_id'), $cur['voce_code'] ?? '');
|
||||
}
|
||||
|
||||
if (!empty($updates)) {
|
||||
Database::update('assets', $updates, 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
|
||||
$this->logAudit('asset_updated', 'asset', $id, $updates);
|
||||
@@ -116,6 +265,138 @@ class AssetController extends BaseController
|
||||
$this->jsonSuccess($updates, 'Asset aggiornato');
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida una sottoclasse per la voce indicata: deve esistere ed essere o di
|
||||
* sistema (organization_id NULL) o dell'org corrente, e appartenere alla voce.
|
||||
* Ritorna l'id (int) oppure null se non fornita.
|
||||
*/
|
||||
private function validateSubclass($id, string $voce): ?int
|
||||
{
|
||||
$id = (int) $id;
|
||||
if ($id <= 0) { return null; }
|
||||
$row = Database::fetchOne(
|
||||
'SELECT id FROM inventory_subclassi
|
||||
WHERE id = ? AND voce_code = ? AND (organization_id = ? OR organization_id IS NULL)',
|
||||
[$id, $voce, $this->getCurrentOrgId()]
|
||||
);
|
||||
if (!$row) { $this->jsonError('Sottoclasse non valida per la voce selezionata', 422, 'INVALID_SUBCLASS'); }
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/assets/subclasses — le 2 voci canoniche + le sottoclassi visibili
|
||||
* (default di sistema + quelle dell'org), per i selettori dell'inventario.
|
||||
*/
|
||||
public function subclasses(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$voci = Database::fetchAll('SELECT voce_code, label, descr, ord FROM cfg_inventory_voci ORDER BY ord');
|
||||
$subs = Database::fetchAll(
|
||||
'SELECT id, voce_code, label, (organization_id IS NULL) AS is_default, ord
|
||||
FROM inventory_subclassi
|
||||
WHERE organization_id IS NULL OR organization_id = ?
|
||||
ORDER BY voce_code, ord, label',
|
||||
[$orgId]
|
||||
);
|
||||
$byVoce = [];
|
||||
foreach ($subs as $s) {
|
||||
$byVoce[$s['voce_code']][] = [
|
||||
'id' => (int) $s['id'], 'label' => $s['label'], 'is_default' => ((int) $s['is_default'] === 1),
|
||||
];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($voci as $v) {
|
||||
$out[] = [
|
||||
'voce_code' => $v['voce_code'], 'label' => $v['label'], 'descr' => $v['descr'],
|
||||
'subclassi' => $byVoce[$v['voce_code']] ?? [],
|
||||
];
|
||||
}
|
||||
$this->jsonSuccess(['voci' => $out]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/assets/subclasses { voce_code*, label* }
|
||||
* Aggiunge una sottoclasse ORGANIZZATIVA (org-scoped). Le due voci principali
|
||||
* NON sono modificabili dall'utente (restano due e solo due).
|
||||
*/
|
||||
public function addSubclass(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
$body = $this->getJsonBody();
|
||||
$voce = $body['voce_code'] ?? '';
|
||||
$label = trim((string) ($body['label'] ?? ''));
|
||||
if (!in_array($voce, ['ID.AM-01', 'ID.AM-02'], true)) {
|
||||
$this->jsonError('Voce inventario non valida', 422, 'INVALID_VOCE');
|
||||
}
|
||||
if ($label === '' || mb_strlen($label) > 120) {
|
||||
$this->jsonError('Etichetta sottoclasse obbligatoria (max 120 caratteri)', 422, 'INVALID_LABEL');
|
||||
}
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$dup = Database::fetchOne(
|
||||
'SELECT id FROM inventory_subclassi WHERE voce_code = ? AND label = ? AND (organization_id = ? OR organization_id IS NULL)',
|
||||
[$voce, $label, $orgId]
|
||||
);
|
||||
if ($dup) { $this->jsonError('Sottoclasse già esistente', 409, 'DUPLICATE'); }
|
||||
$id = Database::insert('inventory_subclassi', [
|
||||
'organization_id' => $orgId, 'voce_code' => $voce, 'label' => $label, 'ord' => 99,
|
||||
]);
|
||||
$this->jsonSuccess(['id' => $id, 'voce_code' => $voce, 'label' => $label], 'Sottoclasse aggiunta', 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/assets/subclasses/{id} { label* }
|
||||
* Rinomina una sottoclasse ORGANIZZATIVA dell'org corrente. Le sottoclassi di
|
||||
* sistema (organization_id NULL) NON sono modificabili dall'utente.
|
||||
*/
|
||||
public function updateSubclass(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
$body = $this->getJsonBody();
|
||||
$label = trim((string) ($body['label'] ?? ''));
|
||||
if ($label === '' || mb_strlen($label) > 120) {
|
||||
$this->jsonError('Etichetta sottoclasse obbligatoria (max 120 caratteri)', 422, 'INVALID_LABEL');
|
||||
}
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$row = Database::fetchOne(
|
||||
'SELECT id, voce_code FROM inventory_subclassi WHERE id = ? AND organization_id = ?',
|
||||
[$id, $orgId]
|
||||
);
|
||||
if (!$row) {
|
||||
$this->jsonError('Sottoclasse non trovata o non modificabile (le voci di sistema non si modificano)', 404, 'SUBCLASS_NOT_FOUND');
|
||||
}
|
||||
$dup = Database::fetchOne(
|
||||
'SELECT id FROM inventory_subclassi WHERE voce_code = ? AND label = ? AND (organization_id = ? OR organization_id IS NULL) AND id <> ?',
|
||||
[$row['voce_code'], $label, $orgId, $id]
|
||||
);
|
||||
if ($dup) { $this->jsonError('Sottoclasse già esistente', 409, 'DUPLICATE'); }
|
||||
Database::update('inventory_subclassi', ['label' => $label], 'id = ? AND organization_id = ?', [$id, $orgId]);
|
||||
$this->jsonSuccess(['id' => $id, 'label' => $label], 'Sottoclasse aggiornata');
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/assets/subclasses/{id}
|
||||
* Rimuove una sottoclasse ORGANIZZATIVA dell'org corrente. Gli asset che la
|
||||
* usano restano senza sottoclasse (FK ON DELETE SET NULL su assets.subclass_id).
|
||||
* Le sottoclassi di sistema (organization_id NULL) NON sono eliminabili.
|
||||
*/
|
||||
public function deleteSubclass(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$row = Database::fetchOne(
|
||||
'SELECT id FROM inventory_subclassi WHERE id = ? AND organization_id = ?',
|
||||
[$id, $orgId]
|
||||
);
|
||||
if (!$row) {
|
||||
$this->jsonError('Sottoclasse non trovata o non eliminabile (le voci di sistema non si rimuovono)', 404, 'SUBCLASS_NOT_FOUND');
|
||||
}
|
||||
$used = Database::fetchOne('SELECT COUNT(*) AS c FROM assets WHERE subclass_id = ?', [$id]);
|
||||
$usedCount = (int) ($used['c'] ?? 0);
|
||||
Database::delete('inventory_subclassi', 'id = ? AND organization_id = ?', [$id, $orgId]);
|
||||
$this->jsonSuccess(['id' => $id, 'unlinked_assets' => $usedCount], 'Sottoclasse rimossa');
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin']);
|
||||
@@ -157,4 +438,116 @@ class AssetController extends BaseController
|
||||
|
||||
$this->jsonSuccess(['nodes' => $nodes, 'edges' => $edges]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/assets/scoringGrid
|
||||
* Ritorna la griglia ufficiale di valutazione rilevanza NIS2 (GV.OC-04)
|
||||
* per costruire la UI di scoring lato client.
|
||||
*/
|
||||
public function scoringGrid(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$this->jsonSuccess([
|
||||
'grid' => AssetScoringService::GRID,
|
||||
'threshold' => AssetScoringService::RELEVANCE_THRESHOLD,
|
||||
'classes' => [
|
||||
['key' => 'critico', 'min' => 80, 'max' => 100, 'label' => 'Critico - Priorita Massima'],
|
||||
['key' => 'alto', 'min' => 60, 'max' => 79, 'label' => 'Alto - Priorita Alta'],
|
||||
['key' => 'medio', 'min' => 40, 'max' => 59, 'label' => 'Medio - Rilevante'],
|
||||
['key' => 'basso', 'min' => 20, 'max' => 39, 'label' => 'Basso - Monitoraggio'],
|
||||
['key' => 'trascurabile', 'min' => 0, 'max' => 19, 'label' => 'Trascurabile'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/assets/{id}/score
|
||||
* Calcola e salva la rilevanza NIS2 dell'asset a partire dalle selezioni
|
||||
* sui 6 criteri. Body: { criteria: { c1_operational_criticality: 'critical', ... } }
|
||||
*/
|
||||
public function score(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager']);
|
||||
|
||||
$asset = Database::fetchOne(
|
||||
'SELECT id FROM assets WHERE id = ? AND organization_id = ?',
|
||||
[$id, $this->getCurrentOrgId()]
|
||||
);
|
||||
if (!$asset) {
|
||||
$this->jsonError('Asset non trovato', 404, 'ASSET_NOT_FOUND');
|
||||
}
|
||||
|
||||
$criteria = $this->getParam('criteria');
|
||||
if (!is_array($criteria)) {
|
||||
$this->jsonError('Campo "criteria" mancante o non valido', 422, 'INVALID_CRITERIA');
|
||||
}
|
||||
|
||||
try {
|
||||
$result = AssetScoringService::calculate($criteria);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$this->jsonError($e->getMessage(), 422, 'INVALID_CRITERIA');
|
||||
return;
|
||||
}
|
||||
|
||||
Database::update('assets', [
|
||||
'relevance_score' => $result['score'],
|
||||
'relevance_criteria' => json_encode($criteria, JSON_UNESCAPED_UNICODE),
|
||||
'relevance_class' => $result['class'],
|
||||
'is_nis2_relevant' => $result['is_relevant'] ? 1 : 0,
|
||||
'criticality' => $result['criticality'],
|
||||
'relevance_assessed_at' => date('Y-m-d H:i:s'),
|
||||
'relevance_assessed_by' => $this->getCurrentUserId(),
|
||||
], 'id = ? AND organization_id = ?', [$id, $this->getCurrentOrgId()]);
|
||||
|
||||
$this->logAudit('asset_scored', 'asset', $id, [
|
||||
'score' => $result['score'],
|
||||
'class' => $result['class'],
|
||||
]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'score' => $result['score'],
|
||||
'class' => $result['class'],
|
||||
'is_nis2_relevant' => $result['is_relevant'],
|
||||
'breakdown' => $result['breakdown'],
|
||||
'required_measures'=> AssetScoringService::requiredMeasures($result['class']),
|
||||
], 'Rilevanza NIS2 calcolata');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/assets/relevantSystems
|
||||
* Elenco dei sistemi classificati rilevanti NIS2 (score >= 40), ordinati
|
||||
* per punteggio. Alimenta il registro formale "Sistemi Rilevanti" (GV.OC-04).
|
||||
*/
|
||||
public function relevantSystems(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
"SELECT a.id, a.name, a.asset_type, a.category, a.ip_address, a.location,
|
||||
a.relevance_score, a.relevance_class, a.relevance_criteria,
|
||||
a.relevance_assessed_at, u.full_name AS owner_name
|
||||
FROM assets a
|
||||
LEFT JOIN users u ON u.id = a.owner_user_id
|
||||
WHERE a.organization_id = ? AND a.is_nis2_relevant = 1
|
||||
ORDER BY a.relevance_score DESC, a.name",
|
||||
[$this->getCurrentOrgId()]
|
||||
);
|
||||
|
||||
$stats = ['critico' => 0, 'alto' => 0, 'medio' => 0];
|
||||
foreach ($rows as &$r) {
|
||||
$r['relevance_criteria'] = json_decode($r['relevance_criteria'] ?? 'null', true);
|
||||
$r['required_measures'] = AssetScoringService::requiredMeasures($r['relevance_class'] ?? '');
|
||||
if (isset($stats[$r['relevance_class']])) {
|
||||
$stats[$r['relevance_class']]++;
|
||||
}
|
||||
}
|
||||
unset($r);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'systems' => $rows,
|
||||
'count' => count($rows),
|
||||
'by_class' => $stats,
|
||||
'threshold' => AssetScoringService::RELEVANCE_THRESHOLD,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,14 @@ class AuditController extends BaseController
|
||||
$this->jsonError('File troppo grande (max 10MB)', 400, 'FILE_TOO_LARGE');
|
||||
}
|
||||
|
||||
// Allowlist estensioni (anti stored-XSS same-origin: niente html/svg/js eseguibili).
|
||||
// Allineata a StakeholderPortalController::ALLOWED_EXT.
|
||||
$allowedExt = ['pdf','png','jpg','jpeg','gif','webp','txt','csv','xlsx','xls','docx','doc','pptx','ppt','odt','ods','zip'];
|
||||
$extCheck = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
if ($extCheck === '' || !in_array($extCheck, $allowedExt, true)) {
|
||||
$this->jsonError('Tipo di file non consentito. Formati ammessi: PDF, immagini, documenti Office, txt, csv, zip.', 422, 'BAD_FILE_TYPE');
|
||||
}
|
||||
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$uploadDir = UPLOAD_PATH . "/evidence/{$orgId}";
|
||||
|
||||
@@ -195,6 +203,83 @@ class AuditController extends BaseController
|
||||
$this->jsonSuccess($mapping);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/nistCsfMapping
|
||||
* Layer di mapping NIST CSF 2.0 (43 controlli) -> NIS2 Art.21 / D.Lgs.138/2024 -> modulo piattaforma.
|
||||
* Reference-only (nessuna persistenza): arricchisce l'assessment Art.21 con i codici controllo
|
||||
* NIST CSF 2.0 usati come standard de-facto. Fonte mapping: NIST CSF 2.0 + Direttiva (UE) 2022/2555.
|
||||
*/
|
||||
public function getNistCsfMapping(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
|
||||
// [code, function, nis2, module]
|
||||
$rows = [
|
||||
// GOVERN
|
||||
['GV.OC-04', 'Govern', '21.1', 'Asset - Sistemi rilevanti (GV.OC-04)'],
|
||||
['GV.RM-03', 'Govern', '21.2.a', 'Risk Management'],
|
||||
['GV.RR-02', 'Govern', '20', 'Organizzazione - Ruoli e responsabilita'],
|
||||
['GV.RR-04', 'Govern', '20', 'Organizzazione - Risorse cybersecurity'],
|
||||
['GV.PO-01', 'Govern', '21.2.a', 'Policy - Politica di sicurezza'],
|
||||
['GV.PO-02', 'Govern', '21.2.a', 'Policy - Revisione politiche'],
|
||||
['GV.SC-01', 'Govern', '21.2.d', 'Supply Chain - Strategia'],
|
||||
['GV.SC-02', 'Govern', '21.2.d', 'Supply Chain - Ruoli fornitori'],
|
||||
['GV.SC-04', 'Govern', '21.2.d', 'Supply Chain - Valutazione fornitori'],
|
||||
['GV.SC-05', 'Govern', '21.2.d', 'Supply Chain - Requisiti contrattuali'],
|
||||
['GV.SC-07', 'Govern', '21.2.d', 'Supply Chain - Monitoraggio rischio fornitori'],
|
||||
// IDENTIFY
|
||||
['ID.AM-01', 'Identify', '21.2.i', 'Asset - Inventario hardware'],
|
||||
['ID.AM-02', 'Identify', '21.2.i', 'Asset - Inventario software'],
|
||||
['ID.AM-03', 'Identify', '21.2.i', 'Asset - Diagrammi flussi/rete (essenziali)'],
|
||||
['ID.AM-04', 'Identify', '21.2.i', 'Asset - Catalogo servizi'],
|
||||
['ID.RA-01', 'Identify', '21.2.a', 'Risk Management - Vulnerabilita'],
|
||||
['ID.RA-05', 'Identify', '21.2.a', 'Risk Management - Valutazione rischio'],
|
||||
['ID.RA-06', 'Identify', '21.2.a', 'Risk Management - Trattamento rischio'],
|
||||
['ID.RA-08', 'Identify', '21.2.e', 'Risk Management - Gestione vulnerabilita/disclosure'],
|
||||
['ID.IM-01', 'Identify', '21.2.f', 'Audit - Miglioramento da valutazioni'],
|
||||
['ID.IM-04', 'Identify', '21.2.c', 'Incidenti - Piani BC/DR e test'],
|
||||
// PROTECT
|
||||
['PR.AA-01', 'Protect', '21.2.i', 'Asset/Access - Gestione identita'],
|
||||
['PR.AA-03', 'Protect', '21.2.i', 'Access - Autenticazione'],
|
||||
['PR.AA-05', 'Protect', '21.2.i', 'Access - Privilegi e accessi'],
|
||||
['PR.AA-06', 'Protect', '21.2.i', 'Access - Accesso fisico'],
|
||||
['PR.AT-01', 'Protect', '21.2.g', 'Training - Awareness'],
|
||||
['PR.AT-02', 'Protect', '21.2.g', 'Training - Ruoli privilegiati'],
|
||||
['PR.DS-01', 'Protect', '21.2.h', 'Policy - Protezione dati a riposo'],
|
||||
['PR.DS-02', 'Protect', '21.2.h', 'Policy - Protezione dati in transito'],
|
||||
['PR.DS-11', 'Protect', '21.2.c', 'Incidenti - Backup'],
|
||||
['PR.PS-01', 'Protect', '21.2.e', 'Policy - Configurazione sicura'],
|
||||
['PR.PS-02', 'Protect', '21.2.e', 'Asset - Gestione software'],
|
||||
['PR.PS-03', 'Protect', '21.2.e', 'Asset - Gestione hardware'],
|
||||
['PR.PS-04', 'Protect', '21.2.b', 'Audit - Log generation'],
|
||||
['PR.PS-06', 'Protect', '21.2.e', 'Policy - Secure development lifecycle'],
|
||||
['PR.IR-01', 'Protect', '21.2.i', 'Asset - Protezione reti'],
|
||||
['PR.IR-03', 'Protect', '21.2.c', 'Incidenti - Resilienza/ridondanza'],
|
||||
// DETECT
|
||||
['DE.CM-01', 'Detect', '21.2.b', 'Incidenti - Monitoraggio reti'],
|
||||
['DE.CM-09', 'Detect', '21.2.b', 'Incidenti - Monitoraggio asset/sistemi'],
|
||||
// RESPOND / RECOVER
|
||||
['RS.MA-01', 'Respond', '21.2.b / 23', 'Incidenti - Gestione incidenti'],
|
||||
['RS.CO-02', 'Respond', '23', 'Incidenti - Notifica CSIRT'],
|
||||
['RC.RP-01', 'Recover', '21.2.c', 'Incidenti - Piano di ripristino'],
|
||||
['RC.CO-03', 'Recover', '21.2.c', 'Incidenti - Post-Incident Review'],
|
||||
];
|
||||
|
||||
$mapping = array_map(fn($r) => [
|
||||
'csf_code' => $r[0],
|
||||
'function' => $r[1],
|
||||
'nis2_art' => $r[2],
|
||||
'module' => $r[3],
|
||||
], $rows);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'mapping' => $mapping,
|
||||
'count' => count($mapping),
|
||||
'functions' => ['Govern', 'Identify', 'Protect', 'Detect', 'Respond', 'Recover'],
|
||||
'source' => 'NIST Cybersecurity Framework 2.0 + Direttiva (UE) 2022/2555 (NIS2) Art.20-21-23 / D.Lgs. 138/2024',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/executive-report
|
||||
* Genera report esecutivo HTML (stampabile come PDF)
|
||||
@@ -211,6 +296,22 @@ class AuditController extends BaseController
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/relevantSystemsRegister
|
||||
* Registro formale "Sistemi Rilevanti NIS2" (GV.OC-04), HTML stampabile.
|
||||
*/
|
||||
public function relevantSystemsRegister(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'board_member', 'auditor']);
|
||||
|
||||
$reportService = new ReportService();
|
||||
$html = $reportService->generateRelevantSystemsRegister($this->getCurrentOrgId());
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo $html;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/export/{type}
|
||||
* Esporta dati in CSV
|
||||
@@ -238,4 +339,237 @@ class AuditController extends BaseController
|
||||
echo $csv;
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/chain-verify
|
||||
* Verifica l'integrità dell'hash chain per l'organizzazione corrente.
|
||||
* Risponde con: valid, total, hashed, coverage_pct, broken_at, last_hash
|
||||
*/
|
||||
public function chainVerify(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'auditor']);
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
$db = Database::getInstance();
|
||||
$result = AuditService::verifyChain($db, $orgId);
|
||||
|
||||
// Se la catena è rotta, registra la violazione
|
||||
if (!$result['valid'] && $result['broken_at'] !== null) {
|
||||
$performer = $this->currentUser['email'] ?? 'system';
|
||||
try {
|
||||
$ins = $db->prepare(
|
||||
"INSERT INTO audit_violations
|
||||
(organization_id, detected_by, broken_at_id, chain_length, notes)
|
||||
VALUES (:org, :by, :bid, :len, :notes)"
|
||||
);
|
||||
$ins->execute([
|
||||
'org' => $orgId,
|
||||
'by' => $performer,
|
||||
'bid' => $result['broken_at'],
|
||||
'len' => $result['total'],
|
||||
'notes' => 'Violazione rilevata tramite API chain-verify',
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[AuditController] chain violation log error: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->logAudit('audit.chain_broken', 'audit_logs', $result['broken_at'], [
|
||||
'total' => $result['total'],
|
||||
'coverage_pct' => $result['coverage_pct'],
|
||||
]);
|
||||
}
|
||||
|
||||
$this->jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/export-certified
|
||||
* Genera un export JSON certificato con hash SHA-256 dell'intero contenuto.
|
||||
* Adatto per ispezioni ACN, audit NIS2 Art.32, certificazione ISO 27001.
|
||||
*/
|
||||
public function exportCertified(): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'auditor']);
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$userId = $this->getCurrentUserId();
|
||||
$email = $this->currentUser['email'] ?? 'system';
|
||||
|
||||
$db = Database::getInstance();
|
||||
$result = AuditService::exportCertified(
|
||||
$db,
|
||||
$orgId,
|
||||
$userId,
|
||||
$email,
|
||||
$_GET['purpose'] ?? 'export_certificato',
|
||||
$_GET['from'] ?? null,
|
||||
$_GET['to'] ?? null
|
||||
);
|
||||
|
||||
$this->logAudit('audit.export_certified', 'audit_logs', null, [
|
||||
'records_count' => $result['records_count'],
|
||||
'chain_valid' => $result['chain_valid'],
|
||||
'export_hash' => $result['export_hash'],
|
||||
]);
|
||||
|
||||
header('Content-Disposition: attachment; filename="nis2_audit_certified_' . date('Y-m-d') . '.json"');
|
||||
$this->jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/controlsMonitoring
|
||||
* Continuous Control Monitoring (P1): stato/freschezza dei controlli
|
||||
* alimentato dalle evidenze automatiche. Versione JWT per la UI.
|
||||
*/
|
||||
public function controlsMonitoring(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
try {
|
||||
Database::query(
|
||||
"UPDATE compliance_controls cc
|
||||
LEFT JOIN (
|
||||
SELECT t.control_code, t.valid_until
|
||||
FROM control_evidence_auto t
|
||||
JOIN (SELECT control_code, MAX(collected_at) mx FROM control_evidence_auto
|
||||
WHERE organization_id = ? GROUP BY control_code) m
|
||||
ON m.control_code = t.control_code AND m.mx = t.collected_at
|
||||
WHERE t.organization_id = ?
|
||||
) last ON last.control_code = cc.control_code
|
||||
SET cc.monitoring_status = 'stale'
|
||||
WHERE cc.organization_id = ?
|
||||
AND cc.monitoring_status NOT IN ('not_monitored')
|
||||
AND last.valid_until IS NOT NULL AND last.valid_until < NOW()",
|
||||
[$orgId, $orgId, $orgId]
|
||||
);
|
||||
} catch (Throwable $e) {
|
||||
error_log('[CCM] ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT control_code, title, status, monitoring_status, last_checked_at, freshness_days, implementation_percentage
|
||||
FROM compliance_controls WHERE organization_id = ? ORDER BY control_code',
|
||||
[$orgId]
|
||||
);
|
||||
|
||||
$summary = ['healthy' => 0, 'warning' => 0, 'stale' => 0, 'failing' => 0, 'not_monitored' => 0];
|
||||
foreach ($rows as $r) {
|
||||
$ms = $r['monitoring_status'] ?? 'not_monitored';
|
||||
if (isset($summary[$ms])) $summary[$ms]++;
|
||||
}
|
||||
$monitored = count($rows) - $summary['not_monitored'];
|
||||
|
||||
$recent = [];
|
||||
try {
|
||||
$recent = Database::fetchAll(
|
||||
'SELECT control_code, source, source_system, status, summary, collected_at, valid_until
|
||||
FROM control_evidence_auto WHERE organization_id = ?
|
||||
ORDER BY collected_at DESC LIMIT 20',
|
||||
[$orgId]
|
||||
);
|
||||
} catch (Throwable $e) { /* ignore */ }
|
||||
|
||||
$this->jsonSuccess([
|
||||
'total_controls' => count($rows),
|
||||
'monitored' => $monitored,
|
||||
'coverage_percent' => count($rows) ? (int) round($monitored * 100 / count($rows)) : 0,
|
||||
'summary' => $summary,
|
||||
'controls' => $rows,
|
||||
'recent_evidence' => $recent,
|
||||
]);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// REQUISITI ACN (specifiche di base, Determina 164179/2025)
|
||||
// Gap Analysis a livello requisiti: 87 (importanti) / 116 (essenziali).
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/** Mappa entity_type org (essential/important/...) -> entity catalogo ACN. */
|
||||
private function acnEntityFor(int $orgId): string
|
||||
{
|
||||
$org = Database::fetchOne('SELECT entity_type FROM organizations WHERE id = ?', [$orgId]);
|
||||
// I soggetti essenziali hanno il set esteso (116). Tutti gli altri (importanti o
|
||||
// non classificati) usano il set base importanti (87).
|
||||
return ($org && ($org['entity_type'] ?? '') === 'essential') ? 'essenziale' : 'importante';
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/audit/acnRequirements
|
||||
* Requisiti ACN applicabili all'org (per entity_type) con stato + sommario per funzione.
|
||||
*/
|
||||
public function acnRequirements(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$entity = $this->acnEntityFor($orgId);
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
"SELECT r.id, r.function_name, r.subcategory, r.subcategory_text, r.req_index, r.requirement,
|
||||
COALESCE(s.status,'not_started') AS status, s.evidence_note, s.updated_at
|
||||
FROM acn_requirements r
|
||||
LEFT JOIN org_acn_requirement_status s
|
||||
ON s.requirement_id = r.id AND s.organization_id = ?
|
||||
WHERE r.entity = ?
|
||||
ORDER BY r.subcategory, r.req_index",
|
||||
[$orgId, $entity]
|
||||
);
|
||||
|
||||
$summary = ['not_started'=>0,'in_progress'=>0,'implemented'=>0,'not_applicable'=>0];
|
||||
$byFunc = [];
|
||||
foreach ($rows as $r) {
|
||||
$st = $r['status'];
|
||||
if (isset($summary[$st])) $summary[$st]++;
|
||||
$f = $r['function_name'];
|
||||
if (!isset($byFunc[$f])) $byFunc[$f] = ['total'=>0,'implemented'=>0];
|
||||
$byFunc[$f]['total']++;
|
||||
if ($st === 'implemented') $byFunc[$f]['implemented']++;
|
||||
}
|
||||
$total = count($rows);
|
||||
$applicable = $total - $summary['not_applicable'];
|
||||
$compliance = $applicable > 0 ? (int) round($summary['implemented'] * 100 / $applicable) : 0;
|
||||
|
||||
$this->jsonSuccess([
|
||||
'entity' => $entity,
|
||||
'total' => $total,
|
||||
'summary' => $summary,
|
||||
'by_function' => $byFunc,
|
||||
'compliance_percent'=> $compliance,
|
||||
'requirements' => $rows,
|
||||
'source' => 'Determina ACN 164179/2025, Allegati 1-2 (Framework Nazionale 2025)',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/audit/acnRequirements/{id}
|
||||
* Aggiorna lo stato di un requisito ACN per l'org. Body: { status, evidence_note? }
|
||||
*/
|
||||
public function updateAcnRequirement(int $id): void
|
||||
{
|
||||
$this->requireOrgRole(['org_admin', 'compliance_manager', 'auditor']);
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
$entity = $this->acnEntityFor($orgId);
|
||||
|
||||
// Il requisito deve appartenere al set applicabile all'org
|
||||
$req = Database::fetchOne('SELECT id FROM acn_requirements WHERE id = ? AND entity = ?', [$id, $entity]);
|
||||
if (!$req) {
|
||||
$this->jsonError('Requisito non trovato o non applicabile a questa organizzazione', 404, 'NOT_FOUND');
|
||||
}
|
||||
|
||||
$status = $this->getParam('status');
|
||||
$valid = ['not_started','in_progress','implemented','not_applicable'];
|
||||
if (!in_array($status, $valid, true)) {
|
||||
$this->jsonError('Stato non valido', 422, 'INVALID_STATUS');
|
||||
}
|
||||
$note = $this->getParam('evidence_note');
|
||||
|
||||
Database::query(
|
||||
'INSERT INTO org_acn_requirement_status (organization_id, requirement_id, status, evidence_note, updated_by)
|
||||
VALUES (?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE status=VALUES(status), evidence_note=VALUES(evidence_note),
|
||||
updated_by=VALUES(updated_by), updated_at=NOW()',
|
||||
[$orgId, $id, $status, $note, $this->getCurrentUserId()]
|
||||
);
|
||||
$this->logAudit('acn_requirement_updated', 'acn_requirement', $id, ['status' => $status]);
|
||||
$this->jsonSuccess(['id' => $id, 'status' => $status], 'Requisito aggiornato');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,38 @@
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
require_once APP_PATH . '/services/RateLimitService.php';
|
||||
require_once APP_PATH . '/services/SsoHelper.php';
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Restituisce l'IP reale del client, gestendo proxy/nginx.
|
||||
*/
|
||||
private function getClientIP(): string
|
||||
{
|
||||
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
// Fidati di X-Forwarded-For solo se la richiesta arriva da localhost (nginx proxy)
|
||||
if (in_array($remoteAddr, ['127.0.0.1', '::1', 'unknown'])
|
||||
&& !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$xForwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'];
|
||||
$ips = array_map('trim', explode(',', $xForwardedFor));
|
||||
$firstIp = filter_var($ips[0], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
|
||||
if ($firstIp !== false) {
|
||||
return $firstIp;
|
||||
}
|
||||
}
|
||||
return $remoteAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/register
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Rate limiting
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
$ip = $this->getClientIP();
|
||||
RateLimitService::check("register:{$ip}", RATE_LIMIT_AUTH_REGISTER);
|
||||
RateLimitService::increment("register:{$ip}");
|
||||
RateLimitService::increment("register:{$ip}"); // $ip defined above via getClientIP()
|
||||
|
||||
$this->validateRequired(['email', 'password', 'full_name']);
|
||||
|
||||
@@ -27,6 +47,19 @@ class AuthController extends BaseController
|
||||
$fullName = trim($this->getParam('full_name'));
|
||||
$phone = $this->getParam('phone');
|
||||
|
||||
// Supporta sia `role` diretto (nuovo register.html) che `user_type` legacy
|
||||
$validRoles = ['super_admin', 'org_admin', 'compliance_manager', 'board_member', 'auditor', 'employee', 'consultant'];
|
||||
$roleParam = trim($this->getParam('role', ''));
|
||||
$userType = $this->getParam('user_type', 'azienda');
|
||||
|
||||
if ($roleParam && in_array($roleParam, $validRoles, true) && $roleParam !== 'super_admin') {
|
||||
$role = $roleParam;
|
||||
} elseif ($userType === 'consultant') {
|
||||
$role = 'consultant';
|
||||
} else {
|
||||
$role = 'employee';
|
||||
}
|
||||
|
||||
// Validazione email
|
||||
if (!$this->validateEmail($email)) {
|
||||
$this->jsonError('Formato email non valido', 400, 'INVALID_EMAIL');
|
||||
@@ -54,24 +87,40 @@ class AuthController extends BaseController
|
||||
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
|
||||
'full_name' => $fullName,
|
||||
'phone' => $phone,
|
||||
'role' => 'employee',
|
||||
'role' => $role,
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
// Genera tokens
|
||||
$accessToken = $this->generateJWT($userId);
|
||||
$refreshToken = $this->generateRefreshToken($userId);
|
||||
// --- Sessione tracciata (jti) come nel login: requireAuth rifiuta i token
|
||||
// senza jti, quindi senza questo l'utente appena registrato verrebbe
|
||||
// sbattuto fuori al primo getMe/completeOnboarding (401 JWT_NO_JTI). ---
|
||||
$jti = bin2hex(random_bytes(16));
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
$ip = $this->getClientIP();
|
||||
Database::insert('active_sessions', [
|
||||
'id' => $jti,
|
||||
'user_id' => (int) $userId,
|
||||
'organization_id' => null,
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => substr($ua, 0, 512),
|
||||
'device_label' => $this->parseDeviceLabel($ua),
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + JWT_REFRESH_EXPIRES_IN),
|
||||
]);
|
||||
|
||||
// Genera tokens (con jti, come login)
|
||||
$accessToken = $this->generateJWT($userId, ['jti' => $jti]);
|
||||
$refreshToken = $this->generateRefreshToken($userId, $jti);
|
||||
|
||||
// Audit log
|
||||
$this->currentUser = ['id' => $userId];
|
||||
$this->logAudit('user_registered', 'user', $userId);
|
||||
$this->logAudit('user_registered', 'user', $userId, ['user_type' => $userType]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'user' => [
|
||||
'id' => $userId,
|
||||
'email' => $email,
|
||||
'full_name' => $fullName,
|
||||
'role' => 'employee',
|
||||
'role' => $role,
|
||||
],
|
||||
'access_token' => $accessToken,
|
||||
'refresh_token' => $refreshToken,
|
||||
@@ -85,7 +134,7 @@ class AuthController extends BaseController
|
||||
public function login(): void
|
||||
{
|
||||
// Rate limiting
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
$ip = $this->getClientIP();
|
||||
RateLimitService::check("login:{$ip}", RATE_LIMIT_AUTH_LOGIN);
|
||||
RateLimitService::increment("login:{$ip}");
|
||||
|
||||
@@ -100,8 +149,66 @@ class AuthController extends BaseController
|
||||
[$email]
|
||||
);
|
||||
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
$this->jsonError('Credenziali non valide', 401, 'INVALID_CREDENTIALS');
|
||||
// --- SSO Federation (Fase 1 / G02) ---
|
||||
// In SSO_MODE=local (default) tutto questo blocco è no-op: SsoHelper::login()
|
||||
// ritorna null e $ssoAuthorized resta false → cade nel fallback locale invariato.
|
||||
$ssoAuthorized = false;
|
||||
$sso = new SsoHelper();
|
||||
if (!$sso->isLocalOnly()) {
|
||||
$ssoResp = $sso->login($email, $password, 'nis2');
|
||||
if ($ssoResp === null) {
|
||||
// SSO unreachable
|
||||
if ($sso->isSsoOnly()) {
|
||||
$this->jsonError('SSO non disponibile, riprovare', 503, 'SSO_UNAVAILABLE');
|
||||
}
|
||||
// dual: fallback locale (procedi al password_verify sotto)
|
||||
} else {
|
||||
$httpStatus = (int) ($ssoResp['_httpStatus'] ?? 0);
|
||||
if ($httpStatus === 200) {
|
||||
$ssoIdentityId = (int) ($ssoResp['identity_id'] ?? $ssoResp['user']['sso_identity_id'] ?? 0);
|
||||
$ssoPasswordVersion = (int) ($ssoResp['password_version'] ?? 1);
|
||||
if (!$user) {
|
||||
// Utente SSO non ancora presente localmente: crealo
|
||||
$ssoUser = $ssoResp['user'] ?? [];
|
||||
$newId = Database::insert('users', [
|
||||
'email' => $email,
|
||||
'password_hash' => password_hash($password, PASSWORD_DEFAULT),
|
||||
'full_name' => $ssoUser['full_name'] ?? $email,
|
||||
'role' => 'employee',
|
||||
'sso_identity_id' => $ssoIdentityId,
|
||||
'password_version' => $ssoPasswordVersion,
|
||||
'is_active' => 1,
|
||||
]);
|
||||
$user = Database::fetchOne('SELECT * FROM users WHERE id = ?', [$newId]);
|
||||
} else {
|
||||
// Utente esistente: link/sync se necessario (lazy backfill — decisione 2)
|
||||
$updates = [];
|
||||
if ((int) ($user['sso_identity_id'] ?? 0) !== $ssoIdentityId) {
|
||||
$updates['sso_identity_id'] = $ssoIdentityId;
|
||||
}
|
||||
if ((int) ($user['password_version'] ?? 0) !== $ssoPasswordVersion) {
|
||||
$updates['password_hash'] = password_hash($password, PASSWORD_DEFAULT);
|
||||
$updates['password_version'] = $ssoPasswordVersion;
|
||||
}
|
||||
if ($updates) {
|
||||
Database::update('users', $updates, 'id = ?', [$user['id']]);
|
||||
$user = Database::fetchOne('SELECT * FROM users WHERE id = ?', [$user['id']]);
|
||||
}
|
||||
}
|
||||
$ssoAuthorized = true;
|
||||
} elseif ($httpStatus === 401 && $sso->isSsoOnly()) {
|
||||
$this->jsonError('Credenziali non valide', 401, 'INVALID_CREDENTIALS');
|
||||
} elseif ($sso->isSsoOnly()) {
|
||||
$this->jsonError('Errore SSO', 502, 'SSO_ERROR');
|
||||
}
|
||||
// dual + non-200: cade nel fallback locale
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ssoAuthorized) {
|
||||
if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
$this->jsonError('Credenziali non valide', 401, 'INVALID_CREDENTIALS');
|
||||
}
|
||||
}
|
||||
|
||||
// Aggiorna ultimo login
|
||||
@@ -109,9 +216,22 @@ class AuthController extends BaseController
|
||||
'last_login_at' => date('Y-m-d H:i:s'),
|
||||
], 'id = ?', [$user['id']]);
|
||||
|
||||
// Genera tokens
|
||||
$accessToken = $this->generateJWT((int) $user['id']);
|
||||
$refreshToken = $this->generateRefreshToken((int) $user['id']);
|
||||
// --- Multi-device session tracking (Fase 2 / G05-G06) ---
|
||||
$jti = bin2hex(random_bytes(16));
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
Database::insert('active_sessions', [
|
||||
'id' => $jti,
|
||||
'user_id' => (int) $user['id'],
|
||||
'organization_id'=> null,
|
||||
'ip_address' => $ip,
|
||||
'user_agent' => substr($ua, 0, 512),
|
||||
'device_label' => $this->parseDeviceLabel($ua),
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + JWT_REFRESH_EXPIRES_IN),
|
||||
]);
|
||||
|
||||
// Genera tokens (jti come claim JWT + FK su refresh_tokens.session_jti)
|
||||
$accessToken = $this->generateJWT((int) $user['id'], ['jti' => $jti]);
|
||||
$refreshToken = $this->generateRefreshToken((int) $user['id'], $jti);
|
||||
|
||||
// Carica organizzazioni
|
||||
$organizations = Database::fetchAll(
|
||||
@@ -147,8 +267,19 @@ class AuthController extends BaseController
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
// Invalida tutti i refresh token dell'utente
|
||||
Database::delete('refresh_tokens', 'user_id = ?', [$this->getCurrentUserId()]);
|
||||
// --- Revoca selettiva della sessione corrente (Fase 2 / G06) ---
|
||||
$jti = $this->currentUser['session_jti'] ?? null;
|
||||
if ($jti) {
|
||||
Database::query(
|
||||
"UPDATE active_sessions SET revoked_at = NOW(), revoked_reason = 'logout'
|
||||
WHERE id = ? AND revoked_at IS NULL",
|
||||
[$jti]
|
||||
);
|
||||
Database::delete('refresh_tokens', 'session_jti = ?', [$jti]);
|
||||
} else {
|
||||
// Fallback (non dovrebbe accadere — requireAuth ora pretende jti)
|
||||
Database::delete('refresh_tokens', 'user_id = ?', [$this->getCurrentUserId()]);
|
||||
}
|
||||
|
||||
$this->logAudit('user_logout', 'user', $this->getCurrentUserId());
|
||||
|
||||
@@ -165,24 +296,55 @@ class AuthController extends BaseController
|
||||
$refreshToken = $this->getParam('refresh_token');
|
||||
$hashedToken = hash('sha256', $refreshToken);
|
||||
|
||||
// Verifica refresh token
|
||||
$tokenRecord = Database::fetchOne(
|
||||
'SELECT * FROM refresh_tokens WHERE token = ? AND expires_at > NOW()',
|
||||
[$hashedToken]
|
||||
);
|
||||
// Transazione atomica per evitare race condition (double-spend del refresh token)
|
||||
Database::beginTransaction();
|
||||
try {
|
||||
// SELECT FOR UPDATE: blocca il record per tutta la transazione
|
||||
$tokenRecord = Database::fetchOne(
|
||||
'SELECT * FROM refresh_tokens WHERE token = ? AND expires_at > NOW() FOR UPDATE',
|
||||
[$hashedToken]
|
||||
);
|
||||
|
||||
if (!$tokenRecord) {
|
||||
$this->jsonError('Refresh token non valido o scaduto', 401, 'INVALID_REFRESH_TOKEN');
|
||||
if (!$tokenRecord) {
|
||||
Database::rollback();
|
||||
$this->jsonError('Refresh token non valido o scaduto', 401, 'INVALID_REFRESH_TOKEN');
|
||||
}
|
||||
|
||||
$userId = (int) $tokenRecord['user_id'];
|
||||
$jti = $tokenRecord['session_jti'] ?? null;
|
||||
|
||||
// Verifica che la sessione associata sia ancora valida (Fase 2 / G06)
|
||||
if ($jti) {
|
||||
$session = Database::fetchOne(
|
||||
'SELECT id FROM active_sessions
|
||||
WHERE id = ? AND revoked_at IS NULL AND expires_at > NOW()',
|
||||
[$jti]
|
||||
);
|
||||
if (!$session) {
|
||||
Database::rollback();
|
||||
$this->jsonError('Sessione revocata, login richiesto', 401, 'SESSION_REVOKED');
|
||||
}
|
||||
// Touch last_activity
|
||||
Database::update('active_sessions',
|
||||
['last_activity_at' => date('Y-m-d H:i:s')],
|
||||
'id = ?',
|
||||
[$jti]
|
||||
);
|
||||
}
|
||||
|
||||
// Elimina vecchio token atomicamente
|
||||
Database::delete('refresh_tokens', 'id = ?', [$tokenRecord['id']]);
|
||||
|
||||
// Genera nuovi tokens preservando il jti della sessione
|
||||
$accessToken = $this->generateJWT($userId, $jti ? ['jti' => $jti] : []);
|
||||
$newRefreshToken = $this->generateRefreshToken($userId, $jti);
|
||||
|
||||
Database::commit();
|
||||
} catch (Throwable $e) {
|
||||
Database::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// Elimina vecchio token
|
||||
Database::delete('refresh_tokens', 'id = ?', [$tokenRecord['id']]);
|
||||
|
||||
// Genera nuovi tokens
|
||||
$userId = (int) $tokenRecord['user_id'];
|
||||
$accessToken = $this->generateJWT($userId);
|
||||
$newRefreshToken = $this->generateRefreshToken($userId);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'access_token' => $accessToken,
|
||||
'refresh_token' => $newRefreshToken,
|
||||
@@ -277,15 +439,611 @@ class AuthController extends BaseController
|
||||
$this->jsonError(implode('. ', $errors), 400, 'WEAK_PASSWORD');
|
||||
}
|
||||
|
||||
// --- SSO change-password propagation (Fase 1 / G03) ---
|
||||
// Solo per utenti federati (sso_identity_id != NULL) e SSO_MODE != local.
|
||||
// Se SSO 200 → procediamo con cambio locale.
|
||||
// Se SSO error → blocchiamo: niente cambio locale, manteniamo consistency.
|
||||
// Se SSO unreachable in dual → log e procediamo localmente (sync verrà via cron).
|
||||
$sso = new SsoHelper();
|
||||
$isFederated = !empty($this->currentUser['sso_identity_id']);
|
||||
$newPasswordVersion = (int) ($this->currentUser['password_version'] ?? 1) + 1;
|
||||
|
||||
if ($isFederated && !$sso->isLocalOnly()) {
|
||||
$jwt = $this->getBearerToken() ?? '';
|
||||
$ssoResp = $sso->changePassword($jwt, $currentPassword, $newPassword);
|
||||
if ($ssoResp === null) {
|
||||
// unreachable
|
||||
if ($sso->isSsoOnly()) {
|
||||
$this->jsonError('SSO non disponibile, riprovare', 503, 'SSO_UNAVAILABLE');
|
||||
}
|
||||
// dual: log e procedi locale (cron riallineerà appena SSO torna)
|
||||
error_log("[SSO] changePassword unreachable for user {$this->getCurrentUserId()} (dual mode → local only)");
|
||||
} else {
|
||||
$httpStatus = (int) ($ssoResp['_httpStatus'] ?? 0);
|
||||
if ($httpStatus !== 200) {
|
||||
$msg = $ssoResp['error'] ?? $ssoResp['message'] ?? 'Errore SSO change-password';
|
||||
$this->jsonError($msg, $httpStatus ?: 502, 'SSO_CHANGE_PWD_FAILED');
|
||||
}
|
||||
// 200: usa la versione restituita da SSO se disponibile
|
||||
if (isset($ssoResp['password_version'])) {
|
||||
$newPasswordVersion = (int) $ssoResp['password_version'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Database::update('users', [
|
||||
'password_hash' => password_hash($newPassword, PASSWORD_DEFAULT),
|
||||
'password_hash' => password_hash($newPassword, PASSWORD_DEFAULT),
|
||||
'password_version' => $newPasswordVersion,
|
||||
], 'id = ?', [$this->getCurrentUserId()]);
|
||||
|
||||
// Invalida tutti i refresh token (force re-login)
|
||||
Database::delete('refresh_tokens', 'user_id = ?', [$this->getCurrentUserId()]);
|
||||
// --- Revoca altre sessioni, mantieni quella corrente (Fase 2 / G06) ---
|
||||
$currentJti = $this->currentUser['session_jti'] ?? null;
|
||||
$userId = $this->getCurrentUserId();
|
||||
if ($currentJti) {
|
||||
Database::query(
|
||||
"UPDATE active_sessions
|
||||
SET revoked_at = NOW(), revoked_reason = 'password_change'
|
||||
WHERE user_id = ? AND id != ? AND revoked_at IS NULL",
|
||||
[$userId, $currentJti]
|
||||
);
|
||||
Database::query(
|
||||
"DELETE FROM refresh_tokens WHERE user_id = ? AND (session_jti IS NULL OR session_jti != ?)",
|
||||
[$userId, $currentJti]
|
||||
);
|
||||
} else {
|
||||
// Nessun jti corrente → revoca tutto (comportamento pre-Fase2)
|
||||
Database::query(
|
||||
"UPDATE active_sessions
|
||||
SET revoked_at = NOW(), revoked_reason = 'password_change'
|
||||
WHERE user_id = ? AND revoked_at IS NULL",
|
||||
[$userId]
|
||||
);
|
||||
Database::delete('refresh_tokens', 'user_id = ?', [$userId]);
|
||||
}
|
||||
|
||||
$this->logAudit('password_changed', 'user', $this->getCurrentUserId());
|
||||
$this->logAudit('password_changed', 'user', $this->getCurrentUserId(), [
|
||||
'federated' => $isFederated,
|
||||
'password_version' => $newPasswordVersion,
|
||||
]);
|
||||
|
||||
$this->jsonSuccess(null, 'Password modificata. Effettua nuovamente il login.');
|
||||
$this->jsonSuccess(null, 'Password modificata. Le altre sessioni sono state disconnesse.');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/validate-invite
|
||||
*
|
||||
* Valida un codice invito B2B e restituisce piano e metadati.
|
||||
* Nessuna autenticazione richiesta — usato dalla pagina register.html
|
||||
* prima della registrazione per mostrare l'anteprima del piano.
|
||||
*
|
||||
* Body: { "invite_token": "inv_xxxx..." }
|
||||
* Response: { valid: true, plan: "professional", duration_months: 12, ... }
|
||||
*/
|
||||
public function validateInvite(): void
|
||||
{
|
||||
$token = trim($this->getParam('invite_token', ''));
|
||||
|
||||
if (!$token) {
|
||||
$this->jsonError('invite_token mancante', 400, 'MISSING_TOKEN');
|
||||
}
|
||||
|
||||
require_once APP_PATH . '/controllers/InviteController.php';
|
||||
$result = InviteController::resolveInvite($token);
|
||||
|
||||
if (!$result['valid']) {
|
||||
$this->jsonError($result['error'], 422, $result['code'] ?? 'INVALID_INVITE');
|
||||
}
|
||||
|
||||
$inv = $result['invite'];
|
||||
|
||||
$metadata = !empty($inv['metadata']) ? json_decode($inv['metadata'], true) : [];
|
||||
$recipient = $metadata['recipient'] ?? null;
|
||||
|
||||
$this->jsonSuccess([
|
||||
'valid' => true,
|
||||
'plan' => $inv['plan'],
|
||||
'duration_months' => (int) $inv['duration_months'],
|
||||
'expires_at' => $inv['expires_at'],
|
||||
'remaining_uses' => (int)$inv['max_uses'] - (int)$inv['used_count'],
|
||||
'channel' => $inv['channel'],
|
||||
'label' => $inv['label'],
|
||||
'restrict_vat' => $inv['restrict_vat'] ? true : false,
|
||||
'restrict_email' => $inv['restrict_email'] ? true : false,
|
||||
'recipient' => $recipient, // dati pre-compilazione form (null se non presenti)
|
||||
], 'Invito valido');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// PREFERENCES — Tema / timezone / notifiche (Fase 4 / G12)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* GET /api/auth/preferences
|
||||
*/
|
||||
public function getPreferences(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$u = $this->currentUser;
|
||||
$this->jsonSuccess([
|
||||
'preferred_language' => $u['preferred_language'] ?? 'it',
|
||||
'theme' => $u['theme'] ?? 'auto',
|
||||
'timezone' => $u['timezone'] ?? 'Europe/Rome',
|
||||
'notif_email' => (bool) ($u['notif_email'] ?? 1),
|
||||
'notif_inapp' => (bool) ($u['notif_inapp'] ?? 1),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/auth/preferences
|
||||
* Body: { theme?, timezone?, preferred_language?, notif_email?, notif_inapp? }
|
||||
*/
|
||||
public function updatePreferences(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$updates = [];
|
||||
|
||||
if ($this->hasParam('preferred_language')) {
|
||||
$lang = $this->getParam('preferred_language');
|
||||
if (!in_array($lang, ['it','en','fr','de'], true)) {
|
||||
$this->jsonError('Lingua non supportata', 400, 'INVALID_LANGUAGE');
|
||||
}
|
||||
$updates['preferred_language'] = $lang;
|
||||
}
|
||||
if ($this->hasParam('theme')) {
|
||||
$theme = $this->getParam('theme');
|
||||
if (!in_array($theme, ['light','dark','auto'], true)) {
|
||||
$this->jsonError('Tema non valido', 400, 'INVALID_THEME');
|
||||
}
|
||||
$updates['theme'] = $theme;
|
||||
}
|
||||
if ($this->hasParam('timezone')) {
|
||||
$tz = $this->getParam('timezone');
|
||||
// Validation minimale: max 64 chars, formato IANA-like (Region/City)
|
||||
if (!preg_match('#^[A-Za-z_]+(?:/[A-Za-z_+\-0-9]+)*$#', $tz) || strlen($tz) > 64) {
|
||||
$this->jsonError('Timezone non valida', 400, 'INVALID_TIMEZONE');
|
||||
}
|
||||
$updates['timezone'] = $tz;
|
||||
}
|
||||
if ($this->hasParam('notif_email')) {
|
||||
$updates['notif_email'] = $this->getParam('notif_email') ? 1 : 0;
|
||||
}
|
||||
if ($this->hasParam('notif_inapp')) {
|
||||
$updates['notif_inapp'] = $this->getParam('notif_inapp') ? 1 : 0;
|
||||
}
|
||||
if (empty($updates)) {
|
||||
$this->jsonError('Nessun campo da aggiornare', 400, 'NO_UPDATES');
|
||||
}
|
||||
|
||||
Database::update('users', $updates, 'id = ?', [$this->getCurrentUserId()]);
|
||||
$this->logAudit('preferences_updated', 'user', $this->getCurrentUserId(), $updates);
|
||||
|
||||
$this->jsonSuccess($updates, 'Preferenze aggiornate');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// IMPERSONATE — Super admin / Consulente entra come altro utente (Fase 4 / G11)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/auth/impersonate
|
||||
* Body: { user_id }
|
||||
* Emette un JWT speciale per "loggarsi come" l'utente target.
|
||||
* - Permesso solo a super_admin (qualunque target) o consulente verso utenti delle org del suo firm.
|
||||
* - JWT TTL ridotto (~1h, no refresh) + claim `impersonated_by` per audit.
|
||||
* - Sessione corrente del super_admin/consulente NON viene revocata.
|
||||
*/
|
||||
public function impersonate(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$this->validateRequired(['user_id']);
|
||||
|
||||
$targetId = (int) $this->getParam('user_id');
|
||||
$actorId = $this->getCurrentUserId();
|
||||
$actorRole = $this->currentUser['role'];
|
||||
|
||||
if ($targetId === $actorId) {
|
||||
$this->jsonError('Non puoi impersonare te stesso', 400, 'SELF_IMPERSONATE');
|
||||
}
|
||||
|
||||
$target = Database::fetchOne(
|
||||
'SELECT id, email, full_name, role, is_active, consulting_firm_id
|
||||
FROM users WHERE id = ?',
|
||||
[$targetId]
|
||||
);
|
||||
if (!$target || (int) $target['is_active'] !== 1) {
|
||||
$this->jsonError('Utente target non trovato o disabilitato', 404, 'TARGET_NOT_FOUND');
|
||||
}
|
||||
if ($target['role'] === 'super_admin' && $actorRole !== 'super_admin') {
|
||||
$this->jsonError('Non puoi impersonare un super admin', 403, 'IMPERSONATE_FORBIDDEN');
|
||||
}
|
||||
|
||||
// Permessi: super_admin OR consulente sullo stesso firm
|
||||
$allowed = false;
|
||||
if ($actorRole === 'super_admin') {
|
||||
$allowed = true;
|
||||
} elseif ($actorRole === 'consultant') {
|
||||
$actorFirmId = (int) ($this->currentUser['consulting_firm_id'] ?? 0);
|
||||
$targetFirmId = (int) ($target['consulting_firm_id'] ?? 0);
|
||||
if ($actorFirmId > 0 && $actorFirmId === $targetFirmId) {
|
||||
$allowed = true;
|
||||
}
|
||||
// In alternativa: target è org_admin/employee di una org del consulente.
|
||||
// Per ora copertura minimale: stesso firm.
|
||||
}
|
||||
if (!$allowed) {
|
||||
$this->jsonError('Permessi insufficienti per impersonare questo utente', 403, 'IMPERSONATE_FORBIDDEN');
|
||||
}
|
||||
|
||||
// Nuova sessione tracciata (durata ridotta: 1h, niente refresh persistente)
|
||||
$jti = bin2hex(random_bytes(16));
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
Database::insert('active_sessions', [
|
||||
'id' => $jti,
|
||||
'user_id' => $targetId,
|
||||
'organization_id'=> null,
|
||||
'ip_address' => $this->getClientIP(),
|
||||
'user_agent' => substr($ua, 0, 512),
|
||||
'device_label' => 'Impersonato da #' . $actorId,
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + 3600),
|
||||
]);
|
||||
|
||||
$accessToken = $this->generateJWT($targetId, [
|
||||
'jti' => $jti,
|
||||
'impersonated_by' => $actorId,
|
||||
'exp' => time() + 3600,
|
||||
]);
|
||||
|
||||
$this->logAudit('user_impersonated', 'user', $targetId, [
|
||||
'actor_id' => $actorId,
|
||||
'actor_role' => $actorRole,
|
||||
'duration_seconds' => 3600,
|
||||
]);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'user' => [
|
||||
'id' => (int) $target['id'],
|
||||
'email' => $target['email'],
|
||||
'full_name' => $target['full_name'],
|
||||
'role' => $target['role'],
|
||||
],
|
||||
'access_token' => $accessToken,
|
||||
'expires_in' => 3600,
|
||||
'impersonated' => true,
|
||||
], 'Impersonate attivo per 1 ora');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// CONTEXT SWITCH — Cambio organization attiva (Fase 3 / G09)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/auth/switch-context
|
||||
* Body: { organization_id }
|
||||
* Verifica membership, revoca la sessione corrente con reason='context_switch',
|
||||
* crea una nuova sessione e ritorna nuovo JWT + refresh con organization_id come claim.
|
||||
*/
|
||||
public function switchContext(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$this->validateRequired(['organization_id']);
|
||||
|
||||
$targetOrgId = (int) $this->getParam('organization_id');
|
||||
$userId = $this->getCurrentUserId();
|
||||
$oldJti = $this->currentUser['session_jti'] ?? null;
|
||||
|
||||
// Super admin bypass membership check
|
||||
if ($this->currentUser['role'] !== 'super_admin') {
|
||||
$membership = Database::fetchOne(
|
||||
'SELECT role FROM user_organizations WHERE user_id = ? AND organization_id = ?',
|
||||
[$userId, $targetOrgId]
|
||||
);
|
||||
if (!$membership) {
|
||||
$this->jsonError('Non sei membro di questa organizzazione', 403, 'NOT_MEMBER');
|
||||
}
|
||||
}
|
||||
|
||||
$org = Database::fetchOne(
|
||||
'SELECT id, name FROM organizations WHERE id = ? AND is_active = 1',
|
||||
[$targetOrgId]
|
||||
);
|
||||
if (!$org) {
|
||||
$this->jsonError('Organizzazione non trovata o disabilitata', 404, 'ORG_NOT_FOUND');
|
||||
}
|
||||
|
||||
Database::beginTransaction();
|
||||
try {
|
||||
// Revoca sessione corrente
|
||||
if ($oldJti) {
|
||||
Database::query(
|
||||
"UPDATE active_sessions
|
||||
SET revoked_at = NOW(), revoked_reason = 'context_switch'
|
||||
WHERE id = ? AND revoked_at IS NULL",
|
||||
[$oldJti]
|
||||
);
|
||||
Database::delete('refresh_tokens', 'session_jti = ?', [$oldJti]);
|
||||
}
|
||||
|
||||
// Nuova sessione con organization_id valorizzato
|
||||
$newJti = bin2hex(random_bytes(16));
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
Database::insert('active_sessions', [
|
||||
'id' => $newJti,
|
||||
'user_id' => $userId,
|
||||
'organization_id' => $targetOrgId,
|
||||
'ip_address' => $this->getClientIP(),
|
||||
'user_agent' => substr($ua, 0, 512),
|
||||
'device_label' => $this->parseDeviceLabel($ua),
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + JWT_REFRESH_EXPIRES_IN),
|
||||
]);
|
||||
|
||||
$accessToken = $this->generateJWT($userId, [
|
||||
'jti' => $newJti,
|
||||
'organization_id' => $targetOrgId,
|
||||
]);
|
||||
$refreshToken = $this->generateRefreshToken($userId, $newJti);
|
||||
|
||||
Database::commit();
|
||||
} catch (Throwable $e) {
|
||||
Database::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->logAudit('context_switched', 'organization', $targetOrgId);
|
||||
|
||||
$this->jsonSuccess([
|
||||
'organization' => [
|
||||
'id' => (int) $org['id'],
|
||||
'name' => $org['name'],
|
||||
],
|
||||
'access_token' => $accessToken,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => JWT_EXPIRES_IN,
|
||||
], 'Contesto cambiato');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// PASSWORD RESET — Forgot/Reset (Fase 3 / G08)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* POST /api/auth/forgot-password
|
||||
* Body: { email }
|
||||
* Risposta SEMPRE 200 + messaggio generico (anti enumeration).
|
||||
*/
|
||||
public function forgotPassword(): void
|
||||
{
|
||||
$this->validateRequired(['email']);
|
||||
$email = strtolower(trim($this->getParam('email')));
|
||||
$ip = $this->getClientIP();
|
||||
|
||||
// Rate limit per IP+email combinati (anti spam)
|
||||
$rlKey = 'forgot:' . $ip . ':' . md5($email);
|
||||
RateLimitService::check($rlKey, RATE_LIMIT_AUTH_FORGOT);
|
||||
RateLimitService::increment($rlKey);
|
||||
|
||||
$user = Database::fetchOne(
|
||||
'SELECT id, email, full_name FROM users WHERE email = ? AND is_active = 1',
|
||||
[$email]
|
||||
);
|
||||
|
||||
// Risposta opaca: anche se l'email non esiste rispondiamo come se ok
|
||||
$okMessage = 'Se l\'indirizzo email è registrato, riceverai a breve un link per reimpostare la password.';
|
||||
|
||||
if ($user) {
|
||||
// Invalida eventuali token precedenti non usati
|
||||
Database::query(
|
||||
'UPDATE password_reset_tokens SET used_at = NOW()
|
||||
WHERE user_id = ? AND used_at IS NULL',
|
||||
[$user['id']]
|
||||
);
|
||||
|
||||
// Genera token (in chiaro inviato via mail, hash sul DB)
|
||||
$token = bin2hex(random_bytes(32));
|
||||
Database::insert('password_reset_tokens', [
|
||||
'user_id' => (int) $user['id'],
|
||||
'token_hash' => hash('sha256', $token),
|
||||
'expires_at' => date('Y-m-d H:i:s', time() + PASSWORD_RESET_TTL_SECONDS),
|
||||
'ip_address' => $ip,
|
||||
]);
|
||||
|
||||
// Invia email
|
||||
try {
|
||||
require_once APP_PATH . '/services/EmailService.php';
|
||||
$email_service = new EmailService();
|
||||
$email_service->sendPasswordReset($user, $token, PASSWORD_RESET_TTL_SECONDS);
|
||||
} catch (Throwable $e) {
|
||||
error_log("[forgot-password] sendPasswordReset failed for user {$user['id']}: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$this->logAudit('password_reset_requested', 'user', (int) $user['id']);
|
||||
}
|
||||
|
||||
$this->jsonSuccess(null, $okMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/auth/reset-password
|
||||
* Body: { token, new_password }
|
||||
*/
|
||||
public function resetPassword(): void
|
||||
{
|
||||
$this->validateRequired(['token', 'new_password']);
|
||||
$token = $this->getParam('token');
|
||||
$newPassword = $this->getParam('new_password');
|
||||
|
||||
$errors = $this->validatePassword($newPassword);
|
||||
if (!empty($errors)) {
|
||||
$this->jsonError(implode('. ', $errors), 400, 'WEAK_PASSWORD');
|
||||
}
|
||||
|
||||
$hash = hash('sha256', $token);
|
||||
$row = Database::fetchOne(
|
||||
'SELECT prt.id, prt.user_id, prt.expires_at, prt.used_at, u.email, u.sso_identity_id
|
||||
FROM password_reset_tokens prt
|
||||
JOIN users u ON u.id = prt.user_id
|
||||
WHERE prt.token_hash = ?',
|
||||
[$hash]
|
||||
);
|
||||
|
||||
if (!$row) {
|
||||
$this->jsonError('Token non valido', 400, 'INVALID_TOKEN');
|
||||
}
|
||||
if ($row['used_at']) {
|
||||
$this->jsonError('Token già utilizzato', 400, 'TOKEN_USED');
|
||||
}
|
||||
if (strtotime($row['expires_at']) < time()) {
|
||||
$this->jsonError('Token scaduto. Richiedere un nuovo link.', 400, 'TOKEN_EXPIRED');
|
||||
}
|
||||
|
||||
$userId = (int) $row['user_id'];
|
||||
$isFederated = !empty($row['sso_identity_id']);
|
||||
$newPasswordVersion = 2; // bump da 1 base; ci aggiorneremo dal cron sync
|
||||
|
||||
Database::beginTransaction();
|
||||
try {
|
||||
// Consume token
|
||||
Database::update('password_reset_tokens',
|
||||
['used_at' => date('Y-m-d H:i:s')],
|
||||
'id = ?',
|
||||
[$row['id']]
|
||||
);
|
||||
|
||||
// Update password
|
||||
Database::update('users', [
|
||||
'password_hash' => password_hash($newPassword, PASSWORD_DEFAULT),
|
||||
'password_version' => $newPasswordVersion,
|
||||
], 'id = ?', [$userId]);
|
||||
|
||||
// Revoca TUTTE le sessioni (no sessione corrente = reset da non-loggato)
|
||||
Database::query(
|
||||
"UPDATE active_sessions
|
||||
SET revoked_at = NOW(), revoked_reason = 'password_change'
|
||||
WHERE user_id = ? AND revoked_at IS NULL",
|
||||
[$userId]
|
||||
);
|
||||
Database::delete('refresh_tokens', 'user_id = ?', [$userId]);
|
||||
|
||||
Database::commit();
|
||||
} catch (Throwable $e) {
|
||||
Database::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->logAudit('password_reset_completed', 'user', $userId, [
|
||||
'federated_user' => $isFederated,
|
||||
]);
|
||||
|
||||
// Nota: per utenti federati (sso_identity_id != NULL) il cambio è solo locale.
|
||||
// Il cron `sso-password-sync` (richiesto via AgileHub Ticket #220) potrebbe
|
||||
// riallineare la password lato SSO al prossimo run.
|
||||
|
||||
$this->jsonSuccess(null, 'Password reimpostata. Puoi accedere con la nuova password.');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SESSIONS — Multi-device management (Fase 2 / G06)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* GET /api/auth/sessions
|
||||
* Lista le sessioni attive dell'utente corrente.
|
||||
*/
|
||||
public function listSessions(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$userId = $this->getCurrentUserId();
|
||||
$currentJti = $this->currentUser['session_jti'] ?? null;
|
||||
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, ip_address, device_label, created_at, last_activity_at, expires_at
|
||||
FROM active_sessions
|
||||
WHERE user_id = ? AND revoked_at IS NULL AND expires_at > NOW()
|
||||
ORDER BY last_activity_at DESC',
|
||||
[$userId]
|
||||
);
|
||||
|
||||
$sessions = array_map(function ($r) use ($currentJti) {
|
||||
return [
|
||||
'id' => $r['id'],
|
||||
'ip_address' => $r['ip_address'],
|
||||
'device_label' => $r['device_label'] ?? 'Browser',
|
||||
'created_at' => $r['created_at'],
|
||||
'last_activity_at' => $r['last_activity_at'],
|
||||
'expires_at' => $r['expires_at'],
|
||||
'is_current' => $r['id'] === $currentJti,
|
||||
];
|
||||
}, $rows);
|
||||
|
||||
$this->jsonSuccess(['sessions' => $sessions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/auth/sessions/{id}
|
||||
* Revoca una sessione specifica. Se id == sessione corrente equivale a logout.
|
||||
*/
|
||||
public function revokeSession(string $id): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$userId = $this->getCurrentUserId();
|
||||
$currentJti = $this->currentUser['session_jti'] ?? null;
|
||||
|
||||
$row = Database::fetchOne(
|
||||
'SELECT id FROM active_sessions
|
||||
WHERE id = ? AND user_id = ? AND revoked_at IS NULL',
|
||||
[$id, $userId]
|
||||
);
|
||||
if (!$row) {
|
||||
$this->jsonError('Sessione non trovata', 404, 'SESSION_NOT_FOUND');
|
||||
}
|
||||
|
||||
$reason = ($id === $currentJti) ? 'logout' : 'admin';
|
||||
Database::query(
|
||||
"UPDATE active_sessions SET revoked_at = NOW(), revoked_reason = ?
|
||||
WHERE id = ?",
|
||||
[$reason, $id]
|
||||
);
|
||||
Database::delete('refresh_tokens', 'session_jti = ?', [$id]);
|
||||
|
||||
$this->logAudit('session_revoked', 'session', null, [
|
||||
'session_id' => $id,
|
||||
'self' => ($id === $currentJti),
|
||||
]);
|
||||
|
||||
$this->jsonSuccess(null, 'Sessione revocata');
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/auth/sessions
|
||||
* Revoca tutte le sessioni dell'utente tranne quella corrente.
|
||||
*/
|
||||
public function revokeAllSessions(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$userId = $this->getCurrentUserId();
|
||||
$currentJti = $this->currentUser['session_jti'] ?? null;
|
||||
|
||||
if ($currentJti) {
|
||||
Database::query(
|
||||
"UPDATE active_sessions SET revoked_at = NOW(), revoked_reason = 'admin'
|
||||
WHERE user_id = ? AND id != ? AND revoked_at IS NULL",
|
||||
[$userId, $currentJti]
|
||||
);
|
||||
Database::query(
|
||||
"DELETE FROM refresh_tokens
|
||||
WHERE user_id = ? AND (session_jti IS NULL OR session_jti != ?)",
|
||||
[$userId, $currentJti]
|
||||
);
|
||||
} else {
|
||||
Database::query(
|
||||
"UPDATE active_sessions SET revoked_at = NOW(), revoked_reason = 'admin'
|
||||
WHERE user_id = ? AND revoked_at IS NULL",
|
||||
[$userId]
|
||||
);
|
||||
Database::delete('refresh_tokens', 'user_id = ?', [$userId]);
|
||||
}
|
||||
|
||||
$this->logAudit('sessions_revoked_all', 'user', $userId);
|
||||
|
||||
$this->jsonSuccess(null, 'Sessioni revocate (tranne questa)');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,20 @@
|
||||
*/
|
||||
|
||||
require_once APP_PATH . '/config/database.php';
|
||||
require_once APP_PATH . '/services/AuditService.php';
|
||||
|
||||
class BaseController
|
||||
{
|
||||
protected ?array $currentUser = null;
|
||||
protected ?array $currentSession = null;
|
||||
protected ?int $currentOrgId = null;
|
||||
protected ?string $currentOrgRole = null;
|
||||
|
||||
// ── Contesto Avatar di prodotto (demo/sandbox) — vedi applyDemoGuard() ──
|
||||
protected bool $isDemo = false; // sessione demo attiva (read-only o sandbox)
|
||||
protected bool $isSandbox = false; // scope training:sandbox (scritture su org sandbox)
|
||||
protected ?int $demoOrgId = null; // org del range riservato demo (996000-996999)
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// RISPOSTE JSON
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
@@ -248,8 +255,78 @@ class BaseController
|
||||
/**
|
||||
* Richiede autenticazione JWT
|
||||
*/
|
||||
/**
|
||||
* Read-only guard Avatar di prodotto (product-demo-protocol v1.0.1).
|
||||
*
|
||||
* SAFE-BY-CONSTRUCTION: ritorna false (e NON tocca nulla) per qualsiasi JWT
|
||||
* applicativo normale. Si attiva SOLO con un demo_jwt firmato che porta
|
||||
* scope=demo:read-only oppure training:sandbox, validato contro demo_sessions.
|
||||
*
|
||||
* - demo:read-only → blocca OGNI scrittura (POST/PUT/PATCH/DELETE) con 403.
|
||||
* - training:sandbox → consente scritture, ma SOLO sull'org sandbox della sessione.
|
||||
* - Contesto sintetico: user id=0, ruolo compliance_manager, org = org del range
|
||||
* demo riservato (996000-996999). Mai super_admin → admin resta irraggiungibile.
|
||||
*
|
||||
* @return bool true se ha gestito un contesto demo (il chiamante deve return).
|
||||
*/
|
||||
protected function applyDemoGuard(): bool
|
||||
{
|
||||
$token = $this->getBearerToken();
|
||||
if (!$token) return false;
|
||||
|
||||
$payload = $this->verifyJWT($token);
|
||||
if (!$payload) return false;
|
||||
|
||||
$scope = $payload['scope'] ?? '';
|
||||
if ($scope !== 'demo:read-only' && $scope !== 'training:sandbox') {
|
||||
return false; // JWT applicativo normale → flusso invariato
|
||||
}
|
||||
|
||||
$orgId = (int) ($payload['org_id'] ?? 0);
|
||||
if ($orgId < 996000 || $orgId > 996999) {
|
||||
$this->jsonError('Contesto demo non valido', 401, 'DEMO_CTX_INVALID');
|
||||
}
|
||||
|
||||
$sid = (string) ($payload['demo_session_id'] ?? '');
|
||||
$sess = $sid !== '' ? Database::fetchOne('SELECT * FROM demo_sessions WHERE session_id = ?', [$sid]) : null;
|
||||
if (!$sess || strtotime($sess['expires_at']) < time()) {
|
||||
$this->jsonError('Sessione demo non valida o scaduta', 401, 'DEMO_SESSION_INVALID');
|
||||
}
|
||||
|
||||
$isWrite = in_array($this->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'], true);
|
||||
if ($scope === 'demo:read-only' && $isWrite) {
|
||||
$this->jsonError('Modalità demo in sola lettura: azione non disponibile', 403, 'DEMO_READ_ONLY');
|
||||
}
|
||||
if ($scope === 'training:sandbox' && $isWrite) {
|
||||
// Scritture consentite SOLO sull'org sandbox della sessione (anti-spoof X-Organization-Id).
|
||||
$reqOrgRaw = $_SERVER['HTTP_X_ORGANIZATION_ID'] ?? $this->getParam('org_id');
|
||||
$reqOrg = ($reqOrgRaw !== null && $reqOrgRaw !== '') ? (int) $reqOrgRaw : null;
|
||||
if ($reqOrg !== null && $reqOrg !== $orgId) {
|
||||
$this->jsonError('Sandbox: scritture consentite solo sull\'organizzazione sandbox', 403, 'SANDBOX_ORG_LOCKED');
|
||||
}
|
||||
}
|
||||
|
||||
// Contesto sintetico read (o sandbox). Nessun accesso al DB utenti reali.
|
||||
$this->isDemo = true;
|
||||
$this->isSandbox = ($scope === 'training:sandbox');
|
||||
$this->demoOrgId = $orgId;
|
||||
$this->currentUser = [
|
||||
'id' => 0, 'email' => 'demo@nis2-demo.local',
|
||||
'full_name' => 'Avatar Demo', 'role' => 'compliance_manager',
|
||||
'consulting_firm_id' => null,
|
||||
];
|
||||
$this->currentOrgId = $orgId;
|
||||
$this->currentOrgRole = 'compliance_manager';
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function requireAuth(): void
|
||||
{
|
||||
// Avatar di prodotto: se è un demo_jwt valido, applica il guard e termina qui.
|
||||
if ($this->applyDemoGuard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$token = $this->getBearerToken();
|
||||
|
||||
if (!$token) {
|
||||
@@ -271,9 +348,61 @@ class BaseController
|
||||
$this->jsonError('Utente non trovato o disabilitato', 401, 'USER_NOT_FOUND');
|
||||
}
|
||||
|
||||
// --- Multi-device session verification (Fase 2 / G06) ---
|
||||
// JWT senza jti = legacy (pre Fase 2) → rifiutato per forzare nuovo login con sessione tracciata.
|
||||
$jti = $payload['jti'] ?? null;
|
||||
if (!$jti) {
|
||||
$this->jsonError('Token senza session id — effettua nuovamente il login', 401, 'JWT_NO_JTI');
|
||||
}
|
||||
$session = Database::fetchOne(
|
||||
'SELECT * FROM active_sessions
|
||||
WHERE id = ? AND user_id = ? AND revoked_at IS NULL AND expires_at > NOW()',
|
||||
[$jti, (int) $user['id']]
|
||||
);
|
||||
if (!$session) {
|
||||
$this->jsonError('Sessione non valida o revocata', 401, 'SESSION_REVOKED');
|
||||
}
|
||||
// Throttle last_activity update: max 1 update/min per evitare write storm
|
||||
$lastTs = strtotime($session['last_activity_at']);
|
||||
if ($lastTs && (time() - $lastTs) > 60) {
|
||||
Database::update('active_sessions',
|
||||
['last_activity_at' => date('Y-m-d H:i:s')],
|
||||
'id = ?',
|
||||
[$jti]
|
||||
);
|
||||
}
|
||||
$user['session_jti'] = $jti;
|
||||
$this->currentSession = $session;
|
||||
|
||||
$this->currentUser = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse User-Agent in label friendly: "Chrome 134 su Windows", "Safari su macOS", ecc.
|
||||
* Fallback "Browser sconosciuto" se UA assente/malformato.
|
||||
*/
|
||||
protected function parseDeviceLabel(string $ua): string
|
||||
{
|
||||
if ($ua === '') return 'Browser sconosciuto';
|
||||
$os = 'OS sconosciuto';
|
||||
if (preg_match('/Windows NT 10/i', $ua)) $os = 'Windows';
|
||||
elseif (preg_match('/Mac OS X|Macintosh/i', $ua)) $os = 'macOS';
|
||||
elseif (preg_match('/Android/i', $ua)) $os = 'Android';
|
||||
elseif (preg_match('/iPhone|iPad|iOS/i', $ua)) $os = 'iOS';
|
||||
elseif (preg_match('/Linux/i', $ua)) $os = 'Linux';
|
||||
|
||||
$browser = 'Browser';
|
||||
if (preg_match('/Edg\/([0-9]+)/', $ua, $m)) $browser = 'Edge ' . $m[1];
|
||||
elseif (preg_match('/OPR\/([0-9]+)/', $ua, $m)) $browser = 'Opera ' . $m[1];
|
||||
elseif (preg_match('/Chrome\/([0-9]+)/', $ua, $m)) $browser = 'Chrome ' . $m[1];
|
||||
elseif (preg_match('/Firefox\/([0-9]+)/', $ua, $m)) $browser = 'Firefox ' . $m[1];
|
||||
elseif (preg_match('/Safari\/([0-9]+)/', $ua, $m) && !preg_match('/Chrome/', $ua)) {
|
||||
if (preg_match('/Version\/([0-9]+)/', $ua, $m2)) $browser = 'Safari ' . $m2[1];
|
||||
else $browser = 'Safari';
|
||||
}
|
||||
return substr($browser . ' su ' . $os, 0, 120);
|
||||
}
|
||||
|
||||
/**
|
||||
* Richiede ruolo super_admin
|
||||
*/
|
||||
@@ -297,6 +426,13 @@ class BaseController
|
||||
{
|
||||
$this->requireAuth();
|
||||
|
||||
// Avatar di prodotto: contesto org già fissato dal guard sull'org demo/sandbox.
|
||||
if ($this->isDemo) {
|
||||
$this->currentOrgId = $this->demoOrgId;
|
||||
$this->currentOrgRole = 'compliance_manager';
|
||||
return;
|
||||
}
|
||||
|
||||
$orgId = $this->resolveOrgId();
|
||||
|
||||
if (!$orgId) {
|
||||
@@ -331,6 +467,12 @@ class BaseController
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
|
||||
// Avatar di prodotto: le scritture sono già filtrate dal guard (demo=block,
|
||||
// sandbox=solo org sandbox). Le letture passano i gate di ruolo.
|
||||
if ($this->isDemo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->currentOrgRole === 'super_admin') {
|
||||
return;
|
||||
}
|
||||
@@ -498,28 +640,31 @@ class BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera refresh token
|
||||
* Genera refresh token.
|
||||
* Da Fase 2 (G05) supporta linking esplicito alla `active_sessions.id` via $sessionJti
|
||||
* per permettere rotazione safe e revoca cascade.
|
||||
*/
|
||||
protected function generateRefreshToken(int $userId): string
|
||||
protected function generateRefreshToken(int $userId, ?string $sessionJti = null): string
|
||||
{
|
||||
$token = bin2hex(random_bytes(32));
|
||||
$expiresAt = date('Y-m-d H:i:s', time() + JWT_REFRESH_EXPIRES_IN);
|
||||
|
||||
Database::insert('refresh_tokens', [
|
||||
'user_id' => $userId,
|
||||
'token' => hash('sha256', $token),
|
||||
'expires_at' => $expiresAt,
|
||||
'user_id' => $userId,
|
||||
'token' => hash('sha256', $token),
|
||||
'expires_at' => $expiresAt,
|
||||
'session_jti' => $sessionJti,
|
||||
]);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function base64UrlEncode(string $data): string
|
||||
protected function base64UrlEncode(string $data): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private function base64UrlDecode(string $data): string
|
||||
protected function base64UrlDecode(string $data): string
|
||||
{
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
@@ -533,16 +678,23 @@ class BaseController
|
||||
*/
|
||||
protected function logAudit(string $action, ?string $entityType = null, ?int $entityId = null, ?array $details = null): void
|
||||
{
|
||||
Database::insert('audit_logs', [
|
||||
'user_id' => $this->getCurrentUserId(),
|
||||
'organization_id' => $this->currentOrgId,
|
||||
'action' => $action,
|
||||
'entity_type' => $entityType,
|
||||
'entity_id' => $entityId,
|
||||
'details' => $details ? json_encode($details) : null,
|
||||
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
|
||||
]);
|
||||
$orgId = $this->currentOrgId;
|
||||
$userId = $this->getCurrentUserId();
|
||||
$severity = AuditService::resolveSeverity($action, $details);
|
||||
$email = $this->currentUser['email'] ?? null;
|
||||
|
||||
AuditService::log(
|
||||
$orgId ?? 0,
|
||||
$userId,
|
||||
$action,
|
||||
$entityType,
|
||||
$entityId,
|
||||
$details,
|
||||
$_SERVER['REMOTE_ADDR'] ?? '',
|
||||
$_SERVER['HTTP_USER_AGENT'] ?? null,
|
||||
$severity,
|
||||
$email
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - BrandingController
|
||||
*
|
||||
* White-label branding per consulting firms (Fase 5 / G16).
|
||||
* - GET /api/branding/current
|
||||
* - Se utente autenticato → lookup tramite users.consulting_firm_id
|
||||
* - Altrimenti, query param ?firm_id=N (utile in pagine pre-auth come login)
|
||||
* - Ritorna logo_url, primary_color, secondary_color, custom_brand_name (o defaults)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
|
||||
class BrandingController extends BaseController
|
||||
{
|
||||
private const DEFAULTS = [
|
||||
'logo_url' => null,
|
||||
'primary_color' => '#1e40af',
|
||||
'secondary_color' => '#06b6d4',
|
||||
'custom_brand_name' => null,
|
||||
];
|
||||
|
||||
public function getCurrent(): void
|
||||
{
|
||||
$firmId = null;
|
||||
|
||||
// Auth opzionale: se token presente, usa firm dell'utente; altrimenti param.
|
||||
$token = $this->getBearerToken();
|
||||
if ($token) {
|
||||
$payload = $this->verifyJWT($token);
|
||||
if ($payload && !empty($payload['user_id'])) {
|
||||
$u = Database::fetchOne(
|
||||
'SELECT consulting_firm_id FROM users WHERE id = ? AND is_active = 1',
|
||||
[$payload['user_id']]
|
||||
);
|
||||
if ($u && !empty($u['consulting_firm_id'])) {
|
||||
$firmId = (int) $u['consulting_firm_id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: query param
|
||||
if (!$firmId && isset($_GET['firm_id'])) {
|
||||
$firmId = (int) $_GET['firm_id'];
|
||||
}
|
||||
|
||||
$branding = self::DEFAULTS;
|
||||
|
||||
if ($firmId > 0) {
|
||||
$row = Database::fetchOne(
|
||||
'SELECT logo_url, primary_color, secondary_color, custom_brand_name
|
||||
FROM firm_branding WHERE firm_id = ?',
|
||||
[$firmId]
|
||||
);
|
||||
if ($row) {
|
||||
foreach ($branding as $k => $v) {
|
||||
if (!empty($row[$k])) $branding[$k] = $row[$k];
|
||||
}
|
||||
$branding['firm_id'] = $firmId;
|
||||
}
|
||||
}
|
||||
|
||||
$this->jsonSuccess($branding);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /api/branding
|
||||
* Body: { logo_url?, primary_color?, secondary_color?, custom_brand_name? }
|
||||
* Permesso: super_admin OR consulente del firm
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
$firmId = (int) ($this->currentUser['consulting_firm_id'] ?? 0);
|
||||
|
||||
if ($this->currentUser['role'] === 'super_admin' && $this->hasParam('firm_id')) {
|
||||
$firmId = (int) $this->getParam('firm_id');
|
||||
}
|
||||
if (!$firmId) {
|
||||
$this->jsonError('Nessun firm associato', 422, 'NO_FIRM');
|
||||
}
|
||||
if ($this->currentUser['role'] !== 'super_admin' && $this->currentUser['role'] !== 'consultant') {
|
||||
$this->jsonError('Solo super_admin o consulente possono modificare il branding', 403, 'BRANDING_FORBIDDEN');
|
||||
}
|
||||
|
||||
$updates = [];
|
||||
foreach (['logo_url','primary_color','secondary_color','custom_brand_name'] as $k) {
|
||||
if ($this->hasParam($k)) {
|
||||
$v = $this->getParam($k);
|
||||
if ($v === '' || $v === null) $v = null;
|
||||
if (in_array($k, ['primary_color','secondary_color'], true) && $v !== null
|
||||
&& !preg_match('/^#[0-9A-Fa-f]{6}$/', $v)) {
|
||||
$this->jsonError('Colore non valido (formato atteso #RRGGBB): ' . $k, 400, 'INVALID_COLOR');
|
||||
}
|
||||
$updates[$k] = $v;
|
||||
}
|
||||
}
|
||||
if (empty($updates)) {
|
||||
$this->jsonError('Nessun campo da aggiornare', 400, 'NO_UPDATES');
|
||||
}
|
||||
|
||||
// Upsert
|
||||
$existing = Database::fetchOne('SELECT firm_id FROM firm_branding WHERE firm_id = ?', [$firmId]);
|
||||
if ($existing) {
|
||||
Database::update('firm_branding', $updates, 'firm_id = ?', [$firmId]);
|
||||
} else {
|
||||
$updates['firm_id'] = $firmId;
|
||||
Database::insert('firm_branding', $updates);
|
||||
}
|
||||
|
||||
$this->logAudit('branding_updated', 'firm', $firmId, $updates);
|
||||
$this->jsonSuccess($updates, 'Branding aggiornato');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
<?php
|
||||
/**
|
||||
* NIS2 Agile - Calendario unico delle scadenze (Modulo C)
|
||||
* ----------------------------------------------------------------------------
|
||||
* AGGREGATORE IN SOLA LETTURA. Nessuna tabella propria, nessuna migrazione: le
|
||||
* scadenze vivono gia' nei moduli sorgente, ciascuna col proprio ciclo di vita.
|
||||
* Questo controller fa una UNION LIVE di TUTTE le sorgenti e normalizza ogni
|
||||
* evento in una forma unica:
|
||||
* { source, type, title, date (YYYY-MM-DD), status, severity, entity_type,
|
||||
* entity_id, link }
|
||||
* dove status e' calcolato LIVE confrontando la data con oggi (come review_schedule):
|
||||
* - done = scadenza gia' assolta/chiusa
|
||||
* - overdue = data < oggi e non assolta
|
||||
* - due_soon = data entro 14 giorni da oggi
|
||||
* - upcoming = data oltre 14 giorni
|
||||
*
|
||||
* Generalizza il pattern di DashboardController::deadlines() su TUTTE le sorgenti.
|
||||
* Ogni sorgente e' racchiusa in un try/catch DIFENSIVO: alcune tabelle (Modulo A
|
||||
* internal_audits, Modulo B management_reviews) potrebbero non esistere ancora, e
|
||||
* il calendario deve continuare a funzionare comunque (degrada solo quella fonte).
|
||||
*
|
||||
* Multi-tenancy: OGNI query filtra organization_id (anti-IDOR). Sola lettura →
|
||||
* requireOrgAccess(). NOTE: Database::query/fetchAll/fetchOne; jsonSuccess/Error exit.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/BaseController.php';
|
||||
|
||||
class CalendarController extends BaseController
|
||||
{
|
||||
/** Soglia "in scadenza": eventi entro N giorni da oggi (inclusi) sono due_soon. */
|
||||
private const DUE_SOON_DAYS = 14;
|
||||
|
||||
/** Tipi noti (per la validazione del filtro ?types= e per la legenda frontend). */
|
||||
private const KNOWN_TYPES = [
|
||||
'incident_early_warning', 'incident_notification', 'incident_final_report',
|
||||
'policy_review', 'risk_treatment', 'control_review',
|
||||
'nc_target_close', 'capa_action', 'training_due',
|
||||
'stakeholder_activity', 'review_schedule',
|
||||
'internal_audit', 'management_review_decision', 'periodic_control',
|
||||
];
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// GET /api/calendar/events?from=YYYY-MM-DD&to=YYYY-MM-DD&types=csv
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
public function events(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
[$from, $to] = $this->resolveRange();
|
||||
$typeFilter = $this->resolveTypeFilter();
|
||||
|
||||
$events = $this->collect($orgId, $from, $to);
|
||||
|
||||
// Filtro per tipo richiesto (applicato in PHP, gli eventi sono pochi per org).
|
||||
if ($typeFilter !== null) {
|
||||
$events = array_values(array_filter($events, static fn($e) => in_array($e['type'], $typeFilter, true)));
|
||||
}
|
||||
|
||||
// Ordina: overdue prima, poi per data crescente.
|
||||
usort($events, static function ($a, $b) {
|
||||
$oa = $a['status'] === 'overdue' ? 0 : 1;
|
||||
$ob = $b['status'] === 'overdue' ? 0 : 1;
|
||||
if ($oa !== $ob) { return $oa <=> $ob; }
|
||||
return strcmp($a['date'], $b['date']);
|
||||
});
|
||||
|
||||
$this->jsonSuccess([
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'today' => date('Y-m-d'),
|
||||
'due_soon_days'=> self::DUE_SOON_DAYS,
|
||||
'known_types' => self::KNOWN_TYPES,
|
||||
'count' => count($events),
|
||||
'events' => $events,
|
||||
]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// GET /api/calendar/summary — conteggi per stato (e per tipo)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
public function summary(): void
|
||||
{
|
||||
$this->requireOrgAccess();
|
||||
$orgId = $this->getCurrentOrgId();
|
||||
|
||||
[$from, $to] = $this->resolveRange();
|
||||
$events = $this->collect($orgId, $from, $to);
|
||||
|
||||
$byStatus = ['overdue' => 0, 'due_soon' => 0, 'upcoming' => 0, 'done' => 0];
|
||||
$byType = [];
|
||||
foreach ($events as $e) {
|
||||
$byStatus[$e['status']] = ($byStatus[$e['status']] ?? 0) + 1;
|
||||
$byType[$e['type']] = ($byType[$e['type']] ?? 0) + 1;
|
||||
}
|
||||
// Aperte = tutte tranne done (utile come badge dashboard).
|
||||
$open = $byStatus['overdue'] + $byStatus['due_soon'] + $byStatus['upcoming'];
|
||||
|
||||
$this->jsonSuccess([
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'today' => date('Y-m-d'),
|
||||
'total' => count($events),
|
||||
'open' => $open,
|
||||
'by_status' => $byStatus,
|
||||
'by_type' => $byType,
|
||||
]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// RACCOLTA — UNION di tutte le sorgenti, ognuna difensiva (try/catch)
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Raccoglie e normalizza tutti gli eventi della finestra [from,to] per l'org.
|
||||
* Ogni sorgente che fallisce (tabella/colonna mancante) viene saltata senza
|
||||
* far cadere l'intero calendario.
|
||||
*/
|
||||
private function collect(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$events = [];
|
||||
$sources = [
|
||||
'incidents' => fn() => $this->srcIncidents($orgId, $from, $to),
|
||||
'policies' => fn() => $this->srcPolicies($orgId, $from, $to),
|
||||
'risk_treatments' => fn() => $this->srcRiskTreatments($orgId, $from, $to),
|
||||
'controls' => fn() => $this->srcControls($orgId, $from, $to),
|
||||
'non_conformities' => fn() => $this->srcNonConformities($orgId, $from, $to),
|
||||
'capa_actions' => fn() => $this->srcCapaActions($orgId, $from, $to),
|
||||
'training' => fn() => $this->srcTraining($orgId, $from, $to),
|
||||
'stk_activities' => fn() => $this->srcStkActivities($orgId, $from, $to),
|
||||
'review_schedule' => fn() => $this->srcReviewSchedule($orgId, $from, $to),
|
||||
'internal_audits' => fn() => $this->srcInternalAudits($orgId, $from, $to),
|
||||
'mgmt_reviews' => fn() => $this->srcManagementReviewDecisions($orgId, $from, $to),
|
||||
'periodic_controls'=> fn() => $this->srcPeriodicControls($orgId, $from, $to),
|
||||
];
|
||||
foreach ($sources as $rows) {
|
||||
try {
|
||||
foreach ($rows() as $ev) {
|
||||
if ($ev !== null) { $events[] = $ev; }
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Sorgente non disponibile (es. tabella di un modulo non ancora migrato):
|
||||
// si ignora senza compromettere le altre fonti.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return $events;
|
||||
}
|
||||
|
||||
/** Incidenti Art.23: early_warning / notification / final_report (datetime). */
|
||||
private function srcIncidents(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, title, severity,
|
||||
early_warning_due, early_warning_sent_at,
|
||||
notification_due, notification_sent_at,
|
||||
final_report_due, final_report_sent_at
|
||||
FROM incidents
|
||||
WHERE organization_id = ? AND is_significant = 1
|
||||
AND status NOT IN ("closed", "post_mortem")',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
if ($r['early_warning_due'] && !$r['early_warning_sent_at']) {
|
||||
$out[] = $this->makeEvent('incidents', 'incident_early_warning',
|
||||
'Early Warning: ' . $r['title'], $r['early_warning_due'],
|
||||
'critical', 'incident', (int) $r['id'], '/incidents.html', $from, $to, false);
|
||||
}
|
||||
if ($r['notification_due'] && !$r['notification_sent_at']) {
|
||||
$out[] = $this->makeEvent('incidents', 'incident_notification',
|
||||
'Notifica CSIRT: ' . $r['title'], $r['notification_due'],
|
||||
'high', 'incident', (int) $r['id'], '/incidents.html', $from, $to, false);
|
||||
}
|
||||
if ($r['final_report_due'] && !$r['final_report_sent_at']) {
|
||||
$out[] = $this->makeEvent('incidents', 'incident_final_report',
|
||||
'Report finale: ' . $r['title'], $r['final_report_due'],
|
||||
'medium', 'incident', (int) $r['id'], '/incidents.html', $from, $to, false);
|
||||
}
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Revisione policy/procedure: policies.next_review_date (status != archived). */
|
||||
private function srcPolicies(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, title, next_review_date
|
||||
FROM policies
|
||||
WHERE organization_id = ? AND next_review_date IS NOT NULL
|
||||
AND status NOT IN ("archived")',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[] = $this->makeEvent('policies', 'policy_review',
|
||||
'Revisione policy: ' . $r['title'], $r['next_review_date'],
|
||||
'medium', 'policy', (int) $r['id'], '/policies.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Trattamenti rischio: risk_treatments.due_date JOIN risks (org via risks). */
|
||||
private function srcRiskTreatments(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT rt.id, rt.due_date, r.title AS risk_title
|
||||
FROM risk_treatments rt
|
||||
JOIN risks r ON r.id = rt.risk_id
|
||||
WHERE r.organization_id = ? AND rt.status IN ("planned", "in_progress")
|
||||
AND rt.due_date IS NOT NULL',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[] = $this->makeEvent('risk_treatments', 'risk_treatment',
|
||||
'Trattamento rischio: ' . $r['risk_title'], $r['due_date'],
|
||||
'medium', 'risk_treatment', (int) $r['id'], '/risks.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Revisione controlli: compliance_controls.next_review_date (non verificati/in corso). */
|
||||
private function srcControls(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, control_code, title, next_review_date, status
|
||||
FROM compliance_controls
|
||||
WHERE organization_id = ? AND next_review_date IS NOT NULL',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$title = trim((string) $r['control_code'] . ' ' . (string) $r['title']);
|
||||
$out[] = $this->makeEvent('compliance_controls', 'control_review',
|
||||
'Revisione controllo: ' . $title, $r['next_review_date'],
|
||||
'medium', 'compliance_control', (int) $r['id'], '/reports.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Non conformita': non_conformities.target_close_date (status non chiuso). */
|
||||
private function srcNonConformities(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, ncr_code, title, target_close_date
|
||||
FROM non_conformities
|
||||
WHERE organization_id = ? AND target_close_date IS NOT NULL
|
||||
AND status NOT IN ("closed", "cancelled")',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$title = trim((string) $r['ncr_code'] . ' ' . (string) $r['title']);
|
||||
$out[] = $this->makeEvent('non_conformities', 'nc_target_close',
|
||||
'Chiusura NC: ' . $title, $r['target_close_date'],
|
||||
'high', 'non_conformity', (int) $r['id'], '/reports.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Azioni correttive: capa_actions.due_date (status non completed/verified). */
|
||||
private function srcCapaActions(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, capa_code, title, due_date
|
||||
FROM capa_actions
|
||||
WHERE organization_id = ? AND due_date IS NOT NULL
|
||||
AND status NOT IN ("completed", "verified")',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$title = trim((string) $r['capa_code'] . ' ' . (string) $r['title']);
|
||||
$out[] = $this->makeEvent('capa_actions', 'capa_action',
|
||||
'Azione correttiva: ' . $title, $r['due_date'],
|
||||
'medium', 'capa_action', (int) $r['id'], '/reports.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Formazione: training_assignments.due_date (status assigned/in_progress). */
|
||||
private function srcTraining(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT ta.id, ta.due_date, tc.title, u.full_name
|
||||
FROM training_assignments ta
|
||||
JOIN training_courses tc ON tc.id = ta.course_id
|
||||
LEFT JOIN users u ON u.id = ta.user_id
|
||||
WHERE ta.organization_id = ? AND ta.status IN ("assigned", "in_progress")
|
||||
AND ta.due_date IS NOT NULL',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$who = $r['full_name'] ? (' - ' . $r['full_name']) : '';
|
||||
$out[] = $this->makeEvent('training', 'training_due',
|
||||
'Formazione: ' . $r['title'] . $who, $r['due_date'],
|
||||
'low', 'training', (int) $r['id'], '/training.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attivita' stakeholder (C5.2): stk_activities.due_date (preferita) e, se assente,
|
||||
* planned_date. Esclude annullate/completate. due_date "assolta" se completed.
|
||||
*/
|
||||
private function srcStkActivities(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, title, type, planned_date, due_date, status
|
||||
FROM stk_activities
|
||||
WHERE organization_id = ? AND status NOT IN ("cancelled")',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$done = ($r['status'] === 'completed');
|
||||
$date = $r['due_date'] ?: $r['planned_date'];
|
||||
if (!$date) { continue; }
|
||||
$label = ($r['due_date'] ? 'Scadenza attivita': 'Attivita pianificata') . ': ' . $r['title'];
|
||||
$out[] = $this->makeEvent('stk_activities', 'stakeholder_activity',
|
||||
$label, $date, 'medium', 'stk_activity', (int) $r['id'],
|
||||
'/stakeholder-activities.html', $from, $to, $done);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Scadenziario revisioni periodiche (A4 4.4): review_schedule.next_review_date. */
|
||||
private function srcReviewSchedule(int $orgId, string $from, string $to): array
|
||||
{
|
||||
// Esclude i tipi che hanno già una sorgente diretta dedicata (internal_audit via
|
||||
// srcInternalAudits, stakeholder_activity via srcStkActivities): altrimenti
|
||||
// comparirebbero due volte nel calendario. Restano gli scheduler generici.
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, title, entity_type, next_review_date, last_reviewed_at
|
||||
FROM review_schedule
|
||||
WHERE organization_id = ? AND next_review_date IS NOT NULL
|
||||
AND entity_type NOT IN ("internal_audit", "stakeholder_activity")',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[] = $this->makeEvent('review_schedule', 'review_schedule',
|
||||
$r['title'], $r['next_review_date'],
|
||||
'medium', 'review_schedule', (int) $r['id'], '/review-schedule.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Audit interni (Modulo A, opzionale): internal_audits.planned_date. */
|
||||
private function srcInternalAudits(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT id, code, title, planned_date, status
|
||||
FROM internal_audits
|
||||
WHERE organization_id = ? AND planned_date IS NOT NULL',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$done = in_array($r['status'], ['completed', 'cancelled'], true);
|
||||
$title = trim((string) $r['code'] . ' ' . (string) $r['title']);
|
||||
$out[] = $this->makeEvent('internal_audits', 'internal_audit',
|
||||
'Audit interno: ' . $title, $r['planned_date'],
|
||||
'medium', 'internal_audit', (int) $r['id'], '/internal-audits.html', $from, $to, $done);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/** Controlli periodici (mig.057, opzionale): periodic_controls.next_due_date. */
|
||||
private function srcPeriodicControls(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
"SELECT id, code, title, next_due_date, status
|
||||
FROM periodic_controls
|
||||
WHERE organization_id = ? AND next_due_date IS NOT NULL AND status = 'active'",
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$title = trim((string) $r['code'] . ' ' . (string) $r['title']);
|
||||
$out[] = $this->makeEvent('periodic_controls', 'periodic_control',
|
||||
'Controllo periodico: ' . $title, $r['next_due_date'],
|
||||
'medium', 'periodic_control', (int) $r['id'], '/controlli-periodici.html', $from, $to, false);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decisioni del riesame di direzione (Modulo B, opzionale):
|
||||
* management_review_decisions.due_date JOIN management_reviews per filtrare per org.
|
||||
*/
|
||||
private function srcManagementReviewDecisions(int $orgId, string $from, string $to): array
|
||||
{
|
||||
$rows = Database::fetchAll(
|
||||
'SELECT d.id, d.decision, d.due_date, d.status, mr.code AS review_code
|
||||
FROM management_review_decisions d
|
||||
JOIN management_reviews mr ON mr.id = d.review_id
|
||||
WHERE mr.organization_id = ? AND d.due_date IS NOT NULL',
|
||||
[$orgId]
|
||||
);
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$done = ($r['status'] === 'done');
|
||||
$txt = mb_substr((string) $r['decision'], 0, 120);
|
||||
$out[] = $this->makeEvent('management_reviews', 'management_review_decision',
|
||||
'Decisione riesame: ' . $txt, $r['due_date'],
|
||||
'medium', 'management_review_decision', (int) $r['id'],
|
||||
'/management-review.html', $from, $to, $done);
|
||||
}
|
||||
return array_values(array_filter($out));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// HELPER
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Normalizza una riga in evento di calendario. Ritorna null se la data
|
||||
* (normalizzata a Y-m-d) cade fuori dalla finestra [from,to]. $done forza
|
||||
* lo status a 'done' (scadenza gia' assolta/chiusa), altrimenti lo calcola
|
||||
* LIVE rispetto a oggi.
|
||||
*/
|
||||
private function makeEvent(
|
||||
string $source, string $type, string $title, ?string $rawDate,
|
||||
string $severity, string $entityType, int $entityId, string $link,
|
||||
string $from, string $to, bool $done
|
||||
): ?array {
|
||||
if (!$rawDate) { return null; }
|
||||
$date = substr((string) $rawDate, 0, 10); // DATETIME o DATE → Y-m-d
|
||||
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) { return null; }
|
||||
if ($date < $from || $date > $to) { return null; }
|
||||
|
||||
return [
|
||||
'source' => $source,
|
||||
'type' => $type,
|
||||
'title' => mb_substr($title, 0, 255),
|
||||
'date' => $date,
|
||||
'status' => $this->computeStatus($date, $done),
|
||||
'severity' => $severity,
|
||||
'entity_type' => $entityType,
|
||||
'entity_id' => $entityId,
|
||||
'link' => $link,
|
||||
];
|
||||
}
|
||||
|
||||
/** Stato LIVE: done | overdue | due_soon (<=N gg) | upcoming. */
|
||||
private function computeStatus(string $date, bool $done): string
|
||||
{
|
||||
if ($done) { return 'done'; }
|
||||
$today = new DateTimeImmutable('today');
|
||||
$d = DateTimeImmutable::createFromFormat('!Y-m-d', $date);
|
||||
if (!$d) { return 'upcoming'; }
|
||||
$diffDays = (int) $today->diff($d)->format('%r%a');
|
||||
if ($diffDays < 0) { return 'overdue'; }
|
||||
if ($diffDays <= self::DUE_SOON_DAYS) { return 'due_soon'; }
|
||||
return 'upcoming';
|
||||
}
|
||||
|
||||
/**
|
||||
* Risolve la finestra [from,to]. Default ampio: dal 1° giorno di 1 mese fa al
|
||||
* 1° giorno di 13 mesi avanti (copre tutto cio' che ha senso vedere in un
|
||||
* calendario annuale). Param from/to opzionali sovrascrivono (validati Y-m-d).
|
||||
*/
|
||||
private function resolveRange(): array
|
||||
{
|
||||
$from = $this->validDate($this->getParam('from'));
|
||||
$to = $this->validDate($this->getParam('to'));
|
||||
if (!$from) { $from = (new DateTimeImmutable('first day of this month'))->modify('-1 month')->format('Y-m-d'); }
|
||||
if (!$to) { $to = (new DateTimeImmutable('first day of this month'))->modify('+13 months')->format('Y-m-d'); }
|
||||
if ($from > $to) { [$from, $to] = [$to, $from]; }
|
||||
return [$from, $to];
|
||||
}
|
||||
|
||||
/** Filtro tipi: ?types=a,b,c → solo i tipi noti; null = nessun filtro. */
|
||||
private function resolveTypeFilter(): ?array
|
||||
{
|
||||
$raw = $this->getParam('types');
|
||||
if ($raw === null || $raw === '') { return null; }
|
||||
$parts = array_filter(array_map('trim', explode(',', (string) $raw)), static fn($t) => $t !== '');
|
||||
$valid = array_values(array_intersect($parts, self::KNOWN_TYPES));
|
||||
return $valid ?: null;
|
||||
}
|
||||
|
||||
/** Valida una data Y-m-d; ritorna la stringa normalizzata o null. */
|
||||
private function validDate($v): ?string
|
||||
{
|
||||
if ($v === null || $v === '') { return null; }
|
||||
$s = trim((string) $v);
|
||||
$dt = DateTime::createFromFormat('Y-m-d', $s);
|
||||
return ($dt && $dt->format('Y-m-d') === $s) ? $s : null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user