From 7b9b8b308d61054690563102e1e2c659fe43015e Mon Sep 17 00:00:00 2001 From: User Date: Fri, 21 Aug 2026 03:58:50 +0200 Subject: [PATCH] docs: explain per-installation DWH access --- docs/guida-utente.md | 2 + docs/index.md | 2 + docs/install/dwh-auth-client-enrollment.md | 71 +++++++++ docs/install/dwh-auth-server.md | 136 ++++++++++++++++++ docs/install/dwh-auth-tls.md | 49 +++++++ docs/install/local-workspace-registry.md | 4 + docs/install/psd-workspace-setup.md | 2 + docs/install/server-workspace-registry.md | 4 + docs/operations/psd-dwh-auth-rollout.md | 39 +++++ docs/testing/dwh-auth-manual-acceptance.md | 39 +++++ .../psd-dwh-auth-rollout-report-template.md | 40 ++++++ mkdocs.yml | 7 + scripts/test-verify-dwh-auth-docs.sh | 77 ++++++++++ scripts/verify-dwh-auth-docs.sh | 79 ++++++++++ 14 files changed, 551 insertions(+) create mode 100644 docs/install/dwh-auth-client-enrollment.md create mode 100644 docs/install/dwh-auth-server.md create mode 100644 docs/install/dwh-auth-tls.md create mode 100644 docs/operations/psd-dwh-auth-rollout.md create mode 100644 docs/testing/dwh-auth-manual-acceptance.md create mode 100644 docs/testing/evidence/psd-dwh-auth-rollout-report-template.md create mode 100755 scripts/test-verify-dwh-auth-docs.sh create mode 100755 scripts/verify-dwh-auth-docs.sh diff --git a/docs/guida-utente.md b/docs/guida-utente.md index 06e05a83..c108dc16 100644 --- a/docs/guida-utente.md +++ b/docs/guida-utente.md @@ -277,6 +277,8 @@ le fasi e conferma ai gate: il modello proporrà lo schema-linking (tabelle/colo ## Dove trovare i dettagli tecnici +Per l'accesso DWH REST, la chiave è per installazione e vale solo per `rest_api`: il server PSD rimane `postgres_direct` e `ssh_tunnel` non usa questa chiave. Vedere [guida server DWH](install/dwh-auth-server.md), [enrollment client](install/dwh-auth-client-enrollment.md), [TLS](install/dwh-auth-tls.md) e [runbook PSD](operations/psd-dwh-auth-rollout.md). + - Contratto CLI: `docs/contracts/workspace-preprocessing-cli.md` - Contratto `.tht-dwh`: `docs/contracts/tht-dwh.md` - Evidence v3: `docs/contracts/workspace-evidence-v3.md` diff --git a/docs/index.md b/docs/index.md index 89682d8b..fb513846 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,6 +14,8 @@ Per installare l'applicazione in Docker nei quattro contesti operativi, usando i `compose.yaml`, l'overlay locale/server e il bundle di secret montato: [Installazione Docker nei quattro contesti](installazione-docker-4-contesti.md). +Per il DWH REST con una chiave revocabile per installazione: [guida server](install/dwh-auth-server.md), [enrollment client](install/dwh-auth-client-enrollment.md), [TLS](install/dwh-auth-tls.md) e [runbook PSD](operations/psd-dwh-auth-rollout.md). Il componente resta separato dallo stack Compose ThothII. + ## Considerazioni Generali Note operative e di configurazione che non sono specifiche del dominio ThothII ma riguardano l'ambiente di sviluppo condiviso con altri progetti — ad esempio come Pi (il coding agent) risolve i modelli a livello built-in, utente e progetto. Parte da qui: [Configurazione dei modelli in Pi](general/pi-configuration.md). diff --git a/docs/install/dwh-auth-client-enrollment.md b/docs/install/dwh-auth-client-enrollment.md new file mode 100644 index 00000000..43bb4dfb --- /dev/null +++ b/docs/install/dwh-auth-client-enrollment.md @@ -0,0 +1,71 @@ +# Enrollment client per DWH REST + +La credenziale `dwh-auth` appartiene a una installazione ThothII, non a una persona. Serve solo se +il trasporto è `rest_api`; `postgres_direct` e `ssh_tunnel` non la usano. + +| Trasporto | Chiave `dwh-auth` | Materiale locale | +| --- | --- | --- | +| `rest_api` | Sì, una per installazione. | URL HTTPS, `API_KEY_FILE`, eventuale `TLS_CA_FILE`. | +| `postgres_direct` | No. | Credenziali PostgreSQL e TLS PostgreSQL. | +| `ssh_tunnel` | No. | Credenziali PostgreSQL e materiali SSH; è diagnostico-only nel runtime corrente. | + +Il ThothII server PSD resta `postgres_direct` read-only. Il Mac PSD e le installazioni remote +usano `rest_api`; non introdurre un tunnel SSH per aggirare REST. + +## Prerequisiti + +Ricevere chiave e CA, se necessaria, attraverso canali protetti separati. Confermare fuori banda il +fingerprint TLS prima dell'uso: [guida TLS](dwh-auth-tls.md). Conservare la chiave nel vault o in +un file protetto, mai Git, `.env` con il valore, argv, ambiente, log o evidenze. Annotare solo ID +pubblico. + +## Percorso GUI: vault dell'installazione + +1. In **Workspace management**, eseguire **Update workspace repository** se necessario e + selezionare il workspace. +2. Scegliere `rest_api`, controllare URL/trust locali e usare **Validate workspace**. +3. Inserire la chiave nel campo write-only **Data warehouse API key**, poi **Save runtime secrets**. + La GUI la conserva nel vault cifrato `workspace-secrets`, non la rileggere né la restituisce. +4. Eseguire **Test connections**. Il controllo innocuo è `/rpc/ping`: atteso 2xx e + database/schema dichiarati. +5. Comunicare al server solo ID pubblico, timestamp e risultato. **Forget** rimuove il valore e va + usato soltanto dopo conferma di sostituzione o revoca. + +## Percorso headless: binding reale + +`API_KEY_FILE` significa che il valore è nel file, non nella variabile. Nel file PSD non tracciato +`workspace-bindings.env` i binding effettivi sono: + +```dotenv +THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api +THT_WS_PSD_CLINICAL_DWH_BASE_URL=https://supabase-aritmolab.policlinicosandonato.it/dwh/ +THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE=/run/secrets/psd-clinical-dwh-api-key +THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE=/run/secrets/psd-clinical-dwh-ca.pem +``` + +Nel file `operator.env` non tracciato, ogni suffisso `_SOURCE` indica solo il percorso assoluto del +file protetto di origine. Il comando genera un override non tracciato che monta quei file nel +`core`; non installa né avvia `dwh-auth` con Compose: + +```bash +bash scripts/generate-connector-secrets-override.sh \ + --bindings-env /absolute/protected/workspace-bindings.env \ + --operator-env /absolute/protected/operator.env \ + --output /absolute/protected/connector-secrets.override.yaml \ + --service core --role dwh +``` + +La chiave sorgente è un file regolare `0600` per il solo account autorizzato. Per altri workspace, +sostituire `PSD_CLINICAL` con ID immutabile maiuscolo (trattini in underscore). Vedere anche il +[protocollo diagnostico](../workspace-diagnostic-protocol.md). + +## Ping, rotazione e revoca + +Usare solo **Test connections** su `/rpc/ping`: successo è 2xx con TLS verificato; il server +conferma l'ID con `key status`. Durante rotazione, ricevere nuova generazione, aggiornare vault o +file `API_KEY_FILE`, ripetere ping, attendere osservazione e far revocare la precedente. Dopo la +revoca: nuova positiva, precedente `401`. + +`401` non distingue chiave assente, scaduta o revocata. `503` è un guasto fail-closed di servizio, +socket o registro: non usare connessione diretta e non ridurre TLS. Non riattivare una chiave +revocata. Il percorso PSD è nel [runbook](../operations/psd-dwh-auth-rollout.md). diff --git a/docs/install/dwh-auth-server.md b/docs/install/dwh-auth-server.md new file mode 100644 index 00000000..766e56c0 --- /dev/null +++ b/docs/install/dwh-auth-server.md @@ -0,0 +1,136 @@ +# `dwh-auth`: guida server + +`dwh-auth` autentica la route REST `/dwh/` con una chiave per installazione. È un componente Linux +opzionale e server-side: usa `systemd`, non `tht` né Docker Compose, non legge risultati clinici e +non si collega a PostgreSQL. La chiave serve solo a `rest_api`; `postgres_direct` e `ssh_tunnel` +non la usano. + +## Prerequisiti e confini + +- Usare un checkout revisionato, Docker per la build e un operatore autorizzato sul server DWH. +- Una chiave identifica un'installazione, non una persona. L'`installation-id` è unico, non + personale e senza dati clinici. +- Chiavi, digest, file di consegna e backup restano in file protetti: mai Git, argv, variabili + d'ambiente, log, JSON pubblico o evidenze. +- Preparare backup e rollback prima di Nginx. Installare il servizio non autorizza una modifica + della route pubblica. + +## Percorsi, owner e mode + +| Oggetto | Percorso | Owner e mode | +| --- | --- | --- | +| Binario | `/usr/local/sbin/dwh-auth` | `root:root`, `0755` | +| Unit | `/etc/systemd/system/dwh-auth.service` | `root:root`, `0644` | +| Tmpfiles | `/usr/lib/tmpfiles.d/dwh-auth.conf` | `root:root`, `0644` | +| Registro, `active`, `revoked` | `/var/lib/dwh-auth/` | `root:dwh-auth`, `2750` | +| Lock | `/var/lib/dwh-auth/.writer.lock` | `root:dwh-auth`, `0640` | +| Record | `/var/lib/dwh-auth/{active,revoked}/.json` | `root:dwh-auth`, `0640` | +| Socket runtime | `/run/dwh-auth/verify.sock` | `dwh-auth:www-data`, `0660` | +| Consegne e backup | `/root/dwh-auth-provision/` | directory `root:root` `0700`, file `0600` | + +Il record conserva un digest interno (`secret_sha256`) e metadati, mai la chiave in chiaro. Non +leggere, stampare, calcolare o mettere quel digest in una prova operativa. + +## Build, installazione e avvio + +Costruire dal commit congelato e registrare solo checksum del binario e SHA sorgente: + +```bash +cd /srv/thothii/app +bash scripts/build-dwh-auth.sh --output /tmp/dwh-auth-release +sha256sum /tmp/dwh-auth-release/dwh-auth-linux-amd64 +``` + +Scegliere l'architettura corretta. Il template PSD usa il gruppo Nginx `www-data`; confermarlo +prima dell'installazione su un host diverso. + +```bash +sudo groupadd --system dwh-auth +sudo useradd --system --no-create-home --shell /usr/sbin/nologin --gid dwh-auth dwh-auth +sudo install -o root -g root -m 0755 /tmp/dwh-auth-release/dwh-auth-linux-amd64 /usr/local/sbin/dwh-auth +sudo install -o root -g root -m 0644 deploy/dwh-auth/dwh-auth.service /etc/systemd/system/dwh-auth.service +sudo install -o root -g root -m 0644 deploy/dwh-auth/dwh-auth.tmpfiles.conf /usr/lib/tmpfiles.d/dwh-auth.conf +sudo install -d -o root -g root -m 0700 /root/dwh-auth-provision +sudo systemd-tmpfiles --create /usr/lib/tmpfiles.d/dwh-auth.conf +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth check +sudo systemd-analyze verify /etc/systemd/system/dwh-auth.service +sudo systemctl daemon-reload +sudo systemctl enable --now dwh-auth +sudo systemctl status dwh-auth --no-pager +``` + +Controllare i mode con `stat`. Il servizio apre il registro in sola lettura e crea solo il socket. +Non creare JSON, lock o socket a mano: oggetti insicuri devono fallire chiusi. + +## Check, elenco e stato + +Usare sempre un root assoluto. Questi comandi espongono solo ID pubblici, stato, date e scadenza: + +```bash +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth check +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key list --json +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key status --key-id --json +``` + +Un errore di integrità, permessi, symlink o JSON malformato richiede ripristino da backup protetto, +non una correzione manuale del record. + +## Creazione, consegna, scadenza e revoca + +Il comando crea la chiave una volta in un nuovo file assoluto `0600`; stdout contiene solo ID +pubblico, installazione e percorso. Il file di output non deve esistere. + +```bash +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key create \ + --installation-id \ + --description \ + --output /root/dwh-auth-provision/-.key +``` + +Aggiungere `--expires-at ` solo se la policy impone una scadenza; il default è nessuna +scadenza. Consegnare il file solo con vault aziendale, secret manager, MDM o trasferimento +autenticato ristretto. Mai email, chat, ticket, `cat` o copia-incolla. Il client conferma ID +pubblico e ping, poi il materiale temporaneo viene rimosso secondo policy. + +L'import legacy è temporaneo PSD: il file sorgente è già `root:root` `0600` e non viene mai letto o +stampato dall'operatore. + +```bash +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key import \ + --legacy-raw --installation-id legacy-shared \ + --from-file /root/dwh-auth-provision/legacy-shared.key +``` + +Per rotare: creare seconda generazione, consegnarla, configurarla e provare `/rpc/ping`; confermare +l'ID pubblico; attendere l'osservazione; poi revocare la precedente e provare nuova=successo, +precedente=401. + +```bash +sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key revoke \ + --key-id --reason +``` + +La revoca non è annullabile e un ID revocato non si ricrea. + +## Backup, rollback e disinstallazione + +Prima di mutare, creare un archivio cifrato e protetto del registro e copie protette delle sole +configurazioni coinvolte. L'archivio contiene digest, quindi è riservato: evidenza ammessa solo +percorso, owner, mode, timestamp e checksum dell'archivio. Il rollback dual-key ripristina la route +e il servizio revisionati, esegue `nginx -t` e fa reload solo autorizzato; non ripristina chiavi +revocate, PostgreSQL, sessioni legacy, indici Qdrant o cache Ollama. + +La disinstallazione richiede autorizzazione esplicita, client REST migrati/revocati e rollback non +più necessario. Solo allora disabilitare l'unità; conservare registro e backup fino alla retention +approvata. Non inserire `dwh-auth` in Compose o in `tht start`/`tht stop`. + +## Troubleshooting + +| Sintomo | Interpretazione e azione | +| --- | --- | +| `401` | Chiave assente, malformata, sconosciuta, scaduta, revocata o errata. Verificare trasporto, ID pubblico e consegna; non cercare dettagli nel messaggio. | +| `503` | Servizio, socket o registro non disponibile/sicuro. Controllare `systemctl`, socket, mode e `check`; ripristinare il backup approvato. | +| `check` fallisce | Integrità del registro non valida. Fermare le scritture, preservare stato e ripristinare; non editare JSON. | +| TLS fallisce | CA o SAN non validi. Seguire [TLS](dwh-auth-tls.md), senza bypass. | + +Per il rollout PSD con i due gate separati vedere il [runbook PSD](../operations/psd-dwh-auth-rollout.md). diff --git a/docs/install/dwh-auth-tls.md b/docs/install/dwh-auth-tls.md new file mode 100644 index 00000000..2ec5c775 --- /dev/null +++ b/docs/install/dwh-auth-tls.md @@ -0,0 +1,49 @@ +# TLS per DWH REST + +La chiave DWH è accettabile solo sopra TLS verificato. Un errore `401` o `503` non autorizza mai a +ridurre la verifica del certificato. + +## Stato PSD + +L'origine REST PSD corrente usa il certificato self-issued/private di Nginx. Il SAN copre +`supabase-aritmolab.policlinicosandonato.it`, l'origine `.it` approvata, e non copre un dominio +`.com`. Non usare `.com` finché non è incluso esplicitamente nel SAN. + +Chi non dispone già di trust equivalente approvato riceve la CA separatamente e configura +`TLS_CA_FILE`. La CA non è una credenziale, ma la sua integrità è un confine di sicurezza: fuori da +Git e non scrivibile da utenti non autorizzati. + +## Fingerprint fuori banda + +Calcolare localmente il fingerprint del file ricevuto: + +```bash +openssl x509 -noout -fingerprint -sha256 -in /absolute/protected/psd-dwh-ca.pem +``` + +Confrontarlo con il responsabile autorizzato tramite un canale indipendente dalla consegna (vault +aziendale o canale telefonico verificato). Nell'evidenza registrare solo conferma, approvatore e +timestamp; mai corpo certificato, fingerprint completo o output grezzo. + +## Binding e ping + +Il binding headless PSD effettivo è: + +```dotenv +THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE=/run/secrets/psd-clinical-dwh-ca.pem +``` + +Il file sorgente locale è collegato da file operatore non tracciato. Usare URL `.it`, poi +**Test connections** su `/rpc/ping`. Non disabilitare TLS e non usare `curl -k`. + +## Rinnovo coordinato + +1. Preparare certificato e chain nuovi; verificare prima SAN `.it` e assenza di falsa copertura `.com`. +2. Confermare fuori banda il nuovo fingerprint. +3. Consegnare la CA/chain nuova ai client con `TLS_CA_FILE`, senza rimuovere ancora la precedente. +4. Aggiornare vault/binding e verificare ping con TLS normale. +5. Solo con gate Nginx approvato installare il certificato server e ripetere il ping. +6. Ritirare il trust precedente dopo la finestra approvata. + +Il rinnovo non modifica chiavi `dwh-auth`, record o ruoli PostgreSQL. TLS e rollback della route +restano approvazioni e backup distinti. diff --git a/docs/install/local-workspace-registry.md b/docs/install/local-workspace-registry.md index 2f71f261..27d26c31 100644 --- a/docs/install/local-workspace-registry.md +++ b/docs/install/local-workspace-registry.md @@ -116,6 +116,10 @@ and fast-forward candidate checkout. It does not copy anything to the user's com ## Complete runtime secrets in Workspace management +### Chiavi DWH REST per installazione + +Se il binding selezionato è `rest_api`, la chiave DWH è una credenziale per questa installazione e si salva nel vault tramite **Save runtime secrets** oppure in un file locale indicato da `API_KEY_FILE`. `postgres_direct` e `ssh_tunnel` non usano questa chiave. Per emissione, TLS, rotazione e verifica `/rpc/ping`, seguire [enrollment DWH REST](dwh-auth-client-enrollment.md). + Open Workspace management after the first successful repository update. 1. At the repository level, review the configured host, repository, branch, and current revision. diff --git a/docs/install/psd-workspace-setup.md b/docs/install/psd-workspace-setup.md index abba0596..3d1d7edd 100644 --- a/docs/install/psd-workspace-setup.md +++ b/docs/install/psd-workspace-setup.md @@ -12,6 +12,8 @@ v3 + `tht`). ## Stato attuale (2026-08-13) +> Per la rotazione della credenziale DWH, fare riferimento al [runbook PSD](../operations/psd-dwh-auth-rollout.md): non autorizza modifiche finché i due gate non sono approvati. Il ThothII PSD server resta `postgres_direct`; il Mac e i client remoti usano `rest_api` con una chiave per installazione. `postgres_direct` e `ssh_tunnel` non usano chiavi `dwh-auth`. + - **Repository PSD pubblicato:** `https://github.com/mptyl/tht-workspace-psd` (privato), branch `main`, commit `d4f9185`. Layout P1.1 già migrato e validato. - **Deploy key SSH** (sola lettura, senza passphrase) in diff --git a/docs/install/server-workspace-registry.md b/docs/install/server-workspace-registry.md index dccab696..17212ae9 100644 --- a/docs/install/server-workspace-registry.md +++ b/docs/install/server-workspace-registry.md @@ -79,6 +79,10 @@ candidate on the server; it does not transfer workspace files to the operator wo ## Complete runtime secrets in Workspace management +### Chiavi DWH REST per installazione + +Un'installazione server che seleziona `rest_api` usa una chiave DWH nel vault cifrato o nel file `API_KEY_FILE`; `postgres_direct` e `ssh_tunnel` non usano chiavi `dwh-auth`. Il servizio `dwh-auth` del DWH ha lifecycle `systemd` separato e non appartiene al Compose di ThothII. Vedere [enrollment client](dwh-auth-client-enrollment.md) e [guida server DWH](dwh-auth-server.md). + After repository activation, an authenticated user can: 1. Review the configured repository identity and update it without selecting a workspace. diff --git a/docs/operations/psd-dwh-auth-rollout.md b/docs/operations/psd-dwh-auth-rollout.md new file mode 100644 index 00000000..618bb3bd --- /dev/null +++ b/docs/operations/psd-dwh-auth-rollout.md @@ -0,0 +1,39 @@ +# PSD — rollout controllato DWH REST + +Questo runbook rispecchia i Task 9–10 del [piano](../superpowers/plans/2026-08-20-dwh-rest-per-installation-auth.md). È descrittivo: non autorizza mutazioni ora. Activity 1 PSD resta `IN_DISCUSSION` fino a due consensi espliciti separati. + +## Invarianti + +- ThothII sul server PSD: `postgres_direct` read-only, senza chiave `dwh-auth`. +- Mac PSD e client remoti: `rest_api`, una chiave per installazione, HTTPS `.it` verificato. +- `dwh-auth` è `systemd` indipendente, non Compose; non fermare o sostituire il vecchio stack ora. +- Sessioni legacy, indici Qdrant e cache Ollama sono dati test: nessuna migrazione o backup per il cutover. Il vecchio stack resta comunque fino a cutover/rollback approvati. +- Usare solo `/dwh/rpc/ping`, mai risultati clinici o catture Nginx grezze. + +## Gate A — Task 9, servizio locale senza Nginx pubblico + +Richiedere prima autorizzazione per SHA congelato, target, rollback e impatto legacy. Senza consenso, fermarsi e registrare solo `IN_DISCUSSION`. + +1. Verificare in sola lettura architettura, gruppo `www-data`, nomi liberi, systemd, `nginx -t`, ping attuale e file legacy regolare `root:root` `0600`; non leggerlo, stamparlo o calcolarne hash. +2. Costruire con `bash scripts/build-dwh-auth.sh --output /tmp/dwh-auth-release`; registrare solo SHA sorgente e checksum binario. +3. Installare binario/unit/tmpfiles come nella [guida server](../install/dwh-auth-server.md): registry `root:dwh-auth` `2750`, lock/record `0640`, socket `dwh-auth:www-data` `0660`. +4. Importare una sola legacy `legacy-shared` dal file protetto e creare `psd-mac-primary` in nuovo file `0600` sotto `/root/dwh-auth-provision/`; mai segreti in argv, ambiente, log o evidenze. +5. Eseguire `dwh-auth check`, `systemd-analyze verify`, avviare l'unità e testare sul socket Unix con config curl protette `0600`: nuova=204, legacy=204, casuale=401, assente=401. +6. Salvare solo ID pubblici, owner/mode, stato unit/socket, timestamp, checksum binario/config e rollback. Non modificare Nginx in questo gate. + +## Gate B — Task 10, Nginx e client + +Serve un secondo consenso: presentare file, backup, canale consegna Mac, osservazione ed esiti 204/401/503. + +1. Creare copie timestampate `root:root` `0600` di `/etc/nginx/sites-available/policlinicosandonato` e file coinvolti; non allegare configurazioni Nginx grezze alle evidenze. +2. Aggiungere solo `/etc/nginx/conf.d/dwh-auth-rate-limit.conf` e route DWH; preservare upstream `http://127.0.0.1:3001/`, mantenere byte-identiche le location vector e rimuovere la chiave prima di PostgREST. +3. Eseguire checker strutturale, scansione con solo esito, diff limitato e `sudo nginx -t`. Se uno fallisce, ripristinare backup prima di reload e registrare FAIL sanitizzato. +4. Dopo consenso fare reload, poi HTTPS `.it` con CA e config curl protette: legacy=204, nuova=204, casuale=401, assente=401 su `/dwh/rpc/ping`; guasto autenticatore=503, mai accesso permissivo. +5. Consegnare al Mac chiave e CA separatamente, verificare fingerprint fuori banda, configurare vault GUI o `API_KEY_FILE`, poi **Validate workspace** e **Test connections**. +6. Dopo osservazione revocare `legacy-shared` con ragione `shared-credential-rotation`; nuova=204, legacy=401 e journal limitato senza chiavi/digest. + +## Rollback e chiusura + +Durante dual-key il rollback ripristina solo route/servizio revisionati, verifica `nginx -t` e fa reload autorizzato. Non ripristina chiavi revocate, PostgreSQL, dati legacy o stack. Scatta per TLS, risposte inattese, salute degradata o assenza di consenso. + +Activity 1 diventa `PASS` solo con nuova positiva, legacy 401, servizio/Nginx validi, log sanitizzati, rollback leggibile e accettazione owner; poi avanza a Activity 2, con programma ancora `SURVEY_NO_GO`. Compilare [evidenza](../testing/evidence/psd-dwh-auth-rollout-report-template.md) e [collaudo](../testing/dwh-auth-manual-acceptance.md). diff --git a/docs/testing/dwh-auth-manual-acceptance.md b/docs/testing/dwh-auth-manual-acceptance.md new file mode 100644 index 00000000..fdff21fa --- /dev/null +++ b/docs/testing/dwh-auth-manual-acceptance.md @@ -0,0 +1,39 @@ +# Collaudo manuale `dwh-auth` + +Eseguire questo collaudo soltanto con consenso ai gate PSD e con dati sintetici. Non eseguire ora +mutazioni server e non registrare chiavi, digest, certificate body, output Nginx grezzo o risultati +clinici. + +## Precondizioni + +- SHA sorgente e checksum binario approvati; registry, lock e record hanno owner/mode attesi. +- `dwh-auth check`, unit `systemd` e socket Unix sono sani; Nginx viene toccato solo al Gate B. +- CA `.it` e fingerprint sono confermati fuori banda; nessun `.com` è usato senza SAN valido. +- Il server ThothII PSD è `postgres_direct`; il Mac/remoti sono `rest_api`. + +## Matrice di accettazione + +| Caso | Azione autorizzata | Atteso | Evidenza ammessa | +| --- | --- | --- | --- | +| Registro | `check`, `key list`, `key status` | Stato e soli ID pubblici | ID, status, owner/mode, timestamp | +| Socket locale | Config curl protetta, nuova/legacy | `204` durante dual-key | codice, unit/socket status | +| Negativo locale | Config casuale e richiesta senza header | `401` | codice, nessun valore header | +| Guasto controllato | Autenticatore/registro non disponibili nel test approvato | `503`, mai accesso | codice e rollback | +| HTTPS reale | `/dwh/rpc/ping` con CA approvata | `204`/2xx, TLS valido | ID, esito e approvazione fingerprint | +| Mac | **Validate workspace**, **Test connections** | Ping positivo | timestamp e stato GUI | +| Revoca | Chiave precedente dopo osservazione | `401`; nuova ancora positiva | ID pubblico e codici | +| Trasporti | Server PSD diretto e SSH diagnostico | nessuna chiave `dwh-auth` | trasporto selezionato | + +## Sequenza + +1. Fare il Gate A: verificare localmente 204/401 e che il servizio resti indipendente dal vecchio + stack. Nessun reload Nginx. +2. Al Gate B, fare backup protetti, `nginx -t`, reload autorizzato e ping `.it` con CA verificata. +3. Configurare il Mac nel vault GUI o con `API_KEY_FILE`; verificare ping e ID pubblico. +4. Dopo la finestra approvata, revocare legacy, ripetere nuova positiva/legacy 401 e controllare + solo un journal bounded sanitizzato. +5. Verificare rollback: backup leggibili, scope limitato a route/unit; nessuna migrazione di + sessioni legacy, indici Qdrant o cache Ollama. + +Il collaudo passa solo con tutti i casi attesi, owner acceptance e template evidenza completato. `ssh_tunnel` non usa chiavi `dwh-auth` e resta fuori dal runtime sessione. +Per la diagnostica seguire [guida server](../install/dwh-auth-server.md) e [TLS](../install/dwh-auth-tls.md). diff --git a/docs/testing/evidence/psd-dwh-auth-rollout-report-template.md b/docs/testing/evidence/psd-dwh-auth-rollout-report-template.md new file mode 100644 index 00000000..02884ebd --- /dev/null +++ b/docs/testing/evidence/psd-dwh-auth-rollout-report-template.md @@ -0,0 +1,40 @@ +# Template evidenza — rollout PSD `dwh-auth` + +Compilare dopo i gate autorizzati. Questa evidenza contiene solo metadati pubblici e sanitizzati. +Non inserire chiavi, digest di credenziali, corpo/fingerprint completo del certificato, output Nginx +grezzo, config curl, stringhe di connessione o risultati clinici. + +## Identità e approvazioni + +| Campo | Valore sanitizzato | +| --- | --- | +| SHA sorgente / checksum binario | `` | +| Proprietario e approvazione Gate A | `` | +| Proprietario e approvazione Gate B | `` | +| ID pubblici interessati | `` | +| Conferma fingerprint fuori banda | `` | + +## Stato e permessi + +| Oggetto | Percorso | Owner/mode | Stato | +| --- | --- | --- | --- | +| Registro | `/var/lib/dwh-auth/` | `root:dwh-auth` `2750` | `` | +| Lock e record | `active` / `revoked` | `root:dwh-auth` `0640` | `` | +| Socket | `/run/dwh-auth/verify.sock` | `dwh-auth:www-data` `0660` | `` | +| Backup configurazione | `` | `root:root` `0600` | `` | + +## Test e decisione + +| Test | Esito atteso | Esito registrato | +| --- | --- | --- | +| Servizio/socket | 204 nuova e legacy nel dual-key | `` | +| Negativi | 401 casuale, assente e legacy revocata | `` | +| Guasto infrastruttura | 503 fail-closed | `` | +| TLS `.it` | Ping verificato, SAN e conferma fuori banda | `` | +| Mac | Ping positivo e vault/file configurato | `` | +| Journal e scansioni | Nessuna chiave/digest esposti | `` | +| Rollback | Backup leggibile, scope confermato | `` | + +Decisione Activity 1: ``. L'avanzamento a Activity 2 richiede nuova +positiva, legacy 401, servizi validi, rollback e accettazione owner; il programma rimane +`SURVEY_NO_GO`. diff --git a/mkdocs.yml b/mkdocs.yml index 8b37acb4..19d1f697 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,13 @@ nav: - Guida utente: guida-utente.md - Accettazione autenticazione: testing/authentication-manual-acceptance.md - Setup Policlinico San Donato: install/psd-workspace-setup.md +- DWH REST per installazione: + - Server dwh-auth: install/dwh-auth-server.md + - Enrollment client DWH: install/dwh-auth-client-enrollment.md + - TLS DWH REST: install/dwh-auth-tls.md + - Rollout PSD DWH: operations/psd-dwh-auth-rollout.md + - Collaudo manuale DWH: testing/dwh-auth-manual-acceptance.md + - Template evidenza DWH: testing/evidence/psd-dwh-auth-rollout-report-template.md - Programma deploy server PSD: plans/2026-08-20-psd-server-deployment-program.md - Collaudo PSD Progetto A: testing/psd-server-project-a-manual.md - Collaudo PSD Progetto B: testing/psd-server-project-b-manual.md diff --git a/scripts/test-verify-dwh-auth-docs.sh b/scripts/test-verify-dwh-auth-docs.sh new file mode 100755 index 00000000..48d56bd7 --- /dev/null +++ b/scripts/test-verify-dwh-auth-docs.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) +verify="$root/scripts/verify-dwh-auth-docs.sh" +temp_root= + +report_pass() { + printf 'case=%s status=PASS\n' "$1" +} + +report_fail() { + printf 'case=%s status=FAIL\n' "$1" >&2 + exit 1 +} + +cleanup() { + if [[ "$temp_root" == /tmp/thothii-dwh-auth-docs.* && -d "$temp_root" ]]; then + rm -rf -- "$temp_root" + fi +} +trap cleanup EXIT + +[[ -x "$verify" ]] || report_fail verifier_missing + +temp_root=$(mktemp -d /tmp/thothii-dwh-auth-docs.XXXXXXXX) || report_fail fixture_root +fixture_root="$temp_root/fixture" +mkdir -p "$fixture_root/docs/install" "$fixture_root/docs/operations" \ + "$fixture_root/docs/testing/evidence" "$fixture_root/scripts" + +for relative in \ + docs/install/dwh-auth-server.md \ + docs/install/dwh-auth-client-enrollment.md \ + docs/install/dwh-auth-tls.md \ + docs/operations/psd-dwh-auth-rollout.md \ + docs/testing/dwh-auth-manual-acceptance.md \ + docs/testing/evidence/psd-dwh-auth-rollout-report-template.md \ + docs/install/local-workspace-registry.md \ + docs/install/server-workspace-registry.md \ + docs/install/psd-workspace-setup.md \ + docs/guida-utente.md \ + docs/index.md \ + mkdocs.yml; do + mkdir -p "$fixture_root/$(dirname "$relative")" + cp "$root/$relative" "$fixture_root/$relative" +done +cp -a "$root/docs/." "$fixture_root/docs/" + +"$verify" --root "$fixture_root" || report_fail positive_source +report_pass positive_source + +expect_rejected() { + local name=$1 target=$2 addition=$3 + local case_root="$temp_root/$name" + cp -a "$fixture_root" "$case_root" + printf '\n%s\n' "$addition" >>"$case_root/$target" + if "$verify" --root "$case_root" >/dev/null 2>&1; then + report_fail "$name" + fi + report_pass "$name" +} + +# Build synthetic only-in-fixture text at runtime: it is never a provisioned credential. +fake_key="thtdwh_v1.$(printf 'A%.0s' {1..16}).$(printf 'A%.0s' {1..43})" +fake_digest="$(printf 'A%.0s' {1..43})" + +expect_rejected credential_literal docs/install/dwh-auth-client-enrollment.md "$fake_key" +expect_rejected credential_digest_literal docs/testing/evidence/psd-dwh-auth-rollout-report-template.md "secret_sha256: $fake_digest" +expect_rejected curl_insecure docs/install/dwh-auth-tls.md 'curl -k https://example.invalid/dwh/rpc/ping' +expect_rejected tls_disabled docs/install/dwh-auth-tls.md 'verify_tls=false' +expect_rejected secret_in_environment docs/install/dwh-auth-client-enrollment.md "DWH_API_KEY=$fake_key" +expect_rejected secret_in_argv docs/install/dwh-auth-client-enrollment.md "curl -H 'X-API-Key: $fake_key' https://example.invalid/dwh/rpc/ping" +expect_rejected world_readable_secret docs/install/dwh-auth-server.md 'chmod 0644 /root/dwh-auth-provision/client.key' +expect_rejected raw_nginx_capture docs/operations/psd-dwh-auth-rollout.md 'nginx -T > /tmp/nginx-full.conf' +expect_rejected compose_coupling docs/install/dwh-auth-server.md 'docker compose up dwh-auth' + +report_pass summary diff --git a/scripts/verify-dwh-auth-docs.sh b/scripts/verify-dwh-auth-docs.sh new file mode 100755 index 00000000..36f85da7 --- /dev/null +++ b/scripts/verify-dwh-auth-docs.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -euo pipefail + +root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) +if [[ $# -gt 0 ]]; then + [[ $# -eq 2 && $1 == --root && -d $2 ]] || { echo 'usage: verify-dwh-auth-docs.sh [--root DIRECTORY]' >&2; exit 2; } + root=$(cd "$2" && pwd -P) +fi + +python3 - "$root" <<'PY' +import pathlib, re, sys + +root = pathlib.Path(sys.argv[1]) +docs = { + "server": "docs/install/dwh-auth-server.md", + "client": "docs/install/dwh-auth-client-enrollment.md", + "tls": "docs/install/dwh-auth-tls.md", + "rollout": "docs/operations/psd-dwh-auth-rollout.md", + "manual": "docs/testing/dwh-auth-manual-acceptance.md", + "evidence": "docs/testing/evidence/psd-dwh-auth-rollout-report-template.md", + "local": "docs/install/local-workspace-registry.md", + "server_registry": "docs/install/server-workspace-registry.md", + "psd": "docs/install/psd-workspace-setup.md", + "guide": "docs/guida-utente.md", + "index": "docs/index.md", + "nav": "mkdocs.yml", +} +text = {} +for label, relative in docs.items(): + path = root / relative + if not path.is_file(): + raise SystemExit(f"dwh-auth docs: missing {relative}") + text[label] = path.read_text(encoding="utf-8") + +requirements = { + "server": ["/var/lib/dwh-auth", "root:dwh-auth", "2750", ".writer.lock", "0640", "/run/dwh-auth/verify.sock", "0660", "systemd", "key create", "key list", "key status", "key revoke", "check", "backup", "rollback", "disinstallazione", "rest_api", "postgres_direct", "ssh_tunnel"], + "client": ["Workspace management", "Save runtime secrets", "API_KEY_FILE", "THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE", "TLS_CA_FILE", "/rpc/ping", "rest_api", "postgres_direct", "ssh_tunnel", "401", "503", "rotazione", "revoca"], + "tls": ["self-issued", ".it", ".com", "SAN", "TLS_CA_FILE", "openssl x509 -noout -fingerprint -sha256", "fuori banda", "rinnovo", "curl -k"], + "rollout": ["Task 9", "Task 10", "IN_DISCUSSION", "postgres_direct", "rest_api", "legacy-shared", "nginx -t", "204", "401", "503", "Qdrant", "Ollama", "rollback"], + "manual": ["204", "401", "503", "TLS", "registry", "postgres_direct", "ssh_tunnel"], + "evidence": ["ID pubblici", "owner", "mode", "timestamp", "checksum", "approvazione"], +} +for label, tokens in requirements.items(): + lowered = text[label].lower() + for token in tokens: + if token.lower() not in lowered: + raise SystemExit(f"dwh-auth docs: {docs[label]} lacks required topic: {token}") + +for path in [root / docs[k] for k in ("server", "client", "tls", "rollout", "manual", "evidence", "local", "server_registry", "psd", "guide", "index")]: + source = path.read_text(encoding="utf-8") + for target in re.findall(r"(? {target}") + +nav = text["nav"] +for relative in (docs["server"], docs["client"], docs["tls"], docs["rollout"], docs["manual"], docs["evidence"]): + nav_relative = relative.removeprefix("docs/") + if nav.count(nav_relative) != 1: + raise SystemExit(f"dwh-auth docs: navigation must include once: {nav_relative}") + +corpus = "\n".join(text.values()) +for pattern, label in [ + (r"thtdwh_v1\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}", "credential literal"), + (r"(?mi)^\s*secret_sha256\s*[:=]\s*[A-Za-z0-9_-]{16,}", "credential digest literal"), + (r"(?mi)^\s*[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD)\s*=\s*(?!/|<)[^\s#]+", "secret in environment"), + (r"(?i)(?:curl|dwh-auth)[^\n]{0,240}(?:-H\s+['\"][^'\"]*X-API-Key\s*:|--(?:api-key|token|password)\b)", "secret in argv"), + (r"(?im)^(?!.*(?:non usare|mai usare)).*curl\s+(?:[^\n]*\s)?(?:-k|--insecure)\b|verify_tls\s*=\s*false|insecure_skip_verify", "TLS bypass"), + (r"(?i)chmod\s+0?[0-7][0-7][4-7]\s+[^\n]*(?:\.key|secret|provision)", "world-readable secret"), + (r"(?m)^\s*nginx\s+-T\b", "raw Nginx capture"), + (r"(?i)docker\s+compose[^\n]*\bdwh-auth\b", "Compose coupling"), +]: + if re.search(pattern, corpus): + raise SystemExit(f"dwh-auth docs: forbidden {label}") + +print("dwh-auth documentation contract passed") +PY