SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor

Secrets manager self-hosted: confronto per un VPS

Confronto tra OpenBao, Infisical, SOPS con age, credenziali systemd e file env protetto: costi, rischi e scelta più adatta a un solo VPS.

Cosa fa un secrets manager self-hosted che un password manager non fa

Un secrets manager self-hosted fornisce le credenziali ai processi. Un password manager fornisce le credenziali alle persone. Tutto il resto deriva da questa differenza. Un password manager viene sbloccato da una persona presente e attenta. Un secrets manager deve fornire alla tua applicazione la password di un database alle 03:00, quando nessuno è sveglio.

Le modalità di errore sono diverse, e questo è importante. Un password manager bloccato è un inconveniente: inserisci di nuovo la password principale. Un secrets manager sigillato causa un'interruzione del servizio: ogni servizio che viene riavviato mentre il secrets manager è sigillato si avvia senza credenziali e rimane fermo. Eseguire Vaultwarden come password manager personale risolve bene il problema degli utenti. Non risolve il problema delle macchine, e non è mai stato progettato per farlo.

Le opzioni realistiche per un solo server rientrano in due gruppi. OpenBao e Infisical sono servizi: un'API, un database, TLS (transport layer security), una fase di accesso e un processo che ora devi mantenere attivo. SOPS con age, le credenziali di systemd e i Docker secrets sono file: cifrati a riposo, decifrati da un componente già in esecuzione, senza nulla di aggiuntivo da monitorare.

Ecco subito la risposta più precisa. Per un singolo server usato da una o due persone, le opzioni basate sui file sono generalmente la scelta corretta. Un'istanza OpenBao che nessuno sigilla correttamente e nessuno sottopone a rotazione è peggiore di un file env con permessi mode 600, perché aggiunge un componente che può guastarsi e un backup che probabilmente configurerai in modo errato, senza offrire alcuna rotazione che non stessi già eseguendo manualmente.

Un file di ambiente con modalità 600 è sufficiente?

Spesso sì. La minaccia che contrasta è che un altro utente del server legga la password del database. I permessi dei file Unix impediscono questa lettura prima ancora che la rete sia attiva.

sudo install -d -m 750 -o root -g myapp /etc/myapp
sudo install -m 640 -o root -g myapp /dev/null /etc/myapp/env
sudoedit /etc/myapp/env

Verificatelo da entrambi i lati:

sudo -u myapp cat /etc/myapp/env
sudo -u nobody cat /etc/myapp/env

Il primo comando stampa il contenuto del file. Il secondo stampa cat: /etc/myapp/env: Permission denied, perché nobody non appartiene al gruppo myapp e il file non concede alcun permesso agli altri utenti. Questo è l'intero modello di sicurezza, ed è un modello effettivo.

La perdita di riservatezza avviene subito dopo. Un'unità systemd con EnvironmentFile= copia quei valori nell'ambiente del processo, che è leggibile.

[Service]
User=myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/local/bin/myapp
sudo cat /proc/$(pgrep -n myapp)/environ | tr '\0' '\n'

Questo comando stampa i secret in chiaro, perché /proc/<pid>/environ è leggibile da root e dall'utente con cui viene eseguito il processo. Anche un crash reporter che allega l'ambiente a un report vede gli stessi valori. Lo stesso vale per qualsiasi strumento eseguito con lo stesso account. Per questo tenere i secret fuori dagli agenti AI inizia dal rimuoverli dall'ambiente. Abbinate al file un utente di servizio dedicato con privilegi ridotti, in modo che "l'utente con cui viene eseguito il processo" non sia root.

SOPS con age: secret crittografati che puoi sottoporre a commit in git

SOPS (secrets operations) crittografa i valori in un file YAML o JSON e lascia le chiavi in chiaro. age è uno strumento di crittografia compatto che usa una sola coppia di chiavi e non richiede un key server. Insieme consentono di sottoporre a commit secrets.enc.yaml insieme al codice; git diff continua a indicare quale impostazione è cambiata senza rivelare a chi legge il nuovo valore.

age è incluso nei pacchetti di Ubuntu 24.04. SOPS non lo è, quindi scarica .deb dalla pagina delle release. La versione 3.13.3 era quella corrente ad agosto 2026.

sudo apt update && sudo apt install -y age
curl -LO https://github.com/getsops/sops/releases/download/v3.13.3/sops_3.13.3_amd64.deb
sudo apt install -y ./sops_3.13.3_amd64.deb
sops --version

Genera una coppia di chiavi. age-keygen scrive la chiave privata nel file e stampa la chiave pubblica, quindi vedrai una riga che inizia con Public key: age1....

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
age-keygen -y ~/.config/sops/age/keys.txt

Inserisci la chiave pubblica in .sops.yaml nella radice del repository, così non dovrai ricordare il destinatario sulla riga di comando.

creation_rules:
  - age: age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23c
sops encrypt secrets.yaml > secrets.enc.yaml
sops decrypt secrets.enc.yaml

Una regola senza path_regex corrisponde a tutto, ed è ciò che serve inizialmente. Se in seguito ne aggiungi una, scrivila in modo che corrisponda al file passato a sops, perché le regole vengono verificate rispetto al percorso di input e non rispetto al file verso cui reindirizzi l'output.

A runtime, passa i valori a un solo processo e a nessun altro:

sops exec-env secrets.enc.yaml './myapp'

sops exec-env decrittografa i valori in memoria e li imposta nell'ambiente del processo figlio, quindi non scrive testo in chiaro su disco. Al processo figlio si applica comunque la limitazione relativa all'ambiente descritta nella sezione precedente.

In questo punto ci sono due problemi ricorrenti. L'errore Failed to get the data key required to decrypt the SOPS file con systemd significa quasi sempre che SOPS ha cercato nella home directory sbagliata, perché un'unità non eredita il tuo HOME. Imposta esplicitamente il percorso con Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt nell'unità. Inoltre, modificare .sops.yaml non ricrittografa ciò che esiste già: aggiungere la chiave pubblica di un collega ha effetto solo sui nuovi file, quindi esegui sops updatekeys secrets.enc.yaml su ogni file esistente. Se la configurazione passa già da Ansible, crittografare gli stessi valori con Ansible Vault porta allo stesso risultato senza introdurre un secondo strumento.

Credenziali systemd: secret che non finiscono mai nell'ambiente

Ubuntu 24.04 include systemd 255, quindi non è necessaria alcuna installazione. systemd-creds cifra un secret sull'host e systemd lo decifra in una directory privata leggibile soltanto dal servizio interessato.

sudo systemd-creds setup
sudo install -d -m 700 /etc/myapp
echo -n 'hunter2' | sudo systemd-creds encrypt --name=db_password - /etc/myapp/db_password.cred
[Service]
User=myapp
LoadCredentialEncrypted=db_password:/etc/myapp/db_password.cred
ExecStart=/usr/local/bin/myapp

Il servizio legge il valore da un file denominato db_password all'interno della directory indicata da $CREDENTIALS_DIRECTORY. Il valore non è presente nell'ambiente, quindi /proc/<pid>/environ non mostra informazioni utili e il testo in chiaro non viene mai scritto nel filesystem root.

Verifica che il file venga decifrato prima di associarlo a una unit:

sudo systemd-creds decrypt /etc/myapp/db_password.cred -

Devi sapere quale chiave lo ha cifrato, perché da questo dipende l'utilità del backup. Per impostazione predefinita, --with-key=auto usa il chip TPM2 (trusted platform module versione 2) quando è presente e utilizzabile; in caso contrario usa la chiave dell'host. La maggior parte delle istanze VPS non dispone di TPM2.

systemd-analyze has-tpm2

no indica che è stata usata la chiave dell'host. Questa chiave si trova in /var/lib/systemd/credential.secret ed è leggibile soltanto da root. Se ripristini db_password.cred su una VPS nuova senza quel file, nessuno potrà mai decifrarlo. Copia credential.secret nello stesso backup oppure conserva il testo in chiaro in un luogo ancora accessibile.

Secret Docker: file in /run/secrets

Compose legge un file dall'host e lo monta nel container in /run/secrets/<name>.

services:
  app:
    image: myapp:latest
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt
docker compose exec app cat /run/secrets/db_password
docker compose exec app env | grep -i password

Il primo comando stampa il secret. Il secondo stampa soltanto DB_PASSWORD_FILE=/run/secrets/db_password, ed è questo il punto: il valore non è mai presente nell'ambiente del container, quindi non compare nell'output di docker inspect. Molte immagini ufficiali prevedono già questa struttura e l'immagine Postgres legge POSTGRES_PASSWORD_FILE esattamente in questo modo.

È importante capire di cosa si tratta. Al di fuori della modalità Swarm non esiste alcuna crittografia a nessun livello: ./db_password.txt è un file in chiaro sull'host e l'unica protezione è costituita dai suoi permessi e dal proprietario. Impostateli entrambi manualmente, perché Compose monterà senza segnalazioni anche un file leggibile da tutti. L'insieme più ampio dei compromessi rispetto alla semplice scorciatoia env_file è descritto nella guida ai file env e ai secret di Compose.

Il costo reale di gestione di OpenBao e Vault

OpenBao è il fork di HashiCorp Vault gestito dalla Linux Foundation, avviato dopo che nel 2023 HashiCorp ha rilasciato Vault con la Business Source License. OpenBao resta sotto la MPL 2.0 (Mozilla Public License). La versione 2.6.2 era quella corrente ad agosto 2026. Quasi tutto ciò che segue si applica anche a Vault, perché il fork ha mantenuto la stessa interfaccia dei comandi.

docker pull docker.io/openbao/openbao

I pacchetti per Debian e Ubuntu sono disponibili nella pagina dei download di OpenBao, se si preferisce usare apt per gestire gli aggiornamenti. Il server richiede un file di configurazione che definisca un listener e un backend di storage:

listener "tcp" {
  address       = "127.0.0.1:8200"
  tls_cert_file = "/path/to/full-chain.pem"
  tls_key_file  = "/path/to/private-key.pem"
}

storage "raft" {
  path = "/path/to/raft/data"
  node_id = "raft_node_1"
}

Quindi avviarlo una volta:

bao operator init

Per impostazione predefinita, il comando divide la chiave root in 5 parti e ne richiede 3 per eseguire l'unseal. Questi sono i flag -key-shares e -key-threshold. Il comando stampa le parti e il token root iniziale una sola volta e non li mostra più.

Questa è la parte che la maggior parte dei confronti omette. Un server riavviato è un server sealed. OpenBao mantiene la chiave root soltanto in memoria. Dopo un riavvio, quindi, non può decrittografare il proprio storage finché qualcuno non fornisce il numero necessario di parti. Un aggiornamento del kernel o una terminazione per esaurimento della memoria lasciano quindi il server sealed e impediscono alle applicazioni di autenticarsi.

Su un VPS gestito da una sola persona, la suddivisione Shamir non protegge nulla, perché tutte e 5 le parti finiscono nello stesso password manager della stessa persona. L'auto-unseal sposta la chiave su un dispositivo o servizio considerato attendibile. In un cloud di grandi dimensioni questo significa in genere usare un servizio gestito di gestione delle chiavi; sul proprio VPS, invece, di solito significa usare un file di chiavi sullo stesso disco dei dati che protegge. Si tratta di una riduzione reale della sicurezza, accettata in cambio di un server che torna operativo automaticamente dopo un riavvio. Effettuare questa scelta consapevolmente e annotare quale opzione è stata adottata.

Infisical: un'interfaccia, un database e una master key che devi ancora conservare

Infisical è una piattaforma per la gestione dei secret con interfaccia web, progetti, ambienti e controllo degli accessi per utente. L'installazione self-hosted con Compose è breve:

curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
docker compose -f docker-compose.prod.yml up -d

Modifica .env prima di eseguire l'ultimo comando. Due valori devono essere specifici della tua installazione e uno di questi non deve mai cambiare in seguito:

openssl rand -hex 16
openssl rand -base64 32

Il primo è ENCRYPTION_KEY, una stringa esadecimale di 16 byte. È la chiave con cui i secret vengono cifrati all'interno di PostgreSQL. Se la perdi, un backup integro del database diventa una raccolta di ciphertext inutilizzabile. Se la cambi su un'istanza in esecuzione, i secret esistenti non possono più essere decifrati. Il secondo è AUTH_SECRET, una stringa base64 di 32 byte utilizzata per le sessioni. SITE_URL deve essere l'URL assoluto che utilizzerai realmente per raggiungere il servizio, incluso il protocollo, altrimenti il redirect di login non funziona.

Infisical è più adatto di OpenBao quando ti servono soprattutto utenti: un'interfaccia web per un piccolo team e la separazione tra gli ambienti, invece di credenziali del database con scadenza automatica. Richiede PostgreSQL, Redis e un certificato TLS, che dovrai mantenere aggiornati e includere nei backup.

Cosa succede quando il servizio dei secret non è disponibile e l’applicazione viene riavviata

Questa domanda determina se un servizio per la gestione dei secret può risiedere su un singolo server. I file sono leggibili prima dell’avvio della rete. Un servizio no.

Riavviate il server: l’applicazione e OpenBao si avviano nello stesso momento. L’applicazione richiede la password del database, OpenBao è ancora sigillato, la richiesta fallisce e systemd riavvia l’applicazione in un ciclo continuo, finché un amministratore non inserisce manualmente le parti necessarie per l’unseal. Non c’è alcun guasto. Ma non è attivo nulla.

Esistono due modalità corrette per gestire il problema. Ordinate le unità e consentite all’applicazione di riprovare: After= il servizio per la gestione dei secret, oltre a Restart=on-failure e a un RestartSec= sufficientemente lungo da evitare di sovraccaricare l’API. In alternativa, recuperate il secret durante il deploy invece che all’avvio: generate il secret in un file con permessi 600 o in una systemd credential, in modo che il sistema in esecuzione dipenda da un file e non da un’API.

La scadenza dei token è lo stesso problema, ma con un intervallo più lungo. I token e i lease di OpenBao hanno un time to live; pertanto, un processo di lunga durata che non rinnova l’accesso lo perde in un momento non correlato ad alcun deploy. Questo errore è difficile da diagnosticare proprio perché quel giorno non è cambiato nulla.

Backup dell’archivio dei secret

Ogni opzione qui descritta utilizza una chiave, e un backup senza quella chiave non è utilizzabile. Annotate dove si trova la vostra.

Per un file env, il file stesso è il secret, quindi il backup deve essere cifrato. Con SOPS, il file cifrato può essere archiviato in qualsiasi posizione pubblica; la chiave privata age in ~/.config/sops/age/keys.txt è l’elemento che non dovete perdere. Per le credenziali systemd, eseguite il backup di /var/lib/systemd/credential.secret insieme ai file .cred. Per Infisical, create un dump PostgreSQL e archiviate ENCRYPTION_KEY in una posizione separata.

OpenBao con storage raft crea uno snapshot dedicato:

bao operator raft snapshot save backup.snap
bao operator raft snapshot restore backup.snap

Lo snapshot contiene il vostro storage cifrato, quindi per ripristinarlo su un nuovo server servono comunque le unseal share di bao operator init. Un job notturno che copia gli snapshot nello storage a oggetti, mentre le share non sono archiviate da nessuna parte, non costituisce un backup. Testate il ripristino su un VPS usa e getta prima di farvi affidamento.

Audit logging: chi ha letto quale secret

I file non forniscono alcuna traccia di audit. La modalità e il proprietario indicano chi potrebbe leggere il secret. Non indicano mai chi lo ha letto. auditd con il monitoraggio del percorso è il sostituto più vicino, ma segnala che un file è stato aperto, non quale valore è stato utilizzato.

OpenBao registra ogni richiesta su un audit device che abiliti esplicitamente:

bao audit enable file file_path=/var/log/openbao_audit.log

Il log introduce due aspetti che cambiano il modo di eseguire il server. La maggior parte delle stringhe nelle richieste e nelle risposte viene sottoposta a hashing con HMAC-SHA256 e un salt. In questo modo puoi confrontare con il log un valore che già conosci, senza che il log contenga il valore in chiaro. Interi e valori booleani vengono scritti in chiaro, quindi un secret numerico non beneficia di questa protezione tramite hashing.

C'è poi un problema operativo importante: OpenBao non risponde alle richieste quando nessun audit device abilitato è in grado di registrarle. Inoltre, un device che si blocca fa rimanere in attesa le richieste finché qualcuno non risolve il problema. Un disco pieno su /var/log interrompe intenzionalmente le API dei secret. Dedica all'audit log uno spazio separato e configura una regola di logrotate il primo giorno, non dopo il primo outage.

Quale gestore di secret self-hosted conviene eseguire?

Contate le macchine e le persone, quindi scegliete.

  1. Una macchina, una persona: un file di ambiente in modalità 600, di proprietà di root e leggibile da un utente di servizio. Aggiungete le credenziali di systemd quando volete che il valore non sia presente nell'ambiente del processo.
  2. Una macchina, da due a cinque persone, configurazione già presente in git: SOPS con age. Ogni persona riceve una coppia di chiavi e .sops.yaml elenca ogni chiave pubblica autorizzata a eseguire la decrittografia.
  3. Più macchine, un repository di configurazione, nessuna necessità di credenziali con scadenza: ancora SOPS con age, con una chiave destinataria per host, in modo che una chiave host sottratta possa decrittografare soltanto i file di quell'host.
  4. Più macchine e più team che hanno effettivamente bisogno di credenziali del database con una durata definita, oltre a un audit trail che qualcuno consulta: OpenBao. Prevedete nel budget un'ora al mese di lavoro operativo per le procedure di unsealing e le esercitazioni di ripristino.

La regola alla base di tutti e quattro i casi è la stessa. Eseguite la soluzione più semplice che soddisfa un requisito formulabile chiaramente, perché un gestore di secret non disponibile è indistinguibile da un gestore di secret vuoto.

FAQ

Vale la pena usare un secrets manager self-hosted su un singolo VPS?

Di solito no, se intendi un servizio come OpenBao o Infisical. Su un solo server con una o due persone, un file env con permessi mode 600 o una credenziale cifrata di systemd offrono la stessa protezione contro un altro utente locale, senza una procedura di unseal e senza un servizio aggiuntivo da aggiornare. Un secrets service diventa utile quando hai più macchine e più persone, oppure quando hai una reale necessità di credenziali con scadenza automatica, senza doverle ruotare manualmente.

Qual è la differenza tra un password manager e un secrets manager?

Un password manager memorizza le credenziali che una persona digita e viene sbloccato da un utente umano quando è presente. Un secrets manager fornisce le credenziali ai processi, quindi deve funzionare alle 03:00 senza che nessuno lo controlli. La differenza principale è questa: un password manager bloccato richiede di nuovo la password principale, mentre un secrets manager sigillato arresta ogni servizio che viene riavviato mentre è sealed.

Cosa succede alle mie applicazioni se OpenBao è sealed dopo un riavvio?

Non possono recuperare i secret, quindi non si avviano e systemd le riavvia in un ciclo continuo finché qualcuno non fornisce la soglia di unseal, che per impostazione predefinita è 3 di 5 share. OpenBao mantiene la root key soltanto in memoria, quindi ogni riavvio lo rende nuovamente sealed. Puoi attivare auto unseal, accettando che su un singolo VPS la chiave di unseal finisca sullo stesso disco dei dati, oppure generare i secret in un file durante il deploy, così l'avvio non dipende mai dall'API.

Posso eseguire il commit di file cifrati con SOPS in un repository pubblico?

I valori sono cifrati, quindi sono al sicuro da chiunque non disponga della chiave privata age. Le chiavi non sono cifrate: un lettore può vedere che possiedi STRIPE_SECRET_KEY e SMTP_PASSWORD e con quale frequenza ciascuna cambia. Per la maggior parte dei progetti questi metadati sono accettabili, ma per alcuni non lo sono. Mantieni la chiave privata age fuori dal repository ed esegui sops updatekeys su ogni file esistente ogni volta che aggiungi o rimuovi un destinatario.