docs: publish English public documentation
Publish documentation / publish (push) Successful in 43s

This commit is contained in:
Codex
2026-08-26 10:54:44 +02:00
parent b5db0cd3c1
commit 7d32bb1e74
21 changed files with 1378 additions and 1326 deletions
+1 -2
View File
@@ -75,7 +75,6 @@ This section applies only when a Linux `profile: server` descriptor declares a r
The canonical authentication root stays root-owned and is the only authority. The container reads
only the separate read-only runtime projection selected by `CURRENT`; it never falls back to the
canonical files or to a previous generation. Run projected mutations and repairs through the
root-operated `tht` commands documented in the [server guide](server.md), and never edit runtime
files directly.
root-operated `tht` commands, and never edit runtime files directly.
Mac, Windows, and local direct-file authentication remain unchanged when the projection is absent.
+3 -3
View File
@@ -71,7 +71,7 @@ The surfaces have distinct semantics and this order is recommended:
issuer/JWKS, catalog credentials, and all configured mapped groups.
3. `tht auth check --interactive` repeats live diagnosis and additionally validates a device-flow
identity and its direct `groups` claim when Device Authorization is available.
4. Workspace Test performs aggregate live workspace and authentication validation.
4. Installation diagnostics perform aggregate live workspace and authentication validation.
The live CLI forms are:
@@ -87,8 +87,8 @@ real ID token including `groups`. It is an operator check, not a replacement for
`tht doctor` emits this exact ordered report: `descriptor`, `files`, `docker`, `compose`,
`configuration`, `authentication`, `services`, `core-http`, `frontend-http`,
`workspace-registry`, `workflow`, `pi`. Its authentication entry is live and non-interactive.
Any authentication failure makes Workspace Validate or Workspace Test non-activatable according
to that surface's static or live scope.
Any authentication failure prevents activation according to the static or live scope of the
relevant diagnostic surface.
The complete closed diagnostic-code union and exact role-to-permission expansion are in the
[authentication architecture](../architecture/authentication.md).
+35 -31
View File
@@ -1,37 +1,41 @@
# Authentik provider setup
# Authentik provider configuration
Authentik is the first certified provider for PSD acceptance. The ThothII browser protocol remains
generic OIDC; these steps configure the provider-specific group catalog only.
ThothII uses generic OIDC in the browser. Authentik provides the identity provider and group
catalog without adding a proprietary login flow.
1. Create an OAuth2/OIDC application and provider in Authentik. Register exactly
`<publicUrl>/api/auth/oidc/callback` as the callback and enable `openid`, `profile`, and `email`.
2. Configure the provider so the ID token contains a direct `groups` array of strings. Verify the
claim with a disposable test identity before running acceptance.
3. Create a dedicated API service account for the group catalog. Grant group-view-only privilege;
do not grant write, user-management, or directory-administration privilege. Put its bearer value
in the protected bundle under `THT_AUTHENTIK_API_TOKEN`.
4. Create or confirm the exact groups `TOT Users` and `TOT Admin`. Map them explicitly in
`auth.yaml` to `user` and `admin`, respectively. Keep other upstream groups out of the mapping.
5. Run Workspace Validate for static authentication validation. Then run live non-interactive
diagnosis, followed by the optional device-flow identity check:
```mermaid
sequenceDiagram
participant Browser
participant ThothII
participant Authentik
Browser->>ThothII: Sign in
ThothII->>Authentik: Authorization Code with PKCE
Authentik-->>Browser: Login and consent
Browser->>ThothII: Callback with code
ThothII->>Authentik: Token exchange
Authentik-->>ThothII: Identity and groups
ThothII-->>Browser: Opaque session
```
```sh
tht auth check
tht auth check --interactive
tht doctor --json
```
## OIDC provider
6. Run Workspace Test for aggregate live workspace and authentication validation. It must prove
discovery/JWKS, catalog access, and every configured group. The diagnostic result must contain
no secret values. `tht doctor --json` reports `authentication` after `configuration` and before
`services` in its exact ordered checklist.
1. Create an OAuth2/OIDC application and provider.
2. Register exactly `PUBLIC_URL/api/auth/oidc/callback`.
3. Enable the `openid`, `profile`, and `email` scopes.
4. Configure a direct `groups` claim as an array of strings.
Only configured exact group names are queried. Additional Authentik or directory groups are ignored
silently, without a warning. A mapped group absent from Authentik fails closed with
`oidc_mapped_group_missing`; an ambiguous exact-name result uses
`oidc_mapped_group_ambiguous`. A group visible only in an upstream directory but not represented
in Authentik is missing from ThothII’s catalog and must not be treated as present.
## Group catalog
Rotate the two credentials independently through the protected secret-file procedure, then repeat
`tht auth check` and workspace Test. Never put either value in this guide, YAML, shell history,
diagnostic output, or acceptance evidence.
Create a dedicated service account with read-only access to groups. Store its token in the
protected bundle as `THT_AUTHENTIK_API_TOKEN`.
Map the exact enterprise group names to the ThothII `user` and `admin` roles in `auth.yaml`.
Unmapped groups are ignored. A configured group that does not exist produces a closed error.
## Diagnostics
`tht auth check` checks discovery, the issuer, JWKS, catalog access, and the configured groups.
The `--interactive` option also verifies identity through device flow when the provider supports it.
Rotate the OIDC secret and group-catalog token separately. Neither may appear in YAML, shell
history, logs, or diagnostic output.
+26 -57
View File
@@ -1,70 +1,39 @@
# Enrollment client per DWH REST
# DWH REST client enrollment
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.
The `dwh-auth` credential belongs to one ThothII installation and is needed only when the
workspace uses the `rest_api` transport.
| 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. |
| Trasporto | Materiale richiesto |
| --- | --- |
| `rest_api` | URL HTTPS, `API_KEY_FILE`, eventuale `TLS_CA_FILE` |
| `postgres_direct` | Credenziali PostgreSQL e configurazione TLS PostgreSQL |
| `ssh_tunnel` | Credenziali PostgreSQL e materiale SSH |
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.
## Delivery and storage
## Prerequisiti
Receive the key and CA through separate protected channels. Store the key in the installation
vault or in a regular file accessible only to the authorized account. Do not put it in Git, YAML
files, arguments, logs, or shared screens.
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.
## ACME Limited configuration
## Percorso GUI: vault dell'installazione
1. In **Workspace management**, eseguire **Update workspace repository** se necessario e
selezionare il workspace.
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 workspace connections**. Il controllo innocuo è `/rpc/ping`: atteso 2xx e
database/schema dichiarati.
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. 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:
Esempio di binding headless per il workspace `acme-ebikes`:
```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
THT_WS_ACME_EBIKES_DWH_TRANSPORT=rest_api
THT_WS_ACME_EBIKES_DWH_BASE_URL=https://dwh.acme.example/dwh/
THT_WS_ACME_EBIKES_DWH_API_KEY_FILE=/run/secrets/acme-ebikes-dwh-api-key
THT_WS_ACME_EBIKES_DWH_TLS_CA_FILE=/run/secrets/acme-ebikes-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:
The workspace suffix comes from the immutable ID, with hyphens changed to underscores and letters
converted to uppercase. `API_KEY_FILE` contains the mounted file path, not the key value.
```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
```
## Rotation and revocation
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).
During rotation, receive the new generation, update the vault or mounted file, and confirm
connectivity through the harmless `/rpc/ping` route. The server owner revokes the previous
generation only after this confirmation.
## Ping, rotazione e revoca
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`.
`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).
A `401` means the key is missing, unknown, expired, or revoked. A `503` means the authorization
service or registry is unavailable. In either case, do not bypass REST or weaken TLS verification.
+53 -346
View File
@@ -1,356 +1,63 @@
# `dwh-auth`: guida server
# `dwh-auth`: server guide
`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.
`dwh-auth` protects the REST `/dwh/` route with a separate key for each ThothII installation.
It runs as a separate Linux service, does not read DWH data, and does not connect directly to
PostgreSQL.
## 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
```mermaid
flowchart LR
CLIENT["Installazione ThothII"] -->|"X-API-Key"| NGINX["Nginx"]
NGINX --> AUTH["dwh-auth\nUnix socket"]
AUTH --> REGISTRY["Registro chiavi\nactive e revoked"]
AUTH -->|"authorized"| REST["DWH REST"]
```
Scegliere l'architettura corretta. Il template PSD usa il gruppo Nginx `www-data`; confermarlo
prima dell'installazione su un host diverso.
## Security boundaries
```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
```
- A key identifies an installation, not a person.
- Keys and backups stay in protected files and never enter Git, logs, arguments, or public JSON.
- The registry stores digests and metadata, never the key in plaintext.
- The REST route must be exposed only through verified TLS.
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.
## Installation
## Check, elenco e stato
Il servizio usa questi percorsi:
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
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.
```bash
installation_id=psd-mac-primary
description=operatore-mac-primario
key_output=/root/dwh-auth-provision/psd-mac-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.
```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
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 root-only `0600` del registro e copie protette delle sole
configurazioni coinvolte. L'archivio contiene digest, quindi è materiale riservato: custodirlo su
storage cifrato approvato; l'evidenza ammessa riporta 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 su storage cifrato approvato.
```bash
run_id=$(date -u +%Y%m%dT%H%M%SZ)
backup_root=/root/dwh-auth-provision
registry_root=/var/lib/dwh-auth
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
```
Il ripristino non sovrappone mai un tar al registro attivo. Estrarre prima in staging nello stesso
filesystem di `/var/lib`, verificare il candidato, rinominare il registro attuale in una copia
recuperabile e sostituirlo. Non cancellare il pre-ripristino: serve al rollback se `check` o
l'avvio falliscono.
```bash
registry_staging="/var/lib/.dwh-auth-restore-$run_id"
registry_candidate="$registry_staging/dwh-auth"
registry_previous="/var/lib/dwh-auth.pre-restore-$run_id"
if [ -e "$registry_staging" ] || [ -e "$registry_previous" ]; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
if ! sudo sha256sum -c "$manifest" >/dev/null; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
if ! sudo install -d -o root -g root -m 0700 "$registry_staging"; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
if ! sudo tar --acls --xattrs -C "$registry_staging" -xf "$registry_backup"; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
if ! sudo /usr/local/sbin/dwh-auth --registry-root "$registry_candidate" check; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
if ! sudo systemctl stop dwh-auth; then printf 'registry_restore=FAIL\n' >&2; exit 1; fi
if ! sudo mv -T -- "$registry_root" "$registry_previous"; then
if sudo systemctl start dwh-auth; then printf 'registry_restore_rollback=PASS\n' >&2; else printf 'registry_restore_rollback=FAIL\n' >&2; fi
exit 1
fi
if ! sudo mv -T -- "$registry_candidate" "$registry_root"; then
if ! sudo mv -T -- "$registry_previous" "$registry_root"; then printf 'registry_restore_rollback=FAIL\n' >&2; exit 1; fi
if sudo /usr/local/sbin/dwh-auth --registry-root "$registry_root" check && sudo systemctl start dwh-auth; then printf 'registry_restore_rollback=PASS\n' >&2; else printf 'registry_restore_rollback=FAIL\n' >&2; fi
exit 1
fi
if sudo /usr/local/sbin/dwh-auth --registry-root "$registry_root" check && sudo systemctl start dwh-auth; then
printf 'registry_restore=PASS\n'
else
sudo systemctl stop dwh-auth || true
if ! sudo mv -T -- "$registry_root" "$registry_staging/failed-dwh-auth"; then printf 'registry_restore_rollback=FAIL\n' >&2; exit 1; fi
if ! sudo mv -T -- "$registry_previous" "$registry_root"; then printf 'registry_restore_rollback=FAIL\n' >&2; exit 1; fi
if sudo /usr/local/sbin/dwh-auth --registry-root "$registry_root" check && sudo systemctl start dwh-auth; then printf 'registry_restore_rollback=PASS\n' >&2; else printf 'registry_restore_rollback=FAIL\n' >&2; fi
exit 1
fi
```
Per le prove, creare file header `0600` che contengono esattamente `X-API-Key: valore`. Il valore
passa dal file chiave al file header senza argv, ambiente o stdout. I file header sono materiale
segreto con la stessa custodia e retention delle chiavi.
```bash
v1_key_file="$key_output"
legacy_key_file=/root/dwh-auth-provision/legacy-shared.key
v1_header_file=/root/dwh-auth-provision/dwh-auth-v1.header
legacy_header_file=/root/dwh-auth-provision/dwh-auth-legacy.header
random_header_file=/root/dwh-auth-provision/dwh-auth-random.header
if ! sudo python3 -c '
import pathlib, sys
if any(b"\n" in pathlib.Path(path).read_bytes() for path in sys.argv[1:]):
raise SystemExit(1)
' "$v1_key_file" "$legacy_key_file"; then
printf 'key_file_bytes=FAIL\n' >&2
exit 1
fi
printf 'key_file_bytes=PASS\n'
for header_file in "$v1_header_file" "$legacy_header_file" "$random_header_file"; do
sudo install -o root -g root -m 0600 /dev/null "$header_file"
done
sudo sh -c '{ printf "%s" "X-API-Key: "; dd if="$1" bs=65536 status=none; printf "\n"; } > "$2"' sh "$v1_key_file" "$v1_header_file"
sudo sh -c '{ printf "%s" "X-API-Key: "; dd if="$1" bs=65536 status=none; printf "\n"; } > "$2"' sh "$legacy_key_file" "$legacy_header_file"
sudo sh -c 'printf "%s\n" "X-API-Key: invalid-test" > "$1"' sh "$random_header_file"
```
Il socket `/verify` deve restituire 204 per v1 e legacy durante il dual-key, 401 per file casuale
e richiesta senza header. Stampare solo PASS/FAIL.
```bash
status=$(sudo curl --header "@$v1_header_file" --unix-socket /run/dwh-auth/verify.sock --output /dev/null --silent --show-error --write-out '%{http_code}' http://localhost/verify)
[ "$status" = 204 ] && printf 'socket_v1=PASS\n' || { printf 'socket_v1=FAIL\n' >&2; exit 1; }
status=$(sudo curl --header "@$legacy_header_file" --unix-socket /run/dwh-auth/verify.sock --output /dev/null --silent --show-error --write-out '%{http_code}' http://localhost/verify)
[ "$status" = 204 ] && printf 'socket_legacy=PASS\n' || { printf 'socket_legacy=FAIL\n' >&2; exit 1; }
status=$(sudo curl --header "@$random_header_file" --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 i file header protetti e la CA approvata contro `/dwh/rpc/ping`: PostgREST
può restituire qualsiasi 2xx, non si pretende 204. Prima della revoca, v1 e legacy devono dare
2xx; il file casuale deve dare 401.
```bash
ping_url=https://supabase-aritmolab.policlinicosandonato.it/dwh/rpc/ping
ca_file=/root/dwh-auth-provision/psd-dwh-ca.pem
status=$(sudo curl --header "@$v1_header_file" --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_v1_pre_revoke=PASS\n' ;; *) printf 'https_v1_pre_revoke=FAIL\n' >&2; exit 1 ;; esac
status=$(sudo curl --header "@$legacy_header_file" --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_legacy_pre_revoke=PASS\n' ;; *) printf 'https_legacy_pre_revoke=FAIL\n' >&2; exit 1 ;; esac
status=$(sudo curl --header "@$random_header_file" --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; }
```
Dopo l'osservazione, revocare solo la legacy usando il suo ID pubblico già registrato. Dopo la
revoca v1 resta 2xx e legacy diventa 401 anche via HTTPS.
```bash
legacy_key_id=legacy-shared
sudo /usr/local/sbin/dwh-auth --registry-root "$registry_root" key revoke --key-id "$legacy_key_id" --reason shared-credential-rotation
status=$(sudo curl --header "@$v1_header_file" --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_v1_post_revoke=PASS\n' ;; *) printf 'https_v1_post_revoke=FAIL\n' >&2; exit 1 ;; esac
status=$(sudo curl --header "@$legacy_header_file" --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_legacy_post_revoke=PASS\n' || { printf 'https_legacy_post_revoke=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 --header "@$random_header_file" --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: controlla davvero le chiavi v1 e legacy leggendo solo i
percorsi dei file da argv, e conserva anche la difesa generica per prefisso e digest. Il filtro
emette solo PASS/FAIL.
```bash
since=$(date -u -d '15 minutes ago' +%Y-%m-%dT%H:%M:%SZ)
if sudo python3 -c '
import pathlib, subprocess, sys
max_journal_bytes = 1_048_576
max_journal_lines = 10_000
max_chunk_bytes = 65_536
process = None
try:
actual_keys = {pathlib.Path(path).read_bytes() for path in sys.argv[2:]}
needles = (b"thtdwh_v1", b"secret_sha256", *actual_keys)
max_needle_length = max(map(len, needles))
process = subprocess.Popen(
["journalctl", "-u", "dwh-auth", "--since", sys.argv[1], "--no-pager", "--output=cat"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
except OSError:
raise SystemExit(2)
def stop_child():
if process is not None:
if process.poll() is None:
process.kill()
process.wait()
bytes_seen = 0
line_count = 0
line_open = False
carry = b""
try:
while True:
remaining = max_journal_bytes - bytes_seen
if remaining == 0:
if process.stdout.read1(1):
raise SystemExit(2)
break
chunk = process.stdout.read1(min(max_chunk_bytes, remaining))
if not chunk:
break
bytes_seen += len(chunk)
searchable = carry + chunk
if any(needle in searchable for needle in needles):
raise SystemExit(1)
carry = searchable[-(max_needle_length - 1):]
for byte in chunk:
if byte == 10:
line_count += 1
line_open = False
if line_count > max_journal_lines:
raise SystemExit(2)
else:
line_open = True
if line_open:
line_count += 1
if line_count > max_journal_lines:
raise SystemExit(2)
finally:
stop_child()
if process.returncode != 0:
raise SystemExit(2)
' "$since" "$v1_key_file" "$legacy_key_file"; then
printf 'journal_actual_key_scan=PASS\n'
else
printf 'journal_actual_key_scan=FAIL\n' >&2
exit 1
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, manifest e file header protetti per la retention; non cancellarli durante il
rollback.
## Troubleshooting
| Sintomo | Interpretazione e azione |
| Oggetto | Percorso |
| --- | --- |
| `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. |
| Binario | `/usr/local/sbin/dwh-auth` |
| Unit systemd | `/etc/systemd/system/dwh-auth.service` |
| Registro | `/var/lib/dwh-auth/` |
| Socket | `/run/dwh-auth/verify.sock` |
| Consegne protette | `/root/dwh-auth-provision/` |
Per il rollout PSD con i due gate separati vedere il [runbook PSD](../operations/psd-dwh-auth-rollout.md).
Install the binary and unit with `root` ownership, create the `dwh-auth` service user, and enable
the unit with `systemctl enable --now dwh-auth`. The socket must be accessible to Nginx's group.
## Creating and revoking keys
Esempio per l'installazione ACME Limited:
```bash
sudo dwh-auth --registry-root /var/lib/dwh-auth key create \
--installation-id acme-factory-primary \
--description acme-factory-primary \
--output /root/dwh-auth-provision/acme-factory-primary.key
```
Deliver the file through an enterprise vault or an authenticated channel. To rotate a key, create
a new one, distribute it, update the client, and revoke the old one using its public ID:
```bash
sudo dwh-auth --registry-root /var/lib/dwh-auth key revoke \
--key-id PUBLIC_KEY_ID \
--reason scheduled-rotation
```
Revocation is permanent. Keep encrypted registry backups before every mutation.
## Nginx integration
Nginx forwards the key to the `dwh-auth` socket. Only an authorized response allows the request
to reach DWH REST. Missing, unknown, expired, or revoked keys receive `401`; an unavailable
service or registry produces `503`.
+27 -38
View File
@@ -1,49 +1,38 @@
# TLS per DWH REST
# TLS for DWH REST
La chiave DWH è accettabile solo sopra TLS verificato. Un errore `401` o `503` non autorizza mai a
ridurre la verifica del certificato.
The DWH key may be used only over verified TLS. Authorization or availability errors never justify
disabling certificate verification.
## Stato PSD
## Private CA
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.
When DWH REST uses an enterprise CA, deliver the certificate separately from the API key. The CA
is not a credential, but its integrity is part of the security boundary. Keep it out of Git and
make it unwritable by unauthorized users.
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 è:
Esempio ACME Limited:
```dotenv
THT_WS_PSD_CLINICAL_DWH_TLS_CA_FILE=/run/secrets/psd-clinical-dwh-ca.pem
THT_WS_ACME_EBIKES_DWH_TLS_CA_FILE=/run/secrets/acme-ebikes-dwh-ca.pem
```
Il file sorgente locale è collegato da file operatore non tracciato. Usare URL `.it`, poi
**Test workspace connections** su `/rpc/ping`. Non disabilitare TLS e non usare `curl -k`.
## Out-of-band fingerprint
## Rinnovo coordinato
Calculate the fingerprint of the received file and compare it through an independent channel:
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.
```bash
openssl x509 -noout -fingerprint -sha256 \
-in /absolute/protected/acme-ebikes-dwh-ca.pem
```
Il rinnovo non modifica chiavi `dwh-auth`, record o ruoli PostgreSQL. TLS e rollback della route
restano approvazioni e backup distinti.
The certificate SAN must include the exact name used by the binding, such as `dwh.acme.example`.
## Renewal
1. Prepare the new certificate and chain.
2. Confirm the SAN and fingerprint out of band.
3. Distribute the new CA to clients while temporarily keeping the old one.
4. Update the binding and confirm connectivity with normal TLS.
5. Install the server certificate.
6. Remove the old trust after the agreed window.
Do not use `curl -k`, disable TLS, or embed complete certificates or fingerprints in shared documents.