Stații de lucru

OS - Windows 9157 Solutii

Reguli si plangeri 11 Solutii

OS - OS X 410 Solutii

Reguli de configurare 12 Solutii

Licentiere 18 Solutii

Securitate 182 Solutii

Copie de rezerva (Backup) 68 Solutii

Antivirus 72 Solutii

Aplicatii specifice 5291 Solutii

Hardware 291 Solutii

Cum protejezi cheile API ale unui serviciu cu systemd-creds și LoadCredential=

O metodă mai sigură decât păstrarea unei chei API în variabile de mediu sau direct în fișierul unității este să folosești credentialele systemd.

Modelul este:

cheie API
   ↓
systemd-creds encrypt
   ↓
fișier criptat
   ↓
LoadCredentialEncrypted=
   ↓
systemd decriptează la pornire
   ↓
/run/credentials/...
   ↓
serviciul citește cheia

Credentialele systemd sunt încărcate la pornirea serviciului, sunt read-only în timpul execuției și sunt accesibile procesului serviciului prin directorul indicat de variabila CREDENTIALS_DIRECTORY.

1. Verifică dacă ai systemd-creds

systemd-creds --version

Comenzile pentru criptare și decriptare sunt disponibile în versiunile moderne de systemd; mecanismul de credentiale a fost introdus începând cu systemd 247, iar criptarea cu systemd-creds din documentația actuală este disponibilă începând cu versiunea 250.

2. Creează cheia API

Exemplu:

sudo install -d -m 700 /etc/myapp
sudo nano /etc/myapp/api-key.txt

Pune cheia în fișier:

sk-example-very-secret-key

Protejează fișierul:

sudo chmod 600 /etc/myapp/api-key.txt
sudo chown root:root /etc/myapp/api-key.txt

Acest fișier este doar etapa inițială. Nu vrem ca aplicația să citească direct această copie plaintext.

3. Criptează cheia cu systemd-creds

sudo systemd-creds encrypt \
  --name=api_key \
  /etc/myapp/api-key.txt \
  /etc/myapp/api-key.cred

Rezultatul este un credential criptat și autentificat. systemd folosește AES-256-GCM, iar protecția poate folosi o cheie derivată din TPM2, cheia locală din /var/lib/systemd/credential.secret sau ambele, în funcție de configurația sistemului.

După ce ai verificat că fișierul criptat există:

sudo ls -l /etc/myapp/api-key.cred

șterge copia plaintext:

sudo shred -u /etc/myapp/api-key.txt

4. Configurează serviciul

Editează serviciul:

sudo systemctl edit myapp.service

Adaugă:

[Service]
LoadCredentialEncrypted=api_key:/etc/myapp/api-key.cred

Aici:

  • api_key este numele credentialului;
  • /etc/myapp/api-key.cred este fișierul criptat;
  • systemd îl decriptează la pornirea serviciului;
  • aplicația primește credentialul în directorul său de credentiale.

5. Citește cheia din aplicație

Directorul credentialelor este disponibil prin:

echo "$CREDENTIALS_DIRECTORY"

Credentialul nostru va fi disponibil astfel:

$CREDENTIALS_DIRECTORY/api_key

De exemplu, un script poate face:

#!/bin/sh

API_KEY=$(cat "$CREDENTIALS_DIRECTORY/api_key")

exec /usr/local/bin/myapp --api-key "$API_KEY"

Mai bine este ca aplicația să știe să citească direct fișierul credentialului, fără să îl transforme într-o variabilă de mediu. Documentația systemd recomandă acest model deoarece credentialele nu sunt propagate prin proces tree precum variabilele de mediu.

6. Reîncarcă systemd

sudo systemctl daemon-reload

Verifică unitatea:

systemd-analyze verify myapp.service

7. Pornește serviciul

sudo systemctl restart myapp.service

Verifică:

systemctl status myapp.service

Și logurile:

journalctl -u myapp.service -b

8. Verifică existența credentialului fără să afișezi cheia

Poți verifica faptul că serviciul are directorul de credentiale:

systemctl show myapp.service -p Environment

Pentru o verificare mai practică, aplicația sau wrapper-ul poate verifica existența fișierului:

test -r "$CREDENTIALS_DIRECTORY/api_key"

Evită comenzi precum:

cat /run/credentials/myapp.service/api_key

deoarece afișează secretul în terminal.

9. Adaugă și sandboxing

Credentialele sunt și mai utile împreună cu izolarea serviciului:

[Service]
LoadCredentialEncrypted=api_key:/etc/myapp/api-key.cred

User=myapp
Group=myapp

NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

Documentația systemd recomandă și namespacing-ul mount-urilor pentru serviciile care folosesc credentiale; mai multe opțiuni de sandboxing, precum ProtectSystem, implică deja PrivateMounts. Astfel, directorul de credentiale al serviciului poate fi ascuns față de alte servicii.

10. Unde ajunge cheia?

Pentru un serviciu system-level, credentialele sunt în mod obișnuit disponibile sub:

/run/credentials/<numele-serviciului>/

Nu este recomandat să hardcodezi această cale în aplicație. Folosește CREDENTIALS_DIRECTORY.

11. De ce este mai bine decât Environment=

O abordare de evitat este:

[Service]
Environment=API_KEY=secret123

Variabilele de mediu sunt moștenite de procesele copil și pot traversa anumite limite de securitate ale proceselor. Documentația systemd recomandă LoadCredential, LoadCredentialEncrypted sau SetCredentialEncrypted pentru date sensibile.

De asemenea, nu pune o cheie secretă direct în:

SetCredential=api_key:secret123

Unit files sunt accesibile și prin D-Bus, deci SetCredential nu este potrivit pentru secrete sensibile. Pentru astfel de date se recomandă LoadCredential sau LoadCredentialEncrypted.

12. Fluxul complet

/etc/myapp/api-key.txt
        │
        │ systemd-creds encrypt
        ▼
/etc/myapp/api-key.cred
        │
        │ LoadCredentialEncrypted=
        ▼
systemd
        │
        │ decrypt + authenticate
        ▼
CREDENTIALS_DIRECTORY/api_key
        │
        ▼
myapp.service

Checklist

[ ] cheia nu este în unit file
[ ] cheia nu este în Environment=
[ ] cheia plaintext este ștearsă după criptare
[ ] fișierul .cred este protejat
[ ] LoadCredentialEncrypted= este folosit
[ ] aplicația citește CREDENTIALS_DIRECTORY
[ ] serviciul rulează cu User= dedicat
[ ] NoNewPrivileges=yes
[ ] ProtectSystem=strict
[ ] logurile nu afișează cheia
[ ] systemd-analyze verify trece

Nu tratezi cheia API ca pe o configurație obișnuită. O criptezi la storage, o atașezi serviciului prin LoadCredentialEncrypted=, iar systemd o decriptează doar când serviciul pornește. Aplicația primește cheia ca fișier read-only într-un spațiu dedicat serviciului, fără să fie nevoie să o pui în unit file sau în variabile de mediu.

[mai mult...]

Cum auditezi și securizezi un serviciu Linux folosind systemd-analyze

systemd-analyze are două comenzi foarte utile pentru securizarea unui serviciu:

  • verify verifică sintaxa și problemele de configurare ale unității.
  • security analizează mecanismele de sandboxing și calculează un nivel de expunere. O valoare mai mică înseamnă, în general, mai puține privilegii și o izolare mai strictă.

Vom folosi serviciul fictiv myapp.service. Înlocuiește numele cu serviciul pe care vrei să îl verifici.

1. Verifică serviciul

Începe cu:

systemctl status myapp.service

Vezi configurația efectivă:

systemctl cat myapp.service

Și verifică proprietățile importante:

systemctl show myapp.service \
  -p User \
  -p Group \
  -p ExecStart \
  -p AmbientCapabilities \
  -p CapabilityBoundingSet \
  -p NoNewPrivileges

Scopul este să afli cu ce utilizator rulează serviciul, ce executabil pornește și ce privilegii primește.

2. Rulează auditul de securitate

systemd-analyze security --no-pager myapp.service

Vei primi o listă cu verificări de tipul:

NAME                         DESCRIPTION                         EXPOSURE
✗ User=/DynamicUser=         Service runs as root user            ...
✗ PrivateTmp=                Service has access to /tmp           ...
✓ ProtectSystem=             Service has limited filesystem       ...
...

La final apare nivelul general de expunere. Acest scor trebuie folosit ca instrument de audit, nu ca verdict absolut. systemd verifică mecanismele sale de izolare, nu securitatea internă a aplicației.

3. Verifică fișierul unității înainte de modificări

systemd-analyze verify /etc/systemd/system/myapp.service

Comanda poate identifica directive necunoscute, probleme de sintaxă și alte erori de configurare. Pentru unitățile existente, poți verifica și configurația instalată de distribuție:

systemd-analyze verify myapp.service

4. Rulează serviciul cu un utilizator dedicat

Un serviciu care nu are nevoie de privilegii administrative nu ar trebui să ruleze ca root.

Exemplu:

[Service]
User=myapp
Group=myapp

Verifică mai întâi că utilizatorul există:

id myapp

Separarea utilizatorului este una dintre primele măsuri de reducere a impactului unei eventuale compromiteri.

5. Activează NoNewPrivileges

Adaugă:

[Service]
NoNewPrivileges=yes

Această opțiune împiedică procesul și procesele sale copil să obțină privilegii suplimentare prin mecanisme precum setuid, setgid sau file capabilities. La nivel de kernel, flag-ul devine permanent pentru procesele care îl moștenesc.

6. Izolează filesystem-ul

O configurație uzuală pentru un serviciu care nu trebuie să modifice sistemul este:

[Service]
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

ProtectSystem=strict face ierarhia filesystem-ului în principal read-only pentru serviciu. ProtectHome ascunde sau restricționează accesul la directoarele utilizatorilor. PrivateTmp oferă serviciului un spațiu separat pentru /tmp și /var/tmp.

Dacă aplicația trebuie să scrie într-un director precis, nu transforma întregul filesystem în writable. Permite doar acea zonă:

[Service]
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp

7. Restricționează accesul la dispozitive

Pentru un serviciu obișnuit care nu are nevoie de dispozitive hardware:

[Service]
PrivateDevices=yes

Acest lucru reduce accesul procesului la dispozitivele sistemului.

8. Protejează interfața kernelului

Pentru servicii normale:

[Service]
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes

Ideea este ca procesul să nu poată modifica facilități ale kernelului sau configurația control groups de care nu are nevoie. Documentația systemd recomandă folosirea acestor mecanisme de sandboxing atunci când compatibilitatea aplicației permite acest lucru.

9. Limitează rețeaua

Dacă aplicația folosește numai conexiuni TCP/IP obișnuite și socketuri Unix:

[Service]
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

Astfel limitezi familiile de adrese pe care procesul le poate folosi prin socket. Nu aplica această regulă unui serviciu înainte să verifici ce tipuri de socket folosește.

10. Limitează capabilities

Verifică mai întâi ce capabilities sunt necesare:

systemctl show myapp.service -p CapabilityBoundingSet

Dacă aplicația nu are nevoie de capabilities, acestea pot fi eliminate:

[Service]
CapabilityBoundingSet=
AmbientCapabilities=

Capability bounding set limitează capabilities care pot fi moștenite de procesele executate ulterior. Nu elimina capabilities necesare pentru operația normală a serviciului.

11. Aplică modificările printr-un drop-in

Pentru un serviciu instalat de distribuție, evită modificarea directă a fișierului original.

Folosește:

sudo systemctl edit myapp.service

Poți introduce, de exemplu:

[Service]
User=myapp
Group=myapp

NoNewPrivileges=yes

ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes

PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes

RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

Acesta este un punct de pornire. Nu toate serviciile acceptă toate restricțiile.

12. Reîncarcă systemd

sudo systemctl daemon-reload

Apoi verifică din nou unitatea:

systemd-analyze verify myapp.service

13. Repornește serviciul

sudo systemctl restart myapp.service

Verifică imediat:

systemctl status myapp.service

Dacă serviciul nu mai pornește, consultă logurile:

journalctl -u myapp.service -b

Pentru urmărire live:

journalctl -u myapp.service -f

14. Rulează din nou auditul

systemd-analyze security --no-pager myapp.service

Compară rezultatul cu cel inițial.

Important: nu urmări doar reducerea scorului. O restricție care face serviciul inutilizabil nu este o îmbunătățire practică. Auditul trebuie să fie urmat de testarea funcționalității. systemd însuși recomandă activarea cât mai multor mecanisme de sandboxing fără a afecta funcționarea serviciului.

Workflow recomandat

systemctl cat
      ↓
systemd-analyze verify
      ↓
systemd-analyze security
      ↓
systemctl edit
      ↓
daemon-reload
      ↓
restart
      ↓
journalctl
      ↓
systemd-analyze security

Checklist rapid

[ ] Nu rulează ca root fără motiv
[ ] NoNewPrivileges=yes
[ ] ProtectSystem=strict
[ ] ProtectHome=yes
[ ] PrivateTmp=yes
[ ] PrivateDevices=yes
[ ] ProtectKernelTunables=yes
[ ] ProtectKernelModules=yes
[ ] ProtectControlGroups=yes
[ ] RestrictAddressFamilies configurat
[ ] Capabilities limitate
[ ] systemd-analyze verify trece
[ ] Serviciul funcționează după restart
[ ] Logurile nu arată erori
[ ] systemd-analyze security a fost rulat din nou

Auditarea cu systemd-analyze nu înseamnă doar să obții un scor. Procesul corect este:

identifici privilegiile → reduci suprafața de atac → verifici compatibilitatea → testezi serviciul → rulezi auditul din nou.

[mai mult...]

Cum rulezi containere Podman automat cu Quadlet și systemd

Quadlet îți permite să definești containere Podman în fișiere simple, iar systemd se ocupă de pornire, oprire și restart automat. Un fișier de tip .container este transformat de Podman într-un serviciu systemd.

1. Verifică Podman

Pe sistemul unde rulezi containerul:

podman --version
podman info --format '{{.Host.CgroupsVersion}}'

Quadlet necesită cgroups v2.

2. Creează directorul Quadlet

Pentru un container rootless, folosește:

mkdir -p ~/.config/containers/systemd

Acesta este unul dintre directoarele standard în care Quadlet caută configurațiile utilizatorului.

3. Creează definiția containerului

Exemplu: un server Nginx care ascultă pe portul 8080.

Creează fișierul:

nano ~/.config/containers/systemd/nginx.container

Conținut:

[Unit]
Description=NGINX container

[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80

[Service]
Restart=always

Cheia Image definește imaginea, iar PublishPort mapează portul 8080 al sistemului la portul 80 din container. Pentru imagine este recomandată folosirea unui nume complet calificat.

4. Reîncarcă systemd

systemctl --user daemon-reload

Quadlet generează serviciul systemd pornind de la fișierul .container.

5. Pornește containerul

systemctl --user start nginx.service

Verifică starea:

systemctl --user status nginx.service

Și verifică direct containerul:

podman ps

6. Testează aplicația

Pe aceeași mașină:

curl http://localhost:8080

Ar trebui să primești pagina implicită Nginx.

7. Pornește automat după restart

Pentru Quadlet nu este necesar să activezi manual serviciul generat cu systemctl enable. Pornirea automată se configurează în fișierul Quadlet prin secțiunea Install.

Modifică fișierul astfel:

[Unit]
Description=NGINX container

[Container]
Image=docker.io/library/nginx:alpine
PublishPort=8080:80

[Service]
Restart=always

[Install]
WantedBy=default.target

Apoi:

systemctl --user daemon-reload

Pentru un serviciu rootless care trebuie să ruleze și atunci când utilizatorul nu este autentificat, activează lingering:

sudo loginctl enable-linger $USER

Lingering permite instanței systemd a utilizatorului să rămână activă după logout și după reboot.

8. Vezi logurile

journalctl --user -u nginx.service

Pentru urmărire în timp real:

journalctl --user -u nginx.service -f

Podman recomandă journalctl pentru verificarea logurilor serviciilor Quadlet.

9. Dacă serviciul nu este găsit

Dacă primești o eroare de tip „Unit not found”, verifică generarea unității:

systemd-analyze --user --generators=true verify nginx.service

Pentru o analiză mai detaliată poți rula generatorul Quadlet în mod dry-run:

/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun

Aceste comenzi ajută la identificarea erorilor de sintaxă sau a opțiunilor Quadlet incompatibile.

Fluxul de bază

În practică, ciclul este:

nginx.container
      ↓
systemd --user daemon-reload
      ↓
nginx.service
      ↓
podman run
      ↓
containerul rulează

Avantajul principal este că configurația containerului rămâne declarativă și este gestionată de systemd, inclusiv restartul și pornirea automată.

Rootless vs. rootful

Pentru rootless:

~/.config/containers/systemd/
systemctl --user
podman ps

Pentru rootful:

/etc/containers/systemd/
sudo systemctl
sudo podman ps

Directorul rootless și cel rootful sunt diferite, iar comenzile systemd trebuie executate în contextul corespunzător.

Pe scurt: scrii un fișier .container, îl pui în directorul Quadlet, rulezi daemon-reload, apoi gestionezi containerul cu systemctl. Asta elimină nevoia de scripturi separate pentru pornire și restart.

[mai mult...]