docs: explain per-installation DWH access

This commit is contained in:
User
2026-08-21 03:58:50 +02:00
parent d0f7e0497d
commit 7b9b8b308d
14 changed files with 551 additions and 0 deletions
+2
View File
@@ -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`
+2
View File
@@ -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).
@@ -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).
+136
View File
@@ -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}/<public-key-id>.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 <public-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 <installation-id> \
--description <metadato-non-segreto> \
--output /root/dwh-auth-provision/<installation-id>-<generation>.key
```
Aggiungere `--expires-at <RFC3339-UTC>` 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 <previous-public-key-id> --reason <non-secret-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).
+49
View File
@@ -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.
+4
View File
@@ -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.
+2
View File
@@ -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
@@ -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.
+39
View File
@@ -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).
@@ -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).
@@ -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 | `<sha-e-checksum>` |
| Proprietario e approvazione Gate A | `<owner-e-timestamp>` |
| Proprietario e approvazione Gate B | `<owner-e-timestamp>` |
| ID pubblici interessati | `<public-key-ids>` |
| Conferma fingerprint fuori banda | `<approvatore-e-timestamp>` |
## Stato e permessi
| Oggetto | Percorso | Owner/mode | Stato |
| --- | --- | --- | --- |
| Registro | `/var/lib/dwh-auth/` | `root:dwh-auth` `2750` | `<pass-fail>` |
| Lock e record | `active` / `revoked` | `root:dwh-auth` `0640` | `<pass-fail>` |
| Socket | `/run/dwh-auth/verify.sock` | `dwh-auth:www-data` `0660` | `<pass-fail>` |
| Backup configurazione | `<protected-path>` | `root:root` `0600` | `<checksum-e-stato>` |
## Test e decisione
| Test | Esito atteso | Esito registrato |
| --- | --- | --- |
| Servizio/socket | 204 nuova e legacy nel dual-key | `<status-e-timestamp>` |
| Negativi | 401 casuale, assente e legacy revocata | `<status-e-timestamp>` |
| Guasto infrastruttura | 503 fail-closed | `<status-e-timestamp>` |
| TLS `.it` | Ping verificato, SAN e conferma fuori banda | `<status-e-timestamp>` |
| Mac | Ping positivo e vault/file configurato | `<status-e-timestamp>` |
| Journal e scansioni | Nessuna chiave/digest esposti | `<solo-pass-fail>` |
| Rollback | Backup leggibile, scope confermato | `<status-e-timestamp>` |
Decisione Activity 1: `<PASS o stato non conclusivo>`. L'avanzamento a Activity 2 richiede nuova
positiva, legacy 401, servizi validi, rollback e accettazione owner; il programma rimane
`SURVEY_NO_GO`.
+7
View File
@@ -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
+77
View File
@@ -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
+79
View File
@@ -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"(?<!!)\[[^]]*\]\(([^)#]+)(?:#[^)]+)?\)", source):
if "://" in target or target.startswith("mailto:"):
continue
candidate = (path.parent / target).resolve()
if not candidate.is_file() or root.resolve() not in candidate.parents:
raise SystemExit(f"dwh-auth docs: broken local link {path.relative_to(root)} -> {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