Spevnenie systemd služieb v roku 2026: Sandboxing, systemd-analyze security a redukcia blast radius

Praktický sprievodca spevnením systemd unitov v roku 2026: sandbox direktívy, systemd-analyze security, drop-in overrides a walkthrough nginx z 9.6 UNSAFE na 3.1 OK.

Spevnenie systemd služieb 2026 (sprievodca)

Aktualizované: 16. júla 2026

Spevnenie systemd služieb v roku 2026 znamená pridať do unit súborov sadu direktív, ktoré aktivujú kernel namespaces, seccomp filtre, obmedzenie kapabilít a read-only filesystem, a potom overiť výsledok pomocou systemd-analyze security. Účel je jednoduchý. Keď útočník kompromituje váš démon, chcem, aby sedel v izolovanej klietke bez zápisu na /etc, bez CAP_SYS_ADMIN, bez schopnosti volať mount(2). Threat model, ktorý vždy kreslím kolegom, začína otázkou: „Čo sa zlomí prvé, keď táto služba padne?" Na správne spevnenom systemd unite je odpoveď „skoro nič mimo samotného démona".

  • systemd v261 (jún 2026) pridáva RestrictFileSystemAccess= postavené na BPF LSM; v258 zaviedol PrivateBPF= a ProtectHostname=private.
  • systemd-analyze security vracia skóre 0.0 až 10.0, kde ≥9.0 je UNSAFE a <1.0 je SAFE. Čísla vychádzajú z tabuľky váh v analyze-security.c.
  • Štyri direktívy s najvyššou váhou: PrivateNetwork= (2500), User=/DynamicUser= (2000), CapabilityBoundingSet=~CAP_SYS_ADMIN (1500), RestrictAddressFamilies=~AF_INET* (1500).
  • Reálny nginx unit klesol z 9.6 UNSAFE na 3.1 OK s jedným drop-in override, bez zmeny binárky.
  • Skóre neberie do úvahy SELinux, AppArmor ani zraniteľnosti v samotnom kóde. Je to checklist konfigurácie, nie audit.
  • Landlock LSM v systemde stále nie je natívne integrovaný. Pull request #39174 je otvorený, ale nezlúčený.

Prečo spevňovať systemd služby: blast radius kompromitácie

Predstavte si typický Linux server ako mesto, kde každý démon je budova. Bez sandboxingu je každá budova prepojená chodbami s bankou, elektrárňou a jadrovou zbrojnicou. Akonáhle útočník prepáli dvere na verejnej pošte (povedzme cez zraniteľnosť v deserializátore JSON knižnice), stojí pár krokov od root shellu. Cieľom spevnenia systemd unitov je stavať okolo každej budovy múry: predovšetkým read-only steny, chýbajúce dvere na miesta, ktoré služba nikdy nepotrebuje, a strážcov pri každom vchode, ktorí kontrolujú, aký syscall smie prejsť.

Tento prístup nie je nový, ale v roku 2026 je pragmaticky lepší ako externé nástroje. Namiesto toho, aby ste písali AppArmor profily alebo balili službu do samostatného kontajnera, pridáte 15 až 25 riadkov do unit súboru a služba beží v namespaces, ktoré kernel vynúti pri každom ExecStart=. Žiadna závislosť na Dockeri, na runc-u, na sidecari. systemd je už PID 1. Ak spadne, spadol celý systém, takže nič nepridávate na attack surface.

Vo svojich threat modeloch delím sandbox na štyri vrstvy: filesystem (kam môže služba čítať a zapisovať), oprávnenia (aké kapabilíty má proces), kernel rozhranie (aké syscalls a namespaces vidí) a sieť (aké adresné rodiny a IP rozsahy môže otvoriť). Každá vrstva má vlastnú sadu direktív a vlastný failure mode, a to je presne to, čo systemd-analyze security hodnotí bod po bode.

Ako funguje systemd-analyze security a čo znamená skóre

systemd-analyze security <unit> vygeneruje dlhú tabuľku každej relevantnej direktívy, či je nastavená, a nakoniec celkové exposure level na škále 0.0 (uzamknuté) až 10.0 (dokorán otvorené). Predikát vedľa čísla vychádza z pevných prahov v zdrojáku src/analyze/analyze-security.c.

SkórePredikátInterpretácia
10.0DANGEROUSBeží ako root bez akéhokoľvek sandboxingu
≥ 9.0UNSAFEStock distro unit bez hardening drop-inu
≥ 7.5EXPOSEDZákladné direktívy chýbajú (napr. NoNewPrivileges)
≥ 5.0MEDIUMČiastočné spevnenie, prijateľné pre menej kritické služby
≥ 1.0OKCieľ pre väčšinu produkčných služieb
≥ 0.1SAFEAgresívne spevnenie, vhodné pre non-network daemony
0.0PERFECTV praxi len prázdne oneshot unity

Váhy jednotlivých direktív sú tvrdo zakódované. Zdroják hovorí, že PrivateNetwork= nesie samostatnú váhu 2500, čo je 25 % z celkového bad-scoru. Preto network daemony ako nginx alebo sshd nikdy nedostanú SAFE, bez ohľadu na to, koľko iných direktív pridáte. Ďalej v poradí sú User=/DynamicUser= (2000), CapabilityBoundingSet=~CAP_SYS_ADMIN (1500), RestrictNamespaces=~user (1500) a RestrictAddressFamilies=~AF_INET|AF_INET6 (1500). To je poradie, v ktorom by ste mali s hardeningom začať.

Zavolanie bez argumentu vypíše rebríček všetkých unitov na systéme, takže okamžite vidíte, ktoré démony sú vaše najhoršie prípady:

# Zoznam všetkých unit-ov zoradený podľa exposure
systemd-analyze security

# Detailná analýza jedného unit-u
systemd-analyze security nginx.service

# Analýza offline (bez bežiaceho manažéra), vhodné v CI
systemd-analyze security --offline=true /etc/systemd/system/myapp.service

# Fail v CI, ak skóre prevýši 3.5
systemd-analyze security --threshold=35 myapp.service || exit 1

Kritický detail: skóre hodnotí len systemd konfiguráciu. Neberie do úvahy zraniteľnosti v binárke, SELinux/AppArmor policy ani D-Bus peer sandboxing. Ako píšu upstream man stránky, „a service can still request dangerous operations from an unsandboxed peer". Berte to ako checklist konfigurácie, nie ako audit.

Filesystem izolácia: ProtectSystem, PrivateTmp, ReadWritePaths

Filesystem vrstva má najviac direktív a najväčší dopad na blast radius. Základná trojica, ktorú by som pridal do každého jedného serverového unit-u:

  • ProtectSystem=strict namontuje celý filesystem read-only okrem /dev, /proc, /sys. Alternatívy: full (read-only /usr, /boot, /etc) alebo true (len /usr a /boot). Vždy volím strict a explicitne otváram cesty cez ReadWritePaths=.
  • ProtectHome=true spôsobí, že /home, /root, /run/user sú neviditeľné a prázdne. Pre serverové démony (ktoré nemajú čo hľadať v users' home) žiadne úskalie.
  • PrivateTmp=true zabezpečí, že /tmp a /var/tmp sú vlastný namespace mimo shared filesystemu. Odstraňuje celú triedu race-condition útokov na predvídateľné mená v /tmp.

Ďalšie, ktoré pridávam pri agresívnejšom spevnení: ProtectProc=invisible skrýva ostatných používateľov z /proc, ProcSubset=pid vyfiltruje /proc/kcore a spol., PrivateDevices=true zabuduje minimálny /dev len s pseudo-zariadeniami (null, zero, random, tty) a PrivateMounts=true izoluje mount namespace tak, že služba nevidí následné mounty hostiteľa.

Pre presné povolenie zápisu použite:

[Service]
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ProtectProc=invisible
ProcSubset=pid
PrivateDevices=true

# Explicitne povolený zápis, všetko ostatné je RO
ReadWritePaths=/var/log/myapp /var/lib/myapp /run/myapp

# Neexistujúce cesty (napr. legacy). Minus znamená "nezlyhaj"
InaccessiblePaths=-/etc/letsencrypt -/root/.ssh

Čo sa zlomí prvé? Poviem to úprimne: deväť z desiatich prípadov to je aplikácia, ktorá si píše do /tmp a zdieľa cestu s pomocným skriptom mimo unit-u. PrivateTmp= ich rozdelí do rôznych namespaces a helper nenájde svoj vstupný súbor. Druhý najčastejší prípad: aplikácia si píše log alebo PID súbor mimo ReadWritePaths=, čo pri ProtectSystem=strict hodí EROFS. Riešenie? Vždy najprv spustite strace -e trace=openat,write <binárka> a pozrite sa, kam skutočne píše. (V minulom projekte som takto našiel démona, ktorý logoval do /var/spool. Nikoho to netrápilo, kým sa to práve nezablokovalo.)

Redukcia oprávnení: NoNewPrivileges, kapabilit a DynamicUser

Filesystem izolácia je múr. Oprávnenia sú kľúče, ktoré útočník ukradne, keď múr preskočí. Cieľom tejto vrstvy je zabezpečiť, aby ani skompromitovaný proces nemohol získať viac privileges, než mal na začiatku.

NoNewPrivileges=true

Toto je jediná najdôležitejšia direktíva v celom systemd sandboxe. Nastavuje kernel PR_SET_NO_NEW_PRIVS flag, ktorý zaručí, že žiadny execve(2) volaný z tejto služby ani jej detí nemôže eskalovať oprávnenia. SUID binárky, capabilities na súboroch, dokonca ani spustenie sudo nevedia zvýšiť privileges. Bez tohto flagu je celý zvyšok sandboxu obídený jedným execve("/usr/bin/mount").

Podľa upstream systemd.exec(5) man stránky viacero ďalších direktív implicitne zapína NoNewPrivileges=, napríklad SystemCallFilter=, PrivateDevices=, ProtectKernelTunables=, MemoryDenyWriteExecute= alebo DynamicUser=. Aj tak ho vždy nastavujem explicitne. Je to signál v code review a chráni pred budúcim odstránením inej direktívy.

CapabilityBoundingSet= a AmbientCapabilities=

Linux kapabilíty rozdeľujú root právomoci na 41 nezávislých bitov. CapabilityBoundingSet= definuje strop; služba nikdy nemôže mať viac kapabilít než je v tejto množine, ani po execve. Pravidlo, ktoré aplikujem: začnite s CapabilityBoundingSet= (prázdny = žiadne kapabilíty) a pridávajte len tie, ktoré aplikácia naozaj potrebuje.

[Service]
# Web server, ktorý potrebuje bind na port <1024 ako nie-root
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

# Alternatíva: úplne prázdne, ak beží na porte > 1024
# CapabilityBoundingSet=

AmbientCapabilities= je novšia direktíva, ktorá umožňuje spustiť ne-root proces s konkrétnou kapabilítou aktívnou (nie len v bounding sete). Je to čistejšie riešenie ako file-based capabilities alebo suid wrapper. Kombinácia User=www-data + AmbientCapabilities=CAP_NET_BIND_SERVICE + CapabilityBoundingSet=CAP_NET_BIND_SERVICE dá nginxu presne to, čo potrebuje, a nič viac.

DynamicUser=yes

Ak služba nemusí perzistentne vlastniť konkrétne súbory, DynamicUser=yes je najsilnejšia jednorazová optimizácia. systemd pri každom štarte vygeneruje náhodný UID/GID z rozsahu 61184 až 65519, zapíše ho do transient /etc/passwd, a pri zastavení ho zmaže. Automaticky sa tým zapne ProtectSystem=strict, ProtectHome=read-only, PrivateTmp=yes, RestrictSUIDSGID=yes, NoNewPrivileges=yes a RemoveIPC=yes.

Kombinujte s StateDirectory=, LogsDirectory=, CacheDirectory= a RuntimeDirectory=. systemd tie adresáre vytvorí pod /var/lib/<name>, /var/log/<name> a podobne, chownne ich na dynamické UID a mount-ne ako ReadWritePaths=. Rieši to aj perzistentný stav aj hardening jedným ťahom:

[Service]
DynamicUser=yes
StateDirectory=myapp
LogsDirectory=myapp
CacheDirectory=myapp
RuntimeDirectory=myapp
ExecStart=/usr/bin/myapp --state=/var/lib/myapp --log=/var/log/myapp

Filtrovanie syscallov cez SystemCallFilter a seccomp

SystemCallFilter= je systemd wrapper okolo seccomp BPF filtra v jadre. Definujete allowlist (alebo denylist s prefixom ~) systémových volaní, ktoré smie proces vykonať; každé iné volanie vráti EPERM alebo (predvolene) SIGSYS. Toto je najsilnejšia obranná vrstva proti kernel-exploit útokom. Ak služba nikdy nevolá keyctl(2) alebo bpf(2), útočník ich z nej nedokáže zavolať ani po RCE.

Ručne písať zoznam 400+ syscallov je nezmysel, preto systemd definuje pomenované skupiny. Praktický základ, ktorý používam:

[Service]
# Allowlist základov + odopretie nebezpečných rodín
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @mount @debug @reboot @swap @cpu-emulation
SystemCallErrorNumber=EPERM

# Iba x86_64 syscall ABI, blokuje i386 kompat vrstvu (častá cesta exploitov)
SystemCallArchitectures=native

@system-service pokrýva 99 % bežných aplikácií: file I/O, sieť, časovače, správa pamäte, IPC. Následný denylist odstraňuje kategórie, ktoré servisná aplikácia takmer nikdy nepotrebuje, ako @mount (mount/umount/pivot_root), @debug (ptrace, process_vm_readv), @reboot, @swap. @privileged obsahuje veci ako chroot, setuid, iopl.

Ladenie, keď služba padne s SIGSYS: pridajte SystemCallLog=@debug alebo spustite strace -c -f, aby ste videli, ktorý syscall bol odopretý. Kombinujte s SystemCallErrorNumber=EPERM namiesto SIGSYS, aby aplikácia mohla zdvorilo failovať namiesto padnutia. Mnoho runtime-ov (Node, Python) tak dokáže pokračovať s degradovanou funkčnosťou a dá vám čas debugovať.

Sieťové obmedzenia: RestrictAddressFamilies a IPAddressDeny

Väčšina backendových služieb komunikuje len cez TCP nad IPv4/IPv6 a Unix sockety. Ostatné adresné rodiny (AF_NETLINK, AF_PACKET, AF_BLUETOOTH, AF_XDP) sú favoritné vektory pre kernel exploity. RestrictAddressFamilies= ich úplne odstrihne:

[Service]
# Povolené: IPv4, IPv6, Unix, netlink len na dotaz stavu
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# Deny všetkých ostatných; služba dostane EAFNOSUPPORT

Ďalšou vrstvou je IPAddressAllow=/IPAddressDeny=, ktoré využívajú eBPF cgroup filter. Bežný recept pre backend, ktorý má hovoriť len s lokálnou databázou a metrics scraper-om:

[Service]
IPAddressDeny=any
IPAddressAllow=127.0.0.1/8 10.0.0.0/8 fe80::/10

V systemd v261 (jún 2026) pribudol RestrictNetworkInterfaces= pre obmedzenie na konkrétne network interfaces a nová SocketBindAllow=/SocketBindDeny= direktíva, ktorá filtruje bind(2) na úrovni portu. Takže služba, ktorá má počúvať len na porte 8080, nemôže „omylom" otvoriť port 22.

Walkthrough: spevnenie nginx z 9.6 UNSAFE na 3.1 OK

Ukazujem celý proces na nginxe, pretože je to najčastejšia inštalácia a čísla sú reprodukovateľné. Stock unit na Ubuntu 24.04 / RHEL 10 skóruje 9.6 UNSAFE. Cieľ: dostať ho pod 3.5 (OK) bez zmeny funkčnosti. Robil som to na štyroch produkčných hostiteľoch a čísla vychádzajú v podstate rovnaké.

Krok 1, baseline.

$ sudo systemd-analyze security nginx.service | tail -5
→ Overall exposure level for nginx.service: 9.6 UNSAFE 😨

Krok 2, vytvorenie drop-in overlay. Nikdy neupravujeme vendor /lib/systemd/system/nginx.service. Namiesto toho:

sudo systemctl edit nginx.service
# otvorí editor pre /etc/systemd/system/nginx.service.d/override.conf

Krok 3, hardening drop-in. Toto je override, ktorý reálne používam v produkcii:

[Service]
# --- Filesystem ---
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
ProtectProc=invisible
ProcSubset=pid
ReadWritePaths=/var/log/nginx /var/lib/nginx /var/cache/nginx /run

# --- Oprávnenia ---
NoNewPrivileges=true
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN CAP_DAC_OVERRIDE CAP_SETGID CAP_SETUID
AmbientCapabilities=
RestrictSUIDSGID=true
RemoveIPC=true
LockPersonality=true
MemoryDenyWriteExecute=true

# --- Namespaces ---
PrivateMounts=true
RestrictNamespaces=true
RestrictRealtime=true
UMask=0077

# --- Syscalls ---
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @mount @debug @reboot @swap
SystemCallErrorNumber=EPERM

# --- Sieť ---
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
IPAddressDeny=any
IPAddressAllow=any

Krok 4, reload a test.

sudo systemctl daemon-reload
sudo systemctl restart nginx
curl -I http://localhost/            # overte, že server odpovedá
sudo systemd-analyze security nginx.service | tail -5
→ Overall exposure level for nginx.service: 3.1 OK 🙂

Podobný postup som robil aj pre Wazuh agenta a jeho manažér proces, konkrétne z 9.4 UNSAFE na 4.2 MEDIUM. Detekčné nástroje typicky zostanú vyššie, pretože potrebujú CAP_NET_ADMIN a CAP_SYS_PTRACE.

Ako správne editovať vendor unity cez drop-in overrides

Drop-in overrides sú jediný správny spôsob, ako pridávať hardening do unit-ov, ktoré prišli s balíkom. Dôvod je jednoduchý: pri každom apt upgrade alebo dnf update package manager prepíše /lib/systemd/system/<unit>.service, ale /etc/systemd/system/<unit>.service.d/*.conf zostane netknuté a systemd ho merge-ne naspäť.

Tri spôsoby, ako drop-in vytvoriť:

  1. sudo systemctl edit nginx.service otvorí editor pre override.conf. Odporúčaný spôsob pre ad-hoc zmeny.
  2. sudo systemctl edit --full nginx.service skopíruje celý vendor unit do /etc/systemd/system/. Nerobte to. Stratíte upstream aktualizácie.
  3. Ručne vytvorte /etc/systemd/system/nginx.service.d/10-hardening.conf. Užitočné pre Ansible/Chef, ktoré chce jasné meno súboru.

Overte finálnu merged konfiguráciu:

systemctl cat nginx.service       # celý merged unit vrátane drop-inov
systemctl show nginx.service      # rozbalené hodnoty všetkých direktív
systemd-delta                     # zoznam všetkých unit-ov s active drop-inmi

Pre integráciu s automatizovaným auditom kombinujte s OpenSCAP CIS benchmarkami. CIS Level 2 profil pre RHEL 10 obsahuje pravidlá, ktoré kontrolujú NoNewPrivileges= a ProtectSystem= na kľúčových unitoch.

Čo prišlo v systemd 258 až 261 a stav Landlock integrácie

Za posledný rok pridal systemd niekoľko direktív, ktoré stojí za to poznať:

  • v258 (september 2025): PrivateBPF= pre privátny bpffs namespace, ProtectHostname=private pre skutočný UTS namespace (nie len read-only), PrivateUsers=full s plnou 32-bit UID mapou, encrypted credentials pre user services, per-user kvóty na /dev/shm a /tmp.
  • v259 (december 2025): UserNamespacePath= pre user-namespace sandboxing s custom mapou, journald default persistentný, TPM 1.2 vypadnutá.
  • v260 (marec 2026): PrivateUsers=managed, čiže dynamický 65536 UID rozsah cez systemd-nsresourced, BPF-LSM policy pre user namespaces uvoľnená, PrivateTmp=yes s DefaultDependencies=no defaultuje na disconnected mode.
  • v261 (jún 2026): RestrictFileSystemAccess=, nová direktíva postavená na BPF LSM, ktorá obmedzí spustenie len na signované, dm-verity-chránené filesystémy. Toto je najväčší hardening skok za posledné roky.

Súčasne treba spomenúť, čo ešte nie je hotové: Landlock LSM integrácia. Pull request #39174 pridáva LandlockConfig= direktívu, ale ku dňu písania tohto článku (júl 2026) nie je merged. Landlock je unprivileged sandbox, ktorý dopĺňa (nie nahrádza) seccomp a je perfektne komplementárny. Zatiaľ čo SystemCallFilter= filtruje volania, Landlock filtruje filesystemové cesty. Do zlúčenia môžete využívať landrun alebo island ako externý wrapper okolo ExecStart=.

Ak vaše systemy podporujú BPF LSM (kernel ≥ 5.7 s CONFIG_BPF_LSM=y), skombinujte hardening z tejto príručky s eBPF monitoringom cez Tetragon. systemd vás izoluje pri štarte, Tetragon vidí, čo skutočne robí za behu.

Časté chyby a čo sa zlomí prvé

Za roky spevňovania služieb som videl tie isté šesť problémov znova a znova. Uvádzam ich v poradí, v ktorom typicky spadnú, aby ste vedeli, kde hľadať:

  1. Aplikácia píše mimo ReadWritePaths=. Najčastejší failure. Prejaví sa ako EROFS alebo Permission denied. Diagnostika: strace -e trace=openat,write,mkdir -f systemctl start myapp.
  2. SystemCallFilter blokuje potrebné volanie. Aplikácia padne so SIGSYS alebo dostane EPERM. Nastavte dočasne SystemCallLog=@debug a pozrite žurnál na audit: type=1326 záznamy.
  3. PrivateNetwork=yes na sieťovej službe. Zjavné, ale videl som to v produkčnom Ansible role. Copy-paste hardening šablóny do database unitu, ktorý potrebuje počúvať na TCP 5432. Zásadné pravidlo: PrivateNetwork= len pre daemony, ktoré komunikujú len cez Unix sockety alebo len lokálne procesy.
  4. DynamicUser=yes s legacy stavom. Aplikácia očakáva súbory pod pevným UID (napr. staré /var/lib/myapp). Riešenie: chown na DynamicUser nie je možný, takže buď migrovať dáta pod StateDirectory=, alebo použiť statický User=.
  5. CapabilityBoundingSet vylúči CAP_DAC_OVERRIDE, ktorý app potrebuje. Prejaví sa ako Permission denied na súbore, ktorý má správneho vlastníka. Ak app vie správne chownovať pri štarte, netreba to. Ak nie, pridajte CAP_DAC_OVERRIDE (a smútte).
  6. MemoryDenyWriteExecute láme JIT-y. JVM, V8/Node, .NET, PyPy, všetky JIT-ujú kód a potrebujú PROT_WRITE|PROT_EXEC. Direktíva zablokuje mprotect(2) s týmito flagmi a aplikácia padne s SIGSEGV. Riešenie: pre JIT služby nezapínajte MemoryDenyWriteExecute=true, alebo použite W^X režim runtime-u, ak ho podporuje.

V širšom threat modeli je stále rozumné kombinovať systemd hardening s vrstvou MAC nad ním, či už SELinux na RHEL 10, alebo AppArmor na Debiane/Ubuntu. systemd sandbox chráni pred väčšinou generických útokov; MAC label chráni pred cielenými útokmi na konkrétny súbor s konkrétnym labelom. Obidve dokopy sú štandard, ktorý dnes očakávam od každého produkčného hostiteľa.

Často kladené otázky

Čo znamená UNSAFE v systemd-analyze security?

UNSAFE je predikát pre exposure skóre ≥ 9.0. Nehovorí, že služba je konkrétne zraniteľná; hovorí, že takmer žiadne systemd sandbox direktívy nie sú zapnuté a v prípade RCE má útočník rovnaké oprávnenia ako celý user, pod ktorým služba beží. Prvý cieľ hardeningu je dostať to pod 5.0 (MEDIUM).

Je NoNewPrivileges=true dostatočné?

Nie, ale je to najlacnejšia a najúčinnejšia jedna direktíva. Zabráni eskalácii cez SUID a file capabilities, ale nechráni pred zraniteľnosťou v kerneli ani pred prístupom k súborom, ktoré má user aj tak čítať. Kombinujte s ProtectSystem=strict, PrivateTmp=true a minimálnym CapabilityBoundingSet=, čo je základné trio pre každý serverový unit.

Funguje DynamicUser=yes s pretrvávajúcim stavom?

Áno, ale len ak stav ukladáte pod StateDirectory=, LogsDirectory=, CacheDirectory= alebo RuntimeDirectory=. systemd vytvorí /var/lib/<name> pri prvom spustení, chownne na náhodný UID, a pri ďalších reštartoch znovu použije rovnaké UID pre ten istý unit. Nefunguje pre aplikácie, ktoré držia file locky mimo týchto adresárov alebo očakávajú konkrétny numerický UID v /etc/passwd.

Ktoré syscalls mám povoliť v SystemCallFilter?

Pre 99 % serverových aplikácií začnite s SystemCallFilter=@system-service, čo je systemd allowlist pokrývajúci file I/O, sieť, IPC, časovače a správu pamäte. Následne odopnite skupiny, ktoré typická služba nepotrebuje: ~@privileged @resources @mount @debug @reboot @swap @cpu-emulation. Ak služba padne so SIGSYS, dočasne pridajte SystemCallLog=@debug a pozrite journal, ktorý syscall bol odopretý.

Aký je rozdiel medzi ProtectSystem=strict a full?

strict namontuje celý filesystem read-only okrem /dev, /proc, /sys; musíte explicitne otvoriť cesty cez ReadWritePaths=. full je slabšie, keďže read-only sú len /usr, /boot a /etc; zvyšok filesystému (/var, /tmp, /home) je zapisovateľný. Pre novo písané unity vždy volím strict. full je vhodné pre legacy služby, ktoré si píšu na 10 rôznych miest a nechce sa vám ich všetky enumerovať.

Aisha Okonkwo
O Autorovi Aisha Okonkwo

Infrastructure security architect at a hyperscaler. Spends her days on Zero Trust, secrets management, and yelling at unencrypted backups.