fix: harden DWH auth operator guidance
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# Copia in un file operatore non tracciato (workspace-bindings.env).
|
||||
# Esempio Mac/local/remota: copia in un file operatore non tracciato (workspace-bindings.env).
|
||||
# Mai usare questo binding REST per il server PSD Project A: il server resta postgres_direct.
|
||||
# Contiene SOLO bindings THT_WS_* non segreti. I *_FILE sono path DI CONTENITORE
|
||||
# (/run/secrets/...), popolati dal connector override generato dai *_SOURCE dell'operatore env.
|
||||
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
|
||||
# Opzionale: solo se il DWH REST presenta una CA interna/privata.
|
||||
# Obbligatorio per questo esempio Mac/local/remoto: il DWH REST PSD usa CA self-issued/private.
|
||||
THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE=/run/secrets/psd-clinical-dwh-ca.pem
|
||||
|
||||
@@ -179,14 +179,14 @@ revisione attiva e stato dell'ultimo aggiornamento.
|
||||
**Livello 2 — workspace selezionato.** Questi comandi sono isolati perché richiedono prima la
|
||||
selezione del workspace.
|
||||
|
||||
- **Validate workspace** verifica nuovamente catalogo, descrittore, Evidence e invarianti della
|
||||
- **Validate workspace source** verifica nuovamente catalogo, descrittore, Evidence e invarianti della
|
||||
revisione attiva selezionata. Non contatta il DWH e non modifica file.
|
||||
- **Save runtime secrets** sostituisce alla cieca i valori compilati. I campi dipendono dal
|
||||
- **Save entered secrets** sostituisce alla cieca i valori compilati. I campi dipendono dal
|
||||
trasporto DWH e dall'autenticazione Evidence dichiarati; il backend restituisce solo lo stato
|
||||
configurato/mancante.
|
||||
- **Forget** elimina dal vault cifrato il singolo secret indicato. Le sessioni o operazioni future
|
||||
- **Forget stored value** elimina dal vault cifrato il singolo secret indicato. Le sessioni o operazioni future
|
||||
che lo richiedono restano bloccate finché non viene inserito di nuovo.
|
||||
- **Test connections** materializza temporaneamente i secret necessari, contatta i servizi dati
|
||||
- **Test workspace connections** materializza temporaneamente i secret necessari, contatta i servizi dati
|
||||
configurati per quel workspace e rimuove i file temporanei alla fine. Non esporta né pubblica
|
||||
nulla.
|
||||
|
||||
|
||||
@@ -23,18 +23,17 @@ pubblico.
|
||||
|
||||
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**.
|
||||
2. Il trasporto `rest_api` è una precondizione amministrativa del binding locale, non una scelta della GUI. Controllare URL/trust locali e usare **Validate workspace source**.
|
||||
3. Inserire la chiave nel campo write-only **Data warehouse API key**, poi **Save entered 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
|
||||
4. Eseguire **Test workspace 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
|
||||
5. Comunicare al server solo ID pubblico, timestamp e risultato. **Forget stored value** 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:
|
||||
`API_KEY_FILE` significa che il valore è nel file, non nella variabile. Questo è l'esempio Mac/local/remoto nel file PSD non tracciato `workspace-bindings.env`; non è il binding del server PSD Project A, che resta `postgres_direct`. I binding REST sono:
|
||||
|
||||
```dotenv
|
||||
THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api
|
||||
@@ -61,7 +60,7 @@ sostituire `PSD_CLINICAL` con ID immutabile maiuscolo (trattini in underscore).
|
||||
|
||||
## Ping, rotazione e revoca
|
||||
|
||||
Usare solo **Test connections** su `/rpc/ping`: successo è 2xx con TLS verificato; il server
|
||||
Usare solo **Test workspace 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`.
|
||||
|
||||
@@ -69,7 +69,8 @@ Usare sempre un root assoluto. Questi comandi espongono solo ID pubblici, stato,
|
||||
```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
|
||||
key_id=public-key-id
|
||||
sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key status --key-id "$key_id" --json
|
||||
```
|
||||
|
||||
Un errore di integrità, permessi, symlink o JSON malformato richiede ripristino da backup protetto,
|
||||
@@ -81,13 +82,16 @@ Il comando crea la chiave una volta in un nuovo file assoluto `0600`; stdout con
|
||||
pubblico, installazione e percorso. Il file di output non deve esistere.
|
||||
|
||||
```bash
|
||||
installation_id=psd-mac-primary
|
||||
description=operatore-mac-primario
|
||||
key_output="/root/dwh-auth-provision/$installation_id-primary.key"
|
||||
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
|
||||
--installation-id "$installation_id" \
|
||||
--description "$description" \
|
||||
--output "$key_output"
|
||||
```
|
||||
|
||||
Aggiungere `--expires-at <RFC3339-UTC>` solo se la policy impone una scadenza; il default è nessuna
|
||||
Aggiungere `--expires-at "YYYY-MM-DDTHH:MM:SSZ"` 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.
|
||||
@@ -106,8 +110,10 @@ l'ID pubblico; attendere l'osservazione; poi revocare la precedente e provare nu
|
||||
precedente=401.
|
||||
|
||||
```bash
|
||||
previous_key_id=public-key-id
|
||||
revocation_reason=shared-credential-rotation
|
||||
sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth key revoke \
|
||||
--key-id <previous-public-key-id> --reason <non-secret-reason>
|
||||
--key-id "$previous_key_id" --reason "$revocation_reason"
|
||||
```
|
||||
|
||||
La revoca non è annullabile e un ID revocato non si ricrea.
|
||||
@@ -124,6 +130,90 @@ La disinstallazione richiede autorizzazione esplicita, client REST migrati/revoc
|
||||
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`.
|
||||
|
||||
## Procedure riproducibili e secret-safe
|
||||
|
||||
Eseguire soltanto nel gate autorizzato. Le variabili seguenti contengono percorsi, timestamp e codici, mai una chiave. Il manifest e l'archivio del registro sono `0600`; l'archivio resta materiale riservato.
|
||||
|
||||
```bash
|
||||
run_id=$(date -u +%Y%m%dT%H%M%SZ)
|
||||
backup_root=/root/dwh-auth-provision
|
||||
registry_backup="$backup_root/registry-$run_id.tar"
|
||||
manifest="$backup_root/registry-$run_id.manifest"
|
||||
sudo install -o root -g root -m 0600 /dev/null "$registry_backup"
|
||||
sudo install -o root -g root -m 0600 /dev/null "$manifest"
|
||||
sudo tar --acls --xattrs -C /var/lib -cf "$registry_backup" dwh-auth
|
||||
sudo sh -c 'sha256sum "$1" > "$2"' sh "$registry_backup" "$manifest"
|
||||
if sudo sha256sum -c "$manifest" >/dev/null; then printf 'registry_manifest=PASS\n'; else printf 'registry_manifest=FAIL\n' >&2; exit 1; fi
|
||||
```
|
||||
|
||||
Per ripristinare, fermare prima il servizio, verificare il manifest senza stamparne il contenuto, estrarre l'archivio solo nel root autorizzato, rieseguire `dwh-auth check` e avviare l'unità. Conservare archivio e manifest per la retention approvata; non sovrascrivere né cancellare record per correggere un errore.
|
||||
|
||||
```bash
|
||||
if ! sudo sha256sum -c "$manifest" >/dev/null; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
|
||||
sudo systemctl stop dwh-auth
|
||||
sudo tar --acls --xattrs -C /var/lib -xf "$registry_backup"
|
||||
sudo /usr/local/sbin/dwh-auth --registry-root /var/lib/dwh-auth check
|
||||
sudo systemctl start dwh-auth
|
||||
printf 'registry_restore=PASS\n'
|
||||
```
|
||||
|
||||
Per i test socket, creare la configurazione curl `0600` leggendo il file chiave direttamente nel file di configurazione: il valore non passa in argv, ambiente o stdout.
|
||||
|
||||
```bash
|
||||
key_file=/root/dwh-auth-provision/psd-mac-primary.key
|
||||
curl_cfg=/root/dwh-auth-provision/dwh-auth-new.curl
|
||||
random_cfg=/root/dwh-auth-provision/dwh-auth-random.curl
|
||||
sudo install -o root -g root -m 0600 /dev/null "$curl_cfg"
|
||||
sudo install -o root -g root -m 0600 /dev/null "$random_cfg"
|
||||
sudo sh -c '{ printf "%s" "header = X-API-Key: "; tr -d "\r\n" < "$1"; printf "\n"; } > "$2"' sh "$key_file" "$curl_cfg"
|
||||
sudo sh -c 'printf "%s\n" "header = X-API-Key: invalid-test" > "$1"' sh "$random_cfg"
|
||||
```
|
||||
|
||||
Il socket `/verify` deve restituire 204 per il file nuovo e 401 per configurazione casuale e richiesta senza header; stampare soltanto il codice.
|
||||
|
||||
```bash
|
||||
status=$(sudo curl --config "$curl_cfg" --unix-socket /run/dwh-auth/verify.sock --output /dev/null --silent --show-error --write-out '%{http_code}' http://localhost/verify)
|
||||
[ "$status" = 204 ] && printf 'socket_new=PASS\n' || { printf 'socket_new=FAIL\n' >&2; exit 1; }
|
||||
status=$(sudo curl --config "$random_cfg" --unix-socket /run/dwh-auth/verify.sock --output /dev/null --silent --show-error --write-out '%{http_code}' http://localhost/verify)
|
||||
[ "$status" = 401 ] && printf 'socket_random=PASS\n' || { printf 'socket_random=FAIL\n' >&2; exit 1; }
|
||||
status=$(sudo curl --unix-socket /run/dwh-auth/verify.sock --output /dev/null --silent --show-error --write-out '%{http_code}' http://localhost/verify)
|
||||
[ "$status" = 401 ] && printf 'socket_missing=PASS\n' || { printf 'socket_missing=FAIL\n' >&2; exit 1; }
|
||||
```
|
||||
|
||||
Per HTTPS reale usare la configurazione protetta e la CA approvata contro `/dwh/rpc/ping`: PostgREST può restituire qualsiasi 2xx, non si pretende 204. La prova 401 usa solo configurazione casuale protetta.
|
||||
|
||||
```bash
|
||||
ping_url=https://supabase-aritmolab.policlinicosandonato.it/dwh/rpc/ping
|
||||
ca_file=/root/dwh-auth-provision/psd-dwh-ca.pem
|
||||
status=$(sudo curl --config "$curl_cfg" --cacert "$ca_file" --connect-timeout 5 --max-time 15 --output /dev/null --silent --show-error --write-out '%{http_code}' "$ping_url")
|
||||
case "$status" in 2??) printf 'https_new=PASS\n' ;; *) printf 'https_new=FAIL\n' >&2; exit 1 ;; esac
|
||||
status=$(sudo curl --config "$random_cfg" --cacert "$ca_file" --connect-timeout 5 --max-time 15 --output /dev/null --silent --show-error --write-out '%{http_code}' "$ping_url")
|
||||
[ "$status" = 401 ] && printf 'https_random=PASS\n' || { printf 'https_random=FAIL\n' >&2; exit 1; }
|
||||
```
|
||||
|
||||
Per provare 503 in una finestra approvata, registrare l'orario, fermare temporaneamente l'unità, eseguire il ping con timeout e trap di ripristino; il comando deve stampare solo PASS/FAIL.
|
||||
|
||||
```bash
|
||||
was_active=$(sudo systemctl is-active dwh-auth || true)
|
||||
[ "$was_active" = active ] || { printf 'https_auth_down=FAIL\n' >&2; exit 1; }
|
||||
restore_auth() { sudo systemctl start dwh-auth; }
|
||||
trap restore_auth EXIT INT TERM
|
||||
sudo systemctl stop dwh-auth
|
||||
status=$(sudo curl --config "$random_cfg" --cacert "$ca_file" --connect-timeout 5 --max-time 15 --output /dev/null --silent --show-error --write-out '%{http_code}' "$ping_url" || true)
|
||||
[ "$status" = 503 ] && printf 'https_auth_down=PASS\n' || { printf 'https_auth_down=FAIL\n' >&2; exit 1; }
|
||||
sudo systemctl start dwh-auth
|
||||
trap - EXIT INT TERM
|
||||
```
|
||||
|
||||
Lo scan journal non salva righe grezze: emette solo PASS/FAIL.
|
||||
|
||||
```bash
|
||||
since=$(date -u -d '15 minutes ago' +%Y-%m-%dT%H:%M:%SZ)
|
||||
if sudo journalctl -u dwh-auth --since "$since" --no-pager --output=cat | grep -E 'thtdwh_v1|secret_sha256' >/dev/null; then printf 'journal_secret_scan=FAIL\n' >&2; exit 1; else printf 'journal_secret_scan=PASS\n'; fi
|
||||
```
|
||||
|
||||
Dopo rollback verificato e migrazione/revoca di ogni client REST, la disinstallazione resta condizionata all'approvazione: eseguire `sudo systemctl disable --now dwh-auth`, ma mantenere registro, backup e manifest protetti per la retention; non cancellarli durante il rollback.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Sintomo | Interpretazione e azione |
|
||||
|
||||
@@ -34,7 +34,7 @@ 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`.
|
||||
**Test workspace connections** su `/rpc/ping`. Non disabilitare TLS e non usare `curl -k`.
|
||||
|
||||
## Rinnovo coordinato
|
||||
|
||||
|
||||
@@ -60,9 +60,9 @@ After the installation is started, use Workspace management from the authenticat
|
||||
clone.
|
||||
2. Confirm that the installation-owned `workspace-secrets` storage remains outside the source
|
||||
repository and contains no credentials in the workspace descriptors.
|
||||
3. Select the workspace and run **Validate workspace** to verify the active descriptor, catalog,
|
||||
3. Select the workspace and run **Validate workspace source** to verify the active descriptor, catalog,
|
||||
Evidence, annotations, and runtime bindings.
|
||||
4. Run **Test connections** only with the approved read-only DWH/Evidence test configuration.
|
||||
4. Run **Test workspace connections** only with the approved read-only DWH/Evidence test configuration.
|
||||
Results are redacted and the workspace source remains unchanged.
|
||||
|
||||
<!-- workspace-descriptor-contract:start -->
|
||||
@@ -118,7 +118,7 @@ and fast-forward candidate checkout. It does not copy anything to the user's com
|
||||
|
||||
### 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).
|
||||
Se il binding selezionato è `rest_api`, la chiave DWH è una credenziale per questa installazione e si salva nel vault tramite **Save entered 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.
|
||||
|
||||
@@ -127,13 +127,13 @@ Open Workspace management after the first successful repository update.
|
||||
tests do.
|
||||
3. Review the runtime fields derived from the selected DWH transport and Evidence authentication
|
||||
mechanism.
|
||||
4. Enter or rotate the required values and choose **Save runtime secrets**.
|
||||
5. Run **Validate workspace** and then **Test connections**.
|
||||
4. Enter or rotate the required values and choose **Save entered secrets**.
|
||||
5. Run **Validate workspace source** and then **Test workspace connections**.
|
||||
|
||||
Secret fields are write-only. The GUI receives only configured/missing status. Values are
|
||||
encrypted by the backend in the platform-neutral `workspace-secrets` volume. ThothII temporarily
|
||||
materializes a restrictive file only while an existing file-oriented connector needs it, then
|
||||
removes that file when the runtime lease ends. **Forget** deletes the selected encrypted value.
|
||||
removes that file when the runtime lease ends. **Forget stored value** deletes the selected encrypted value.
|
||||
|
||||
The workspace YAML stays environment-independent: it declares connector mechanisms, not host
|
||||
paths or credentials. Installation trust material such as a Git CA or `known_hosts` remains an
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
|
||||
Authentication acceptance is documented in the [manual authentication matrix](../testing/authentication-manual-acceptance.md).
|
||||
Use generic OIDC with Authentik as the certified group catalog, map only the exact TOT Users and
|
||||
TOT Admin groups, then run Workspace Validate, `tht auth check`, `tht auth check --interactive`,
|
||||
and Workspace Test in that order. Browser callback E2E, native Windows execution, approved PSD
|
||||
TOT Admin groups, then run **Validate workspace source**, `tht auth check`, `tht auth check --interactive`,
|
||||
and **Test workspace connections** in that order. Browser callback E2E, native Windows execution, approved PSD
|
||||
manual identities, external L2, and the two parked restore-lock preconditions remain pending the
|
||||
Task 15/release gates.
|
||||
|
||||
Guida operativa per collegare ThothII al DWH di PSD con il nuovo sistema (registry Git + descriptor
|
||||
v3 + `tht`).
|
||||
|
||||
## Stato attuale (2026-08-13)
|
||||
## Stato storico Mac/local (2026-08-13)
|
||||
|
||||
> Questo stato è storico per Mac/local; il server PSD Project A usa binding separato `postgres_direct` read-only.
|
||||
>
|
||||
> 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
|
||||
@@ -22,7 +24,7 @@ v3 + `tht`).
|
||||
- **Config operatore pronta** (file reali gitignored in `deploy/psd/`): `operator.env`,
|
||||
`thothii-installation.yaml` e i secret d'installazione in `secrets/` (pi-auth, secret bundle,
|
||||
chiave SSH, known_hosts). L'API key DWH va completata nella gestione Workspace ed è conservata
|
||||
nel vault cifrato del backend. Nessuna CA: il DWH REST usa HTTPS pubblico.
|
||||
nel vault cifrato del backend. Il certificato REST è self-issued/private: ogni Mac/local senza trust equivalente deve usare `TLS_CA_FILE` e verificare il fingerprint fuori banda, come in `docs/install/dwh-auth-tls.md`.
|
||||
- **Stack avviato** (progetto `thothii-70417a3e30ea`, via `tht start`): `qdrant`, `embedding`
|
||||
(con `qwen3-embedding:0.6b`), `core`, `frontend` sani. Il registry ha **clonato e attivato**
|
||||
`psd-clinical` (stato `ready`).
|
||||
|
||||
@@ -87,9 +87,9 @@ After repository activation, an authenticated user can:
|
||||
|
||||
1. Review the configured repository identity and update it without selecting a workspace.
|
||||
2. Select a workspace to see the DWH/Evidence credential fields required by its connector modes.
|
||||
3. Blind-save or rotate values; returned responses contain status only.
|
||||
4. Run **Validate workspace source** and then test its configured connections.
|
||||
5. Forget an obsolete value after dependent sessions and jobs have ended.
|
||||
3. Blind-save or rotate values with **Save entered secrets**; returned responses contain status only.
|
||||
4. Run **Validate workspace source** and then **Test workspace connections**.
|
||||
5. Use **Forget stored value** for an obsolete value after dependent sessions and jobs have ended.
|
||||
|
||||
The backend encrypts values in `/data/workspace-secrets`, including the installation-specific
|
||||
master key. The server profile persists that directory inside `THT_DATA_ROOT`; no workspace YAML
|
||||
@@ -106,7 +106,7 @@ descriptors, Evidence paths, and cross-workspace invariants at one commit, then
|
||||
the complete candidate. A rejected candidate never replaces the previous active snapshot. The
|
||||
application-owned checkout and snapshots are read-only runtime state.
|
||||
|
||||
Validation proves descriptor and repository structure. **Test connections** additionally
|
||||
Validation proves descriptor and repository structure. **Test workspace connections** additionally
|
||||
materializes the current runtime secrets and contacts only the selected workspace's configured
|
||||
DWH/Evidence endpoints. Failure does not modify or publish workspace source.
|
||||
|
||||
|
||||
@@ -27,9 +27,9 @@ Serve un secondo consenso: presentare file, backup, canale consegna Mac, osserva
|
||||
|
||||
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**.
|
||||
3. Eseguire checker strutturale, scansione segreti con solo `PASS/FAIL` e metadati, installare candidati e `sudo nginx -t`. No raw diff: non eseguire o conservare raw diff, `nginx -T` o dump: il file legacy può contenere la chiave. 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=2xx, nuova=2xx, 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 source** e **Test workspace 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
|
||||
|
||||
@@ -684,8 +684,9 @@ PostgREST; include no literal key.
|
||||
|
||||
- [ ] **Step 3: Validate before reload**
|
||||
|
||||
Run structural checker, secret scan without match output, bounded diff, install candidates,
|
||||
`sudo nginx -t`. On failure restore backups before reload and record sanitized FAIL.
|
||||
Run the structural checker and a secret scan that emits only PASS/FAIL metadata, install candidates,
|
||||
and run `sudo nginx -t`. Never run or retain a raw diff, `nginx -T`, or configuration dump: a legacy
|
||||
Nginx file can contain the exposed key. On failure restore backups before reload and record sanitized FAIL.
|
||||
|
||||
- [ ] **Step 4: Reload/prove dual-key**
|
||||
|
||||
@@ -696,7 +697,7 @@ change.
|
||||
- [ ] **Step 5: Deliver/configure Mac**
|
||||
|
||||
Use approved protected channel. Verify CA fingerprint, configure vault or headless `API_KEY_FILE`,
|
||||
run Workspace Validate, Test connections, `/rpc/ping`. Record public IDs, fingerprint confirmation,
|
||||
run **Validate workspace source**, **Test workspace connections**, `/rpc/ping`. Record public IDs, fingerprint confirmation,
|
||||
timestamp, result only.
|
||||
|
||||
- [ ] **Step 6: Observe/revoke legacy**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Collaudo manuale `dwh-auth`
|
||||
|
||||
Eseguire questo collaudo soltanto con consenso ai gate PSD e con dati sintetici. Non eseguire ora
|
||||
Prima del rollout, i test sono sintetici e non usano dati clinici. Nel rollout reale si usano credenziali reali esclusivamente su `/rpc/ping`, senza acquisire risultati clinici. Non eseguire ora
|
||||
mutazioni server e non registrare chiavi, digest, certificate body, output Nginx grezzo o risultati
|
||||
clinici.
|
||||
|
||||
@@ -19,8 +19,8 @@ clinici.
|
||||
| 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 |
|
||||
| HTTPS reale | `/dwh/rpc/ping` con CA approvata | qualsiasi `2xx`, TLS valido | ID, esito e approvazione fingerprint |
|
||||
| Mac | **Validate workspace source**, **Test workspace 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 |
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ trap cleanup EXIT
|
||||
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"
|
||||
"$fixture_root/docs/testing/evidence" "$fixture_root/scripts" "$fixture_root/deploy/psd"
|
||||
|
||||
for relative in \
|
||||
docs/install/dwh-auth-server.md \
|
||||
@@ -45,6 +45,7 @@ for relative in \
|
||||
cp "$root/$relative" "$fixture_root/$relative"
|
||||
done
|
||||
cp -a "$root/docs/." "$fixture_root/docs/"
|
||||
cp "$root/deploy/psd/workspace-bindings.env.example" "$fixture_root/deploy/psd/workspace-bindings.env.example"
|
||||
|
||||
"$verify" --root "$fixture_root" || report_fail positive_source
|
||||
report_pass positive_source
|
||||
@@ -65,13 +66,45 @@ 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 opaque_legacy_header_literal docs/install/dwh-auth-client-enrollment.md "curl --header 'X-API-Key: opaque-legacy-fixture' https://example.invalid/dwh/rpc/ping"
|
||||
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_environment docs/install/dwh-auth-client-enrollment.md "export THT_WS_PSD_CLINICAL_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 sudo_raw_nginx_capture docs/operations/psd-dwh-auth-rollout.md 'sudo nginx -T > /tmp/nginx-full.conf'
|
||||
expect_rejected raw_diff_capture docs/operations/psd-dwh-auth-rollout.md 'sudo diff -u /etc/nginx/sites-available/policlinicosandonato /root/backup.conf'
|
||||
expect_rejected git_raw_diff_capture docs/operations/psd-dwh-auth-rollout.md 'git diff --no-index /root/old.conf /root/new.conf'
|
||||
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'
|
||||
|
||||
expect_replacement_rejected() {
|
||||
local name=$1 target=$2 needle=$3 replacement=$4
|
||||
local case_root="$temp_root/$name"
|
||||
cp -a "$fixture_root" "$case_root"
|
||||
[[ $(grep -Foc -- "$needle" "$case_root/$target") -eq 1 ]] || report_fail "${name}_fixture"
|
||||
sed -i "s~$needle~$replacement~" "$case_root/$target"
|
||||
if "$verify" --root "$case_root" >/dev/null 2>&1; then report_fail "$name"; fi
|
||||
report_pass "$name"
|
||||
}
|
||||
|
||||
|
||||
expect_global_replacement_rejected() {
|
||||
local name=$1 target=$2 needle=$3 replacement=$4
|
||||
local case_root="$temp_root/$name"
|
||||
cp -a "$fixture_root" "$case_root"
|
||||
[[ $(grep -Foc -- "$needle" "$case_root/$target") -gt 0 ]] || report_fail "${name}_fixture"
|
||||
sed -i "s~$needle~$replacement~g" "$case_root/$target"
|
||||
if "$verify" --root "$case_root" >/dev/null 2>&1; then report_fail "$name"; fi
|
||||
report_pass "$name"
|
||||
}
|
||||
|
||||
expect_replacement_rejected missing_exact_gui_label docs/install/dwh-auth-client-enrollment.md "Validate workspace source" "Validate workspace"
|
||||
expect_replacement_rejected server_transport_contradiction docs/operations/psd-dwh-auth-rollout.md 'server PSD: `postgres_direct` read-only' 'server PSD: `rest_api` read-only'
|
||||
expect_replacement_rejected missing_mac_local_marker deploy/psd/workspace-bindings.env.example "Mac/local/remota" "server PSD"
|
||||
expect_replacement_rejected missing_private_ca docs/install/psd-workspace-setup.md "TLS_CA_FILE" "TLS_CA_REMOVED"
|
||||
expect_global_replacement_rejected missing_socket_path docs/install/dwh-auth-server.md "/run/dwh-auth/verify.sock" "/run/dwh-auth/other.sock"
|
||||
expect_replacement_rejected rest_transport_flag deploy/psd/workspace-bindings.env.example "THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api" "THT_WS_PSD_CLINICAL_DWH_TRANSPORT=postgres_direct"
|
||||
|
||||
report_pass summary
|
||||
|
||||
@@ -24,6 +24,7 @@ docs = {
|
||||
"guide": "docs/guida-utente.md",
|
||||
"index": "docs/index.md",
|
||||
"nav": "mkdocs.yml",
|
||||
"psd_template": "deploy/psd/workspace-bindings.env.example",
|
||||
}
|
||||
text = {}
|
||||
for label, relative in docs.items():
|
||||
@@ -33,11 +34,11 @@ for label, relative in docs.items():
|
||||
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"],
|
||||
"server": ["manifest", "curl_cfg", "journalctl", "systemctl disable --now", "trap", "/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", "Validate workspace source", "Test workspace connections", "Save entered secrets", "Forget stored value", "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"],
|
||||
"rollout": ["PASS/FAIL", "no raw diff", "Task 9", "Task 10", "IN_DISCUSSION", "postgres_direct", "rest_api", "legacy-shared", "nginx -t", "204", "401", "503", "Qdrant", "Ollama", "rollback"],
|
||||
"manual": ["credenziali reali", "sintetici", "/rpc/ping", "204", "401", "503", "TLS", "registry", "postgres_direct", "ssh_tunnel"],
|
||||
"evidence": ["ID pubblici", "owner", "mode", "timestamp", "checksum", "approvazione"],
|
||||
}
|
||||
for label, tokens in requirements.items():
|
||||
@@ -64,16 +65,27 @@ for relative in (docs["server"], docs["client"], docs["tls"], docs["rollout"], d
|
||||
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"(?mi)^\s*[\"']?secret_sha256[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9_-]{16,}", "credential digest literal"),
|
||||
(r"(?mi)^\s*(?:export\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|--header)\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"(?m)^\s*(?:sudo\s+)?nginx\s+-T\b", "raw Nginx capture"),
|
||||
(r"(?m)^\s*(?:sudo\s+)?(?:diff\b|git\s+diff\b)", "raw diff 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}")
|
||||
|
||||
server_direct = re.search(r"server PSD[^\n]{0,100}postgres_direct", text["rollout"], re.I)
|
||||
psd_direct = re.search(r"server PSD[^\n]{0,100}postgres_direct", text["psd"], re.I)
|
||||
if not server_direct or not psd_direct:
|
||||
raise SystemExit("dwh-auth docs: PSD server must remain postgres_direct")
|
||||
template = text["psd_template"]
|
||||
if "tht_ws_psd_clinical_dwh_transport=rest_api" not in template.lower() or "mac/local/remota" not in template.lower() or not re.search(r"mai .*server psd", template, re.I):
|
||||
raise SystemExit("dwh-auth docs: PSD REST template must be explicitly Mac/local/remota, never server PSD")
|
||||
if "TLS_CA_FILE" not in text["psd"] or re.search(r"(?i)nessuna CA|HTTPS pubblico", text["psd"]):
|
||||
raise SystemExit("dwh-auth docs: PSD setup contradicts private CA TLS requirement")
|
||||
|
||||
print("dwh-auth documentation contract passed")
|
||||
PY
|
||||
|
||||
@@ -517,8 +517,10 @@ flow_tokens = [
|
||||
"ThothII",
|
||||
"Update workspace repository",
|
||||
"workspace-secrets",
|
||||
"Validate workspace",
|
||||
"Test connections",
|
||||
"Validate workspace source",
|
||||
"Test workspace connections",
|
||||
"Save entered secrets",
|
||||
"Forget stored value",
|
||||
]
|
||||
for guide in (local_path, server_path):
|
||||
text = guide.read_text()
|
||||
|
||||
Reference in New Issue
Block a user