Gestione Sicura delle Variabili d'Ambiente nelle Unità di Servizio Systemd

Configura le variabili d'ambiente di systemd con Environment, EnvironmentFile, drop-in e una gestione più sicura dei segreti.

Gestione Sicura delle Variabili d'Ambiente nelle Unità di Servizio Systemd

Le variabili d'ambiente sono comode, ma non sono automaticamente private. In un servizio systemd, possono apparire nei file di unità, nei drop-in, in systemctl show, negli strumenti di ispezione dei processi, nei rapporti di crash, nei log di debug o nei bundle di supporto copiati. Ciò non significa che non si possano mai usare. Significa che bisogna essere intenzionali su cosa inserirvi e su chi può leggere i file che le definiscono.

Questa guida copre le due direttive comuni, Environment= e EnvironmentFile=, poi mostra come usare i drop-in in modo che la configurazione locale rimanga separata dalle unità gestite dal pacchetto.


Il Ruolo delle Variabili d'Ambiente in Systemd

Le variabili d'ambiente forniscono un modo diretto per configurare un servizio senza modificarne il codice. Quando systemd avvia un servizio, costruisce l'ambiente del processo e applica le variabili definite nell'unità prima di eseguire ExecStart=.

Systemd fornisce due direttive principali all'interno della sezione [Service] di un file di unità per gestire queste variabili.

1. Definizione Diretta: La Direttiva Environment

Questo metodo permette di definire le variabili direttamente all'interno del file di unità di Systemd. È adatto per parametri di configurazione non sensibili che cambiano raramente.

Utilizzo e Sintassi

La direttiva Environment accetta una lista separata da spazi di assegnazioni di variabili nel formato "CHIAVE=VALORE".

# /etc/systemd/system/mio-app.service

[Unit]
Description=Servizio della Mia Applicazione

[Service]
User=myuser
WorkingDirectory=/opt/mio-app

# Definisci le variabili direttamente nel file di unità
Environment="APP_PORT=8080" "NODE_ENV=production"

ExecStart=/usr/local/bin/mio-app --start

[Install]
WantedBy=multi-user.target

Limitazioni e Sicurezza

Sebbene comoda, la direttiva Environment è un posto inadeguato per password, token o credenziali del database. I file di unità sono spesso memorizzati nei sistemi di gestione della configurazione, copiati nei ticket o leggibili dagli operatori che devono ispezionare il comportamento del servizio ma non dovrebbero vedere i segreti. Usala per valori come porte, flag di funzionalità, livelli di log e percorsi.

2. Configurazione Esterna: La Direttiva EnvironmentFile

Per configurazioni più grandi, caricare le variabili da un file esterno è solitamente più pulito. Permette di gestire i permessi del file delle variabili indipendentemente dal file di unità principale. Mantiene anche leggibili le unità fornite dal pacchetto mentre le impostazioni locali risiedono in /etc.

Utilizzo e Sintassi

La direttiva EnvironmentFile accetta un percorso assoluto a un file di configurazione. Systemd legge questo file riga per riga, trattando ogni riga come una potenziale assegnazione CHIAVE=VALORE.

[Service]
# Carica le variabili da un file esterno
EnvironmentFile=/etc/config/impostazioni-mio-app.conf

ExecStart=/usr/local/bin/mio-app --start

Formato del File Ambiente

Il file esterno deve aderire a un formato semplice simile a shell:

  • Le righe che iniziano con # sono trattate come commenti.
  • Le righe che iniziano con un'assegnazione di variabile vuota (VAR=) cancelleranno la variabile se era stata impostata in precedenza.
  • Le variabili sono definite come CHIAVE=VALORE.
  • L'uso di virgolette per il valore (CHIAVE="VALORE CON SPAZI") è supportato.
# /etc/config/impostazioni-mio-app.conf

# Variabili non sensibili
MAX_WORKERS=4
LOG_LEVEL=INFO

# Variabile sensibile (richiede permessi file rigorosi e attento controllo degli accessi)
DB_PASSWORD=SecureRandomString12345

Evita abitudini di shell che il parser dei file ambiente di systemd non supporta nel modo che ti aspetti. Non scrivere export CHIAVE=valore. Non mettere spazi attorno al segno di uguale. Se il valore contiene spazi, mettilo tra virgolette. Se il valore contiene virgolette letterali, barre rovesciate o nuove righe, testalo prima di farci affidamento in produzione.

Gestione dei File Mancanti

Per impostazione predefinita, se il file specificato da EnvironmentFile non esiste, Systemd farà fallire l'avvio del servizio. Se il file ambiente è opzionale, puoi prefissare il percorso del file con un trattino (-):

EnvironmentFile=-/etc/config/impostazioni-opzionali.conf

Se il file è prefissato con -, Systemd ignorerà gli errori causati dall'assenza del file.

Buona Pratica: Usare Unità Drop-in per Dati Sensibili

Modificare il file di unità principale (ad es., /usr/lib/systemd/system/mio-app.service) è generalmente sconsigliato, specialmente se il file è gestito da un gestore di pacchetti. Invece, usa file di unità drop-in per applicare sovrascritture o aggiunte alla configurazione.

Questa pratica è importante perché separa i valori predefiniti del fornitore dalla configurazione locale. Rende anche più facili gli audit: l'unità dice da dove viene caricata la configurazione, e i permessi su quel file dicono chi può leggerla.

Configurazione Drop-in Passo dopo Passo

1. Trova/Crea la Directory Drop-in

Per un servizio chiamato mio-app.service, la directory drop-in deve essere chiamata mio-app.service.d/ e risiedere nella gerarchia /etc/systemd/system/.

sudo mkdir -p /etc/systemd/system/mio-app.service.d/

2. Crea la Sovrascrittura di Configurazione

Crea un file all'interno della directory drop-in (ad es., secrets.conf). Questo file ha bisogno solo della sezione [Service] e delle direttive specifiche che desideri sovrascrivere o aggiungere.

# /etc/systemd/system/mio-app.service.d/secrets.conf

[Service]
# Carica il file delle credenziali sicure
EnvironmentFile=/etc/secrets/credenziali-mio-app.env

3. Proteggi il File Ambiente Esterno

Questo è il passo di sicurezza più critico. Assicurati che il file esterno contenente i segreti abbia permessi restrittivi. Idealmente, dovrebbe essere di proprietà di root:root e leggibile solo dall'utente root o dall'utente del servizio stesso.

# Crea il file dei segreti
sudo touch /etc/secrets/credenziali-mio-app.env

# Popola il file con i segreti
sudo sh -c 'echo "DB_PASS=S3cr3tP@ssw0rd" >> /etc/secrets/credenziali-mio-app.env'

# Imposta permessi restrittivi
sudo chmod 600 /etc/secrets/credenziali-mio-app.env

Se il file referenziato da EnvironmentFile contiene credenziali, mantienilo leggibile solo dall'account che deve gestire il servizio. 0600 root:root è comune quando systemd legge il file prima di abbassare i privilegi con User=, ma alcuni modelli operativi usano un gruppo dedicato di proprietà di root e 0640. La parte importante è che gli utenti ordinari non possano leggere il file.

Sii anche onesto riguardo al rischio rimanente. Le variabili d'ambiente sono più facili da gestire rispetto agli argomenti della riga di comando hardcoded, ma non sono ancora un sistema completo di gestione dei segreti. Per credenziali a rischio più elevato, considera un archivio segreti dedicato, credenziali a breve durata, credenziali systemd su distribuzioni più recenti o un meccanismo specifico dell'applicazione che legge direttamente un file protetto.

Risoluzione dei Problemi e Verifica

Dopo aver apportato qualsiasi modifica ai file di unità o ai drop-in, devi ricaricare la configurazione del gestore Systemd.

sudo systemctl daemon-reload
sudo systemctl restart mio-app.service

Per verificare quali variabili d'ambiente sono state caricate con successo da Systemd per un servizio in esecuzione, usa il comando systemctl show e interroga specificamente la proprietà Environment:

systemctl show mio-app.service --property=Environment

Output di Esempio (che mostra le variabili caricate):

Environment=APP_PORT=8080 NODE_ENV=production DB_PASS=S3cr3tP@ssw0rd

Questo comando è utile per il debug, ma è anche un promemoria: chiunque sia autorizzato a eseguire i giusti comandi di ispezione come root può vedere i valori. Non incollare questo output in chat condivise, ticket o rapporti di bug pubblici senza oscurarlo.

Se il servizio non si avvia, controlla i log del servizio usando journalctl -xeu mio-app.service. Le ragioni comuni di fallimento relative alle variabili d'ambiente includono:

  1. Percorso del file errato in EnvironmentFile.
  2. File mancante (e il percorso non era prefissato con -).
  3. Sintassi della variabile errata nel file ambiente esterno (ad es., spazi attorno al segno =).

Schemi Pratici che Funzionano

Scenario Direttiva da Usare Buona Pratica di Posizione Considerazioni sulla Sicurezza
Configurazione Statica, Non Sensibile Environment File di unità diretto o drop-in Rischio di sicurezza basso.
Credenziali Sensibili (Segreti) EnvironmentFile File esterno, referenziato tramite un drop-in (*.service.d/) CRITICO: Il file ambiente deve avere permessi 0600.
Modularità e Sovrascritture EnvironmentFile File di unità drop-in Separa la configurazione dai valori predefiniti del fornitore.

Sfruttando la direttiva EnvironmentFile all'interno di un'unità drop-in dedicata e assicurando permessi file rigorosi, gli amministratori possono gestire in modo sicuro e flessibile le configurazioni dei servizi, aderendo ai principi del minimo privilegio e della separazione delle responsabilità.

Per un piccolo servizio interno, una configurazione ragionevole spesso si presenta così:

# /etc/systemd/system/mio-app.service.d/env.conf
[Service]
Environment="APP_ENV=production"
EnvironmentFile=/etc/mio-app/runtime.env
EnvironmentFile=-/etc/mio-app/local.env

runtime.env contiene i valori richiesti. local.env è opzionale e permette a un operatore di sovrascrivere un'impostazione durante una finestra di manutenzione senza modificare l'unità principale. Dopo una modifica:

sudo systemctl daemon-reload
sudo systemctl restart mio-app.service
sudo journalctl -u mio-app.service -n 50 --no-pager

L'abitudine più sicura è semplice: mantieni i valori predefiniti non sensibili nell'unità o in un file di configurazione normale, tieni i segreti fuori dalle unità di proprietà del pacchetto, blocca qualsiasi file che contenga credenziali e verifica l'ambiente caricato senza divulgarlo in luoghi dove non dovrebbe stare.

Errori Comuni da Evitare

Il primo errore è mettere i segreti in ExecStart=:

ExecStart=/usr/local/bin/mio-app --db-password=s3cret

Sembra innocuo quando si ha fretta, ma gli argomenti della riga di comando sono spesso più facili da esporre rispetto ai file ambiente. Possono apparire negli elenchi dei processi, negli strumenti di monitoraggio, nella cronologia della shell, nei rapporti di crash o nelle definizioni di servizio copiate. Se l'applicazione supporta la lettura di un file di configurazione protetto, di solito è meglio. Se si aspetta una variabile d'ambiente, usa un EnvironmentFile= protetto e tieni il valore fuori dalla riga di comando.

Il secondo errore è modificare direttamente l'unità del fornitore. Un aggiornamento del pacchetto può sostituire il file, e il successivo riavvio potrebbe silenziosamente eliminare le tue impostazioni d'ambiente. Usa un drop-in:

sudo systemctl edit mio-app.service

Poi aggiungi solo la sovrascrittura locale:

[Service]
EnvironmentFile=/etc/mio-app/mio-app.env

Il terzo errore è presumere che il servizio veda lo stesso ambiente shell che vedi nel tuo terminale. Di solito non è così. La tua shell interattiva può avere variabili da .bashrc, .profile, una sessione SSH o uno strumento di distribuzione. Un servizio di sistema parte dall'ambiente gestito di systemd. Se l'app ha bisogno di PATH, JAVA_HOME, NODE_ENV, LD_LIBRARY_PATH o un valore simile, definiscilo esplicitamente o usa percorsi assoluti.

Ad esempio, questo è fragile:

ExecStart=npm start

Questo è più facile da ragionare:

WorkingDirectory=/opt/mio-app
Environment="NODE_ENV=production"
ExecStart=/usr/bin/npm start

Il quarto errore è rendere il file ambiente scrivibile dall'utente del servizio quando non è necessario. Un'app web che può sovrascrivere il proprio file ambiente può trasformare un normale bug dell'applicazione in un problema di persistenza. In molte configurazioni, l'utente del servizio dovrebbe leggere i dati dell'applicazione e scrivere log o upload, ma non dovrebbe essere in grado di riscrivere le credenziali usate per avviare il servizio.

Quando le Variabili d'Ambiente Sono lo Strumento Sbagliato

Le variabili d'ambiente sono popolari perché sono semplici, ma non sono sempre la migliore interfaccia. Se un valore è grande, strutturato, ruotato spesso o condiviso da diversi servizi, un vero file di configurazione o archivio segreti è solitamente più facile da gestire.

Un URL del database è una variabile d'ambiente ragionevole:

DATABASE_URL=postgresql://[email protected]:5432/app

Un documento completo di account di servizio JSON è meno piacevole. Le virgolette diventano scomode, le interruzioni di riga accidentali causano fallimenti e le persone sono più propense a incollarlo nei log durante il debug. In tal caso, memorizza il JSON in un file protetto e passa il percorso del file:

GOOGLE_APPLICATION_CREDENTIALS=/etc/mio-app/google-service-account.json

Poi proteggi il file JSON separatamente:

sudo chown root:mio-app /etc/mio-app/google-service-account.json
sudo chmod 640 /etc/mio-app/google-service-account.json

Questo non rende il segreto magico. L'applicazione può ancora leggerlo. Root può ancora leggerlo. Ma evita di stipare un segreto complesso nel parser ambiente di systemd e rende più chiaro l'audit a livello di file.

Una Lista di Controllo per una Revisione Più Sicura

Prima di riavviare un servizio che usa variabili d'ambiente, controlla quattro cose:

systemctl cat mio-app.service
sudo ls -l /etc/mio-app/mio-app.env
sudo systemd-analyze verify /etc/systemd/system/mio-app.service
sudo systemctl daemon-reload

systemctl cat conferma quali drop-in sono attivi. ls -l conferma che i permessi siano quelli che intendevi. systemd-analyze verify può cogliere alcuni problemi di sintassi dell'unità prima di riavviare. Non validerà ogni impostazione specifica dell'applicazione, ma è comunque un utile guardrail.

Dopo il riavvio, controlla il journal per errori di avvio:

sudo systemctl restart mio-app.service
sudo journalctl -u mio-app.service -n 100 --no-pager

Se devi confermare che una variabile è stata caricata, interrogalo con attenzione e oscura l'output prima di condividerlo. Per servizi sensibili, preferisco controllare prima una variabile non segreta come APP_ENV o LOG_LEVEL. Se quella è stata caricata dallo stesso file, il percorso del file e la sintassi del parser sono probabilmente corretti, e potresti non aver bisogno di stampare affatto i valori che portano segreti.

Un ultimo punto pratico: pianifica la rotazione prima di averne bisogno. Se una password o un token è memorizzato in un file ambiente, annota quale servizio deve riavviarsi dopo che il valore cambia e se quel riavvio causa tempi di inattività. Una credenziale facile da impostare ma difficile da ruotare diventerà eventualmente un incidente. Per piccoli servizi, il runbook di rotazione può essere di solo quattro righe:

sudoedit /etc/mio-app/mio-app.env
sudo systemctl restart mio-app.service
sudo systemctl status mio-app.service
sudo journalctl -u mio-app.service -n 50 --no-pager

Questo è sufficiente se tutti conoscono il raggio d'esplosione. Per sistemi più grandi, preferisci credenziali che possono sovrapporsi durante la rotazione, in modo da poter distribuire il nuovo valore, verificarlo e rimuovere il vecchio valore senza una finestra di interruzione affrettata.