CONTEXT/runbook aggiornati con le conclusioni 2026-06-10 sera: - L'app HTTP (php-fpm) usa il MySQL HOST via UNIX SOCKET (localhost), NON il container (la "scoperta container" precedente era basata sul path CLI = falso positivo). - TLS su socket non si applica e rompe (outage rientrato: rm flag + USR2). - Switch socket→TCP via vault-net (172.30.0.1): CLI(root) OK ma fpm(www-data) esce con source=gateway → masquerade → 135.x → 1045. Switch NON eseguito (sarebbe outage). - Lezione: validare SEMPRE sul path fpm reale (diag servito da php-fpm), mai CLI/docker exec. - Pre-equip codice resta committato e inerte (flag OFF); attivabile quando il path TCP fpm sarà grantabile (bind 172.21 in finestra con restart MySQL). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
87 lines
8.3 KiB
Markdown
87 lines
8.3 KiB
Markdown
# TLS-in-transito DB — pre-equip NIS2 (VIGILE 2026-06-10)
|
|
|
|
> 🔴 **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)
|
|
```bash
|
|
# 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.**
|
|
```bash
|
|
# 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)
|
|
```bash
|
|
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)
|
|
```bash
|
|
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)
|
|
```sql
|
|
-- 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)
|
|
```bash
|
|
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`.
|