Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d98d52ed58 | ||
|
|
4d89b1e04b |
@@ -41,3 +41,7 @@ docker/data/
|
|||||||
|
|
||||||
# Chiavi SSH effimere — NON committare MAI chiavi private
|
# Chiavi SSH effimere — NON committare MAI chiavi private
|
||||||
.ssh-temp/
|
.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
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ class Database
|
|||||||
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
|
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 {
|
try {
|
||||||
self::$instance = new PDO($dsn, DB_USER, DB_PASS, $options);
|
self::$instance = new PDO($dsn, DB_USER, DB_PASS, $options);
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
@@ -55,6 +60,43 @@ class Database
|
|||||||
return self::$instance;
|
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 [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$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
|
* Esegue una query con parametri
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,6 +2,40 @@
|
|||||||
|
|
||||||
> Il 2026-05-29 ci sono state DUE sessioni: **pomeriggio** e **mattina** (TRPG). Il 2026-05-30 sessione lunga: gap competitivi P1/P2/P3 + connettori + review multi-agente + fix.
|
> Il 2026-05-29 ci sono state DUE sessioni: **pomeriggio** e **mattina** (TRPG). Il 2026-05-30 sessione lunga: gap competitivi P1/P2/P3 + connettori + review multi-agente + fix.
|
||||||
|
|
||||||
|
## 2026-06-10 — TLS-DB pre-equip (VIGILE) + SCOPERTA split DB host/container
|
||||||
|
|
||||||
|
> Task da CLAUDE.md (cima, "VIGILE 2026-06-10"): pre-equip PHP-PDO/CA per TLS-in-transito DB, gated default-OFF, **senza** enforce (l'`ALTER USER` è di VIGILE). Eseguito con precisione chirurgica + 2 agenti background di review.
|
||||||
|
|
||||||
|
### Fatto (commit `4d89b1e`)
|
||||||
|
- **`application/config/database.php`** → `Database::sslOptions()`: TLS gated (env `DB_SSL=true` **o** flag-file `application/config/.db_ssl_on`), **DEFAULT OFF**. `PDO::MYSQL_ATTR_SSL_CA` + `VERIFY_SERVER_CERT=false`. **Fail-safe**: CA assente → resta in chiaro + `error_log` (un VERIFY=false senza CA in mysqlnd connette IN CHIARO silenziosamente — doc §11). Merge con `+` (preserva chiavi-intere PDO, §14).
|
||||||
|
- **Verificato locale** (no DB): `php -l` + reflection test → OFF ⇒ `base + [] === base` (**inerzia provata**, zero regressione); ON+CA-mancante ⇒ `[]`; ON+CA ⇒ chiavi `1009`/`1014`.
|
||||||
|
- **2 agenti background**: (a) censimento connessioni → **runtime `application/` coperto 100%** da `Database::getInstance()` (0 raw PDO); 9 `new PDO` raw solo in NON-prod (`simulate-*.php` come root → ininfluenti; `scripts/import-feedback-to-nexus.php:112/114` cross-DB e `public/test-runner.php:529` da gestire prima dell'enforce SE usano nis2_user). (b) review adversariale → **SICURO default-OFF, nessun bug**.
|
||||||
|
- `.gitignore`: `application/config/.db_ssl_on` + `db-ca.pem`. Runbook completo: **`docs/TLS_DB_PREEQUIP_NIS2.md`**.
|
||||||
|
|
||||||
|
### 🔴 SCOPERTA CRITICA: split DB host vs container (confermata da VIGILE)
|
||||||
|
- La recon (`docker exec nis2-app php -r 'Database::getInstance...'`) ha rivelato: **l'app nis2 usa il DB CONTAINER** (`DB_HOST=db → 172.21.0.4`, server `fd63ec13e889`, **MySQL 8.0.45**), **NON il MySQL host** (8.0.46). Questo **CORREGGE la memoria `project_db_topology`** (diceva "host"). `Ssl_version=[]` (chiaro), `nis2_user@%`.
|
||||||
|
- **VIGILE ha confermato lo split + fixato**: il cron `sso-password-sync` aggiornava le **copie host MORTE** di `nis2_agile_db` (+ lg231/wms) mentre le app usano i container → password SSO non arrivavano. Fixato (sync sul container reale via `docker exec`). taxai resta su host (la sua app usa davvero l'host). trpg/sustainai intatti.
|
||||||
|
- **Conseguenza sul mio lavoro 2026-05-29**: migrazioni **020/021/022** erano state applicate via `mysql -h localhost` = copia host morta → **mancanti nel container** → v1.7.0 NON funzionava in prod. **VIGILE le ha ri-applicate al container** (backup+idempotenti) → verificato `assets.relevance_score`/`incidents.nis2_incident_type`/`incident_pir` ✅. Schema v1.7.0 ora corretto in prod.
|
||||||
|
- ⚠️ Per il TLS: **CA + `ALTER USER nis2_user@% REQUIRE SSL` vanno sul CONTAINER `db`** (non host). Comandi corretti nel runbook.
|
||||||
|
|
||||||
|
### Aperti (in mano a VIGILE — DB = solo Agile)
|
||||||
|
1. **Benassati/org129**: assente nel container (host id=4, SSO #232); membership org129=0 sia host sia container. Stato voluto comunicato: **super_admin (NO declassare)** + **org_admin membro org 129** nel CONTAINER, SSO-linked. NB `add-agile-user.php` crea i nuovi come `org_admin` → NON usarlo as-is per lui; probabile gap provisioning SSO nel container. VIGILE inserisce da contesto `DB_HOST=db`.
|
||||||
|
2. **Riconciliazione migrazioni 023→036**: applicate (sessioni 05-30/05-31) via `mysql -h localhost` = path host → **da verificare/ri-applicare sul container** come 020-022. Sentinelle per-migrazione fornite a VIGILE (tabella `docs/TLS_DB_PREEQUIP_NIS2.md` + chat). Offerto script SQL unico di reconciliation (information_schema) — da generare se richiesto.
|
||||||
|
3. **Enforce TLS**: di VIGILE, con 3-sì, dopo CA distribuito sul container + verifica sul path reale (`Ssl_version=TLSv1.3`, lezione §16 "no test sintetici").
|
||||||
|
|
||||||
|
### Accesso
|
||||||
|
Chiave SSH effimera in `.ssh-temp/` (rigenerata dall'utente, validità ~3h). Devenv NON raggiunge il DB prod direttamente: solo via SSH host (`root@172.18.0.1` o `135.181.149.254`). Push Gitea bloccato sul token (`git-login` interattivo) — commit locali, push a cura host/VIGILE.
|
||||||
|
|
||||||
|
### 🔴🔴 AGGIORNAMENTO SERA — la "scoperta container" era SBAGLIATA; verità: fpm→HOST via SOCKET
|
||||||
|
> Le conclusioni "container DB" qui sopra sono **superate**. Verificato col **path fpm reale** (diag PDO servito da php-fpm, NON `docker exec` che è CLI):
|
||||||
|
- **L'app HTTP (php-fpm/www-data) usa il MySQL HOST `dfm-dev` (8.0.46) via UNIX SOCKET** (`localhost`, env non propagato ai worker). Lo schema v1.7.0 **è presente sul host** (le migrazioni 020/021/022 del 29/5 via `mysql -h localhost` erano sul DB GIUSTO). Il `docker exec nis2-app php` (`DB_HOST=db`→container 8.0.45) è solo il path **CLI**, secondario. Memoria `project_db_topology` **ri-corretta** (fpm→host socket; CLI→container).
|
||||||
|
- **OUTAGE causato + risolto**: VIGILE ha attivato CA+flag+`REQUIRE SSL` (sul container) → ma **TLS su UNIX socket non si applica e ROMPE** (`[2002] Cannot connect to MySQL using SSL`) → app giù (login 500). **Fix**: `rm application/config/.db_ssl_on` + `kill -USR2 1` → ripristino. VIGILE poi: **NIS2 🔵 escluso dall'enforce (caso socket), container revertito a baseline, app verde**.
|
||||||
|
- **Tentativo switch socket→TCP (per allinearlo agli altri)**: VIGILE ha aperto un path TCP via **vault-net** (`172.30.0.1`, host MySQL già in ascolto lì) + grant `nis2_user@'172.30.%'`. **MA il path fpm reale FALLISCE**: prova affiancata 3+3 → **CLI** (root) `getsockname=172.30.0.9` → `nis2_user@172.30.0.9` OK; **fpm** (www-data) `getsockname=**172.30.0.1**` → MySQL vede **135.181.149.254** (IP pubblico) → **1045**. Causa probabile: **policy-routing/NAT per-uid** (www-data instradato via gateway). Il collaudo "OK" di VIGILE era **CLI = falso positivo**. → **switch NON eseguito** (sarebbe outage). Palla a VIGILE: grant `@'135.181.149.254'` (quick) o fix routing per-uid; poi ri-probo da **fpm** prima di switchare.
|
||||||
|
- **Stato finale**: app **verde** (socket clear), pre-equip TLS **inerte** (flag OFF), `db-ca.pem` presente ma inerte. Patch codice committato (`4d89b1e`), corretto e pronto; **non attivabile finché l'app è su socket / il TCP fpm non è grantabile**.
|
||||||
|
- ⚠️ **Lezione ricorrente della sera**: validare SEMPRE sul path **fpm reale** (diag servito da php-fpm: login/business), **mai** `docker exec`/CLI né `/health` — CLI e fpm divergono (DB_HOST, source IP, opcache).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-06-01 — Modulo Gap Analysis ACN + review + landing (prod v1.13.0, ahead 0)
|
## 2026-06-01 — Modulo Gap Analysis ACN + review + landing (prod v1.13.0, ahead 0)
|
||||||
|
|
||||||
> ⚠️ EMAIL ancora DISABILITATE (`EMAIL_SENDING_ENABLED=false`, solo dati demo). Vedi sezione 2026-05-31 + memoria `project_email_killswitch`.
|
> ⚠️ EMAIL ancora DISABILITATE (`EMAIL_SENDING_ENABLED=false`, solo dati demo). Vedi sezione 2026-05-31 + memoria `project_email_killswitch`.
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# 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`.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* One-off idempotente: aggiunge un utente all'organizzazione Agile Technology SRL (org 129).
|
||||||
|
* Sicuro/rilanciabile: se l'email esiste NON duplica; aggiunge solo la membership mancante.
|
||||||
|
* Eseguire sull'HOST Hetzner: php /var/www/nis2-agile/scripts/add-agile-user.php
|
||||||
|
*/
|
||||||
|
if (!defined('BASE_PATH')) define('BASE_PATH', dirname(__DIR__));
|
||||||
|
if (!defined('APP_PATH')) define('APP_PATH', BASE_PATH . '/application');
|
||||||
|
require_once APP_PATH . '/config/env.php';
|
||||||
|
require_once APP_PATH . '/config/config.php';
|
||||||
|
require_once APP_PATH . '/config/database.php';
|
||||||
|
|
||||||
|
// ── Parametri richiesta ──────────────────────────────────────────────
|
||||||
|
const EMAIL = 'cristiano.benassati@gmail.com';
|
||||||
|
const PASSWORD = 'Silvia1978!@';
|
||||||
|
const FULL_NAME = 'Cristiano Benassati';
|
||||||
|
const ORG_ID = 129; // Agile Technology SRL
|
||||||
|
const ORG_ROLE = 'org_admin'; // ruolo nell'organizzazione
|
||||||
|
const GLOBAL_ROLE= 'org_admin'; // ruolo globale (enum users.role)
|
||||||
|
const FIRM_ID = null; // null = NESSUN accesso firm-wide (least privilege). Mettere 1 per parità con Fattori/Tagliavini.
|
||||||
|
const IS_PRIMARY = 0;
|
||||||
|
|
||||||
|
$pdo = Database::getInstance();
|
||||||
|
$report = [];
|
||||||
|
|
||||||
|
// 1) Utente esiste già?
|
||||||
|
$u = Database::fetchOne('SELECT id, email, full_name, role, is_active, sso_identity_id, consulting_firm_id FROM users WHERE email = ?', [EMAIL]);
|
||||||
|
|
||||||
|
if ($u) {
|
||||||
|
$report['user'] = "ESISTE GIÀ (id={$u['id']}, role={$u['role']}, " . ($u['sso_identity_id'] ? "SSO#{$u['sso_identity_id']}" : 'locale') . ')';
|
||||||
|
$userId = (int) $u['id'];
|
||||||
|
// Aggiorna password SOLO se locale (su utenti SSO verrebbe sovrascritta dal cron sync)
|
||||||
|
if (empty($u['sso_identity_id'])) {
|
||||||
|
$hash = password_hash(PASSWORD, PASSWORD_DEFAULT);
|
||||||
|
Database::query('UPDATE users SET password_hash = ?, is_active = 1 WHERE id = ?', [$hash, $userId]);
|
||||||
|
$report['password'] = 'aggiornata (utente locale)';
|
||||||
|
} else {
|
||||||
|
$report['password'] = 'NON toccata (utente SSO: la password è gestita dal sync SSO)';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$hash = password_hash(PASSWORD, PASSWORD_DEFAULT);
|
||||||
|
$userId = Database::insert('users', [
|
||||||
|
'email' => EMAIL,
|
||||||
|
'password_hash' => $hash,
|
||||||
|
'full_name' => FULL_NAME,
|
||||||
|
'role' => GLOBAL_ROLE,
|
||||||
|
'consulting_firm_id' => FIRM_ID,
|
||||||
|
'is_active' => 1,
|
||||||
|
'email_verified_at' => date('Y-m-d H:i:s'),
|
||||||
|
'password_version' => 1,
|
||||||
|
]);
|
||||||
|
$report['user'] = "CREATO (id={$userId}, role=" . GLOBAL_ROLE . ', locale)';
|
||||||
|
$report['password'] = 'impostata';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica hash
|
||||||
|
$chk = Database::fetchOne('SELECT password_hash FROM users WHERE id = ?', [$userId]);
|
||||||
|
$report['password_verify'] = password_verify(PASSWORD, $chk['password_hash']) ? 'OK ✓' : 'FALLITO ✗';
|
||||||
|
|
||||||
|
// 2) Membership org 129
|
||||||
|
$m = Database::fetchOne('SELECT id, role FROM user_organizations WHERE user_id = ? AND organization_id = ?', [$userId, ORG_ID]);
|
||||||
|
if ($m) {
|
||||||
|
$report['membership'] = "già presente (role={$m['role']})";
|
||||||
|
} else {
|
||||||
|
Database::insert('user_organizations', [
|
||||||
|
'user_id' => $userId,
|
||||||
|
'organization_id' => ORG_ID,
|
||||||
|
'role' => ORG_ROLE,
|
||||||
|
'is_primary' => IS_PRIMARY,
|
||||||
|
]);
|
||||||
|
$report['membership'] = 'AGGIUNTA (org 129, role=' . ORG_ROLE . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== Risultato ===\n";
|
||||||
|
foreach ($report as $k => $v) printf(" %-16s %s\n", $k . ':', $v);
|
||||||
|
|
||||||
|
// 3) Stato finale membri org 129
|
||||||
|
echo "\n=== Membri attuali org 129 ===\n";
|
||||||
|
$rows = Database::fetchAll(
|
||||||
|
'SELECT uo.user_id, u.full_name, u.email, uo.role, uo.is_primary
|
||||||
|
FROM user_organizations uo JOIN users u ON u.id = uo.user_id
|
||||||
|
WHERE uo.organization_id = ? ORDER BY uo.is_primary DESC, uo.user_id', [ORG_ID]);
|
||||||
|
foreach ($rows as $r) {
|
||||||
|
printf(" #%d %-22s %-32s %-14s %s\n", $r['user_id'], $r['full_name'], $r['email'], $r['role'], $r['is_primary'] ? '(primario)' : '');
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user