12 KiB
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:
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.
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:
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
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, 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.
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 "$description" \
--output "$key_output"
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.
L'import legacy è temporaneo PSD: il file sorgente è già root:root 0600 e non viene mai letto o
stampato dall'operatore.
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.
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_key_id" --reason "$revocation_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.
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.
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.
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.
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.
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.
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.
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.
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 |
|---|---|
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, senza bypass. |
Per il rollout PSD con i due gate separati vedere il runbook PSD.