Files
nis2-agile/docs/TLS_DB_PREEQUIP_NIS2.md
T

9.1 KiB

TLS-in-transito DB — pre-equip NIS2 (VIGILE 2026-06-10)

✅ DECISIONE 2026-06-10: opzione C (DEFER). NIS2 resta 🔵 escluso dall'enforce / connessione socket clear (verde). Motivo: l'app fpm usa il MySQL host via UNIX socket = IPC locale, nessun transito di rete da cifrare → il TLS non aggiunge sicurezza reale. Scartate: A (GRANT @'135.181.149.254' = largo come @'%', smell su prodotto di compliance) e B (fix NAT per-uid, rischioso). Path pulito futuro (finestra pianificata, con restart MySQL): aggiungere 172.21.0.1 al bind_address host + grant nis2_user@'172.21.%' + switch app DB_HOST=172.21.0.1 + attivare il pre-equip — validando dal path fpm (www-data), NON da CLI (il source-bind-su-gateway per-uid può ripresentarsi anche su 172.21). Pre-equip codice già committato e pronto (4d89b1e).

🔴 AGGIORNAMENTO 2026-06-10 (post-test reale): nis2 NON è TLS-izzabile così com'è. Verificato in contesto php-fpm reale: l'app HTTP connette al MySQL HOST dfm-dev (8.0.46) via UNIX SOCKET (DB_HOST=localhost fallback, env non propagato ai worker www-data). TLS-in-transito NON si applica a un socket (IPC locale, nessun transito) e forzarlo rompe la connessione: attivando il flag .db_ssl_on → PDOException [2002] Cannot connect to MySQL using SSL → outage live (api-status database:error, login 500). Ripristinato con rm .db_ssl_on + kill -USR2 1. Conseguenze: (1) il pre-equip codice (Database::sslOptions()) resta corretto e inerte default-OFF, ma il flag NON va attivato per nis2; (2) per fare TLS l'app andrebbe prima spostata su TCP verso un endpoint di rete (e poi CA+enforce su QUEL server); (3) l'enforce REQUIRE SSL fatto sul container nis2-db è irrilevante per l'HTTP (che usa il socket host) → nis2 va ESCLUSO dall'enforce finché resta su socket. (4) ⚠️ Il docker exec nis2-app php mostra il path CLI (db→container), non quello dell'app — non usarlo per validare. Il resto del documento descrive il pattern PHP-PDO/CA generale (valido per prodotti che connettono via TCP); per nis2 vale l'avviso qui sopra.

Stato: codice pre-equipaggiato (gated, DEFAULT OFF). Restano gli step che richiedono accesso host/coordinamento VIGILE (distribuzione CA, attivazione, verifica sul path reale, enforce ALTER USER). Doc autoritativo del metodo: agile-services/docs/ANALISI_TLS_DB_STRUTTURALE_E_CENSIMENTO_2026_06_10.md (§11 pattern PHP-PDO/CA, §14 + vs array_merge, §16 lezione "codice baked / verifica sul path reale").

✅ Fatto (codice, in application/config/database.php)

  • Metodo Database::sslOptions(): gated, DEFAULT OFF. Si attiva SOLO con env DB_SSL=true oppure flag-file application/config/.db_ssl_on.
  • Opzioni quando attivo: PDO::MYSQL_ATTR_SSL_CA => <DB_SSL_CA|application/config/db-ca.pem> + PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false.
  • Fail-safe: se attivo ma il CA manca/non leggibile → ritorna [] (resta in chiaro come oggi) + error_log. Mai una connessione "a metà" (un VERIFY=false senza CA in PHP/mysqlnd connette IN CHIARO silenziosamente — §11).
  • Merge con + (non array_merge) per preservare le chiavi-intere PDO (§14).
  • .gitignore: application/config/.db_ssl_on + application/config/db-ca.pem (env-specifici, non committare).
  • Verifica locale (no DB): php -l ok; test via reflection: OFF → [] e base + [] === base (inerzia dimostrata, zero regressione); ON+CA-mancante → [] (fail-safe); ON+CA → chiavi 1009(SSL_CA)+1014(VERIFY=false).

Perché è sicuro lasciarlo in prod ora: con feature OFF (default) l'array opzioni passato a new PDO è identico a prima. Inoltre opcache.validate_timestamps=Off ⇒ il nuovo bytecode non è nemmeno servito finché non si fa kill -USR2 1.

⛔ Da fare (richiede SSH host + coordinamento VIGILE) — NON ancora eseguito

Topologia VERIFICATA via recon 2026-06-10 (sola lettura, code path reale Database::getInstance()): l'app nis2 si connette a un container DB: DB_HOST=db → 172.21.0.4, server fd63ec13e889:3306 MySQL 8.0.45, USER()=nis2_user@172.21.0.5 / CURRENT_USER()=nis2_user@%, Ssl_version=[] (chiaro). NON è il MySQL host (8.0.46) — questo CORREGGE la memoria project_db_topology. have_ssl=YES, require_secure_transport=OFF (host; il container db da verificare). ⚠️ Conseguenze: (a) il CA è quello del container db, non dell'host; (b) l'ALTER USER va sul container db; (c) application/ resta bind-mount in nis2-app (≠ baked §16, patch efficace sul runtime). ❓ Da riconciliare con Agile: le migrazioni storiche sono state applicate via mysql -h localhost (host) ma l'app usa il container db → confermare il target.

1. Recon (read-only)

# a quale MySQL si connette DAVVERO l'app + stato TLS attuale (atteso: Ssl_version vuoto = chiaro)
docker exec nis2-app php -r 'require "/var/www/nis2-agile/application/config/env.php";require "/var/www/nis2-agile/application/config/config.php";require "/var/www/nis2-agile/application/config/database.php";
$r=Database::fetchOne("SELECT @@hostname h,@@port p,USER() u");$s=Database::fetchOne("SHOW STATUS LIKE \"Ssl_version\"");
echo "DB_HOST=".DB_HOST." server=".$r["h"].":".$r["p"]." user=".$r["u"]." Ssl_version=[".($s["Value"]??"")."]\n";'

2. Distribuire il CA del MySQL al container (path leggibile, bind-mount)

⚠️ CA = quello del container DB db (172.21.0.4, MySQL 8.0.45) a cui l'app si connette davvero — NON dell'host. (Verificato 2026-06-10.) CA sbagliato ⇒ PDOException all'handshake (fail-closed). Operazione DB-side: di competenza Agile/VIGILE.

# nome reale del container db da identificare (quello a 172.21.0.4 sulla rete nis2)
DBC=$(docker ps --format '{{.Names}}' | grep -iE 'nis2.*db|^db$' | head -1)
docker exec "$DBC" cat /var/lib/mysql/ca.pem > /var/www/nis2-agile/application/config/db-ca.pem
chmod 0644 /var/www/nis2-agile/application/config/db-ca.pem
# bind-montato → visibile nel container nis2-app in /var/www/nis2-agile/application/config/db-ca.pem

3. Attivazione (no recreate)

touch /var/www/nis2-agile/application/config/.db_ssl_on   # gate flag-file (robusto anche se getenv non propaga in fpm)
docker exec nis2-app kill -USR2 1                          # reload graceful FPM (opcache) — OBBLIGATORIO

4. Verifica sul PATH REALE (lezione §16: non test sintetici)

docker exec nis2-app php -r 'require "/var/www/nis2-agile/application/config/env.php";require "/var/www/nis2-agile/application/config/config.php";require "/var/www/nis2-agile/application/config/database.php";
$s=Database::fetchOne("SHOW STATUS LIKE \"Ssl_version\"");echo "Ssl_version=[".($s["Value"]??"")."]\n";'  # atteso: TLSv1.3
curl -s -o /dev/null -w "%{http_code}\n" https://nis2.agile.software/api-status.php   # atteso 200 (app sana in TLS)

Se Ssl_version resta vuoto → il CA non è valido/raggiungibile: NON procedere all'enforce. Rollback step 6.

5. Enforce (SOLO con VIGILE + 3-sì, dopo che step 4 è verde)

-- riga utente da confermare con il censimento connessioni (host nis2_user@<host_reale>)
ALTER USER 'nis2_user'@'<host>' REQUIRE SSL;   -- => ssl_type=ANY (TLS obbligatorio)

Test deterministico: connessione no-TLS rifiutata (1045) / TLS ok / nis2.agile.software 200 / suite intatta. require_secure_transport resta OFF (enforce per-utente, mai global).

6. Rollback (reversibile ~10s)

rm -f /var/www/nis2-agile/application/config/.db_ssl_on && docker exec nis2-app kill -USR2 1
# se già enforced: ALTER USER 'nis2_user'@'<host>' REQUIRE NONE;

⚠️ Prerequisito all'enforce (censimento path connessione — ESEGUITO 2026-06-10)

Esito: il runtime di produzione (application/, 49 file) è coperto al 100% da Database::getInstance() → ZERO new PDO raw. Il patch SSL centrale copre tutto il traffico DB dell'app. Nessun mysqli nel codebase.

9 new PDO raw — tutti NON-production (bypassano il patch; da gestire prima dell'enforce SOLO se usano nis2_user):

File:riga Tipo Note
simulate-nis2.php:212,646,833 demo/sim spesso come root (fallback) → enforce su nis2_user NON li tocca
simulate-nis2-b2b.php:150 · simulate-nis2-big.php:178,491 demo/sim idem (dev-only)
scripts/import-feedback-to-nexus.php:112,114 cross-DB (NIS2↔Nexus) ⚠️ se eseguito DOPO l'enforce e connette come nis2_user → fallisce. Patchare prima (helper SSL + +, pattern WMS §14).
public/test-runner.php:529 test harness basso rischio funzionale; ma test-runner in public/ = odore di sicurezza (valutare rimozione/restrizione, fuori scope TLS)

Raccomandazione: l'enforce nis2_user è sicuro per l'app; prima di abilitarlo verificare (a) con che utente si connettono import-feedback-to-nexus.php e test-runner.php; (b) se è nis2_user, patchare quei raw con un helper application/config/db-ssl-opts.php require-abile ($opts + require ...). Gli script demo come root sono ininfluenti. Censimento via agente; ri-verificare con un census 0 clear reale prima dell'ALTER.