Θωράκιση systemd Services στο Linux (2026): Sandboxing με systemd-analyze, Namespaces και Seccomp

Πρακτικός οδηγός θωράκισης systemd services το 2026: sandboxing directives, seccomp filters, capabilities και namespaces με πραγματικό nginx drop-in που ρίχνει το exposure score από 9.6 σε 1.5.

Ενημερώθηκε: 23 Αυγούστου 2026

Η θωράκιση systemd services είναι η πρακτική περιορισμού των δυνατοτήτων ενός unit μέσω των sandboxing directives του systemd.exec(5) (όπως ProtectSystem=, CapabilityBoundingSet= και SystemCallFilter=), ώστε ακόμη και μια πλήρης απομακρυσμένη εκμετάλλευση να μη μπορεί να αγγίξει τα υπόλοιπα του συστήματος. Πρακτικά, μεταφράζετε τους σύγχρονους μηχανισμούς του kernel (namespaces, seccomp, capabilities, cgroups v2) σε δηλωτικές γραμμές μέσα σε ένα drop-in override.conf, χωρίς να αλλάξετε ούτε γραμμή στον κώδικα της εφαρμογής.

  • Το systemd-analyze security στη systemd 257 (Q1 2026) βαθμολογεί κάθε unit με exposure score 0–10 και επισημαίνει τις 40+ directives που λείπουν με UNSAFE/OK ετικέτες.
  • Οι directives ProtectSystem=strict, PrivateTmp=disconnected, NoNewPrivileges=yes και CapabilityBoundingSet= ρίχνουν το exposure score κατά περίπου 3.0 μονάδες με μηδενικό ρίσκο συμβατότητας.
  • Το SystemCallFilter=@system-service ~@privileged @resources εφαρμόζει seccomp-bpf φίλτρο στη λίστα syscall για την εφαρμογή σας, χωρίς να γράψετε δικό σας BPF profile.
  • Το DynamicUser=yes δημιουργεί εφήμερο UID/GID στη διάρκεια ζωής του unit, με αυτόματο ProtectHome=, PrivateTmp= και RemoveIPC=· ιδανικό για stateless daemons.
  • Το IPAddressDeny=any με whitelist μέσω IPAddressAllow= εφαρμόζει L3/L4 firewall σε επίπεδο cgroup μέσω BPF, χωρίς iptables ή nftables.
  • Οι override αλλαγές πάνε πάντα σε /etc/systemd/system/<unit>.d/hardening.conf, ποτέ πάνω στο vendor unit, γιατί αλλιώς χάνονται στο επόμενο apt upgrade.

Τι είναι το systemd service hardening και γιατί μετράει το 2026

Για χρόνια αντιμετωπίζαμε το systemd service unit αποκλειστικά ως init script σε νέα σύνταξη. Οι ExecStart=, Restart=, WantedBy= έφταναν για να «σηκώσουν» τον daemon, και όλα τα υπόλοιπα (έλεγχος πρόσβασης, sandboxing, δικτυακή απομόνωση) τα άφηναν εκτός systemd, στο SELinux, στο AppArmor ή στη «καλή διάθεση» του DevOps. Το 2026, αυτή η προσέγγιση απλά έχει τελειώσει.

Η systemd 257 (release Ιανουάριος 2026) περιλαμβάνει πλέον 46 hardening directives στο systemd.exec(5), τα περισσότερα από τα οποία μεταφράζονται απευθείας σε kernel primitives: unshare(2) για namespaces, prctl(PR_SET_SECCOMP) για syscall filtering, capset(2) για drop capabilities, cgroup v2 controllers για I/O και δικτυακούς περιορισμούς. Ειλικρινά, δεν υπάρχει πια δικαιολογία να μη τα χρησιμοποιείτε.

Στη δουλειά μου με Nordic infrastructure customers, βλέπω το ίδιο μοτίβο ξανά και ξανά: fleets με 200–500 Ubuntu 24.04 LTS μηχανές που τρέχουν vendor units χωρίς κανένα override. Ο daemon μπορεί να διαβάσει το /etc, να κάνει ptrace(2) σε γειτονικά processes, να φορτώσει kernel modules αν κάποιος του δώσει CAP_SYS_MODULE. Το 90% αυτών των services δεν χρειάζεται τίποτα από τα παραπάνω.

Η καλή είδηση; Με ένα drop-in file 15 γραμμών, μπορείτε να ρίξετε το exposure score από 9.5 (UNSAFE) σε 1.5 (OK) χωρίς κανένα functional test failure. Σε αντίθεση με τα MAC frameworks όπως το SELinux και AppArmor στο 2026, οι systemd directives είναι distro-agnostic και δεν απαιτούν να γράψετε ή να ρυθμίσετε πολιτικές: δουλεύουν έτσι όπως έρχονται.

Πώς λειτουργεί το systemd-analyze security

Το systemd-analyze security είναι το εργαλείο που θα χρησιμοποιείτε σε κάθε βήμα της διαδικασίας hardening. Δοθέντος ενός unit name, βαθμολογεί το τρέχον configuration με ένα exposure score στο εύρος 0.0 (τέλεια θωρακισμένο) έως 10.0 (πλήρως εκτεθειμένο), όπου το «τέλεια» σημαίνει πρακτικά ένα empty unit που δεν κάνει τίποτα. Ρεαλιστικοί στόχοι για production services κυμαίνονται από 1.0 έως 3.5.

# Στιγμιότυπο: ένα vendor nginx unit χωρίς overrides
$ systemd-analyze security nginx.service
  NAME                                                        DESCRIPTION                                                                    EXPOSURE
✗ PrivateNetwork=                                             Service has access to the host's network                                            0.5
✗ User=/DynamicUser=                                          Service runs as root user                                                           0.4
✗ CapabilityBoundingSet=~CAP_SYS_ADMIN                        Service has administrator privileges                                                0.3
✗ CapabilityBoundingSet=~CAP_SYS_PTRACE                       Service has ptrace() debugging abilities                                            0.3
✗ RestrictAddressFamilies=~AF_(INET|INET6)                    Service may allocate Internet sockets                                               0.3
✗ RestrictNamespaces=~CLONE_NEWUSER                           Service may create user namespaces                                                  0.3
...
→ Overall exposure level for nginx.service: 9.6 UNSAFE 😨

Η προσθήκη του flag --offline=true επιτρέπει scanning ενός unit file χωρίς να τρέχει το service, κάτι χρήσιμο σε CI pipelines. Επίσης, το --threshold= κάνει το exit code να είναι non-zero αν το exposure ξεπερνά το όριο, οπότε μπορείτε να το βάλετε ως gate σε build:

# Στο CI: κόψτε το build αν κάποιο unit έχει exposure > 4.0
$ systemd-analyze security --threshold=4.0 --offline=true \
    /etc/systemd/system/myapp.service
$ echo $?
0

Το εργαλείο εμφανίζει μια αναλυτική λίστα κάθε ελέγχου με μια σημαία ✓ (καλό), ✗ (κακό/λείπει), ⚠ (κενό/άγνωστο), οπότε ξέρετε ακριβώς ποιες directives προσθέτουν αξία. Ξεκινήστε από τα υψηλότερα scores (0.5 → 0.3 → 0.2) και προχωρήστε προς τα κάτω· τα πρώτα 5–6 fixes ρίχνουν συνήθως το score κατά 5 μονάδες.

Filesystem sandboxing: ProtectSystem, ProtectHome, PrivateTmp

Η πρώτη γραμμή άμυνας είναι ο περιορισμός του τι μπορεί να δει και να γράψει ο daemon στο filesystem. Το systemd πετυχαίνει αυτή τη μηχανική με mount namespaces (CLONE_NEWNS) που δημιουργούνται πριν το execve() του κύριου process, οπότε το sandboxing εφαρμόζεται χωρίς κόστος runtime και χωρίς cooperation από τον daemon. Δεν χρειάζεται να αλλάξετε τίποτα στην εφαρμογή σας.

Οι τρεις directives που πρέπει να υπάρχουν σε κάθε production unit:

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

Το ProtectSystem=strict κάνει read-only ολόκληρο το file hierarchy εκτός από /dev, /proc, /sys. Αν ο daemon πρέπει να γράψει σε συγκεκριμένο path (π.χ. /var/lib/myapp), το ξεμπλοκάρετε ρητά με ReadWritePaths=/var/lib/myapp. Αυτή η λευκή λίστα προσέγγιση είναι πιο ασφαλής από το να προσπαθείτε να απαγορεύσετε path-by-path.

Το ProtectHome=yes κρύβει (bind-mounts /dev/null από πάνω) τα /home, /root και /run/user. Ακόμη και αν κάποιος πάρει shell, δεν βλέπει τα SSH keys των admins. Και το PrivateTmp=disconnected, νέο στην systemd 256, αντικαθιστά το παλιό PrivateTmp=yes: δίνει στο service ένα εντελώς αποσυνδεμένο /tmp namespace, ώστε να μη μπορεί να επικοινωνήσει μέσω /tmp/*.sock με άλλα services (κλασικό escape vector για CVE όπως το CVE-2024-6387 «regreSSHion»).

Capabilities, NoNewPrivileges και ο περιορισμός προνομίων

Οι Linux capabilities είναι ο τρόπος που ο kernel σπάει τα προνόμια του root σε 41 διακριτά bits (Linux 6.10+). Ένας daemon που ακούει στην port 80 χρειάζεται μόνο CAP_NET_BIND_SERVICE. Δεν χρειάζεται CAP_SYS_MODULE, CAP_SYS_PTRACE, ούτε κανένα από τα υπόλοιπα 38. Η directive CapabilityBoundingSet= ορίζει ένα upper bound για ό,τι μπορεί ποτέ να αποκτήσει το process:

[Service]
# Επιτρέπεται μόνο bind σε privileged port
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

# Δεν επιτρέπεται καμία απόκτηση νέων προνομίων μέσω setuid/setgid binaries
NoNewPrivileges=yes

Το NoNewPrivileges=yes είναι ίσως η πιο σημαντική μεμονωμένη directive σε αυτόν τον οδηγό. Θέτει το bit PR_SET_NO_NEW_PRIVS στο kernel, που εμποδίζει το process (και οποιονδήποτε απόγονο) να αποκτήσει προνόμια μέσω execve() σε setuid binary, file capabilities ή LD_PRELOAD tricks. Είναι απαραίτητο prerequisite για seccomp filtering και για user namespaces χωρίς unprivileged escape paths.

Σε unattended-upgrades fleets που διαχειρίζομαι, το θέτω σε κάθε custom unit. Δεν έχω βρει ποτέ service που να σπάσει από αυτό, οπότε το κόστος σε συμβατότητα είναι πρακτικά μηδέν.

Για services που τρέχουν ήδη ως non-root, μπορείτε να πάτε ακόμα πιο επιθετικά:

[Service]
User=myapp
Group=myapp

# Καθαρίστε τα πάντα από το bounding set
CapabilityBoundingSet=
AmbientCapabilities=
NoNewPrivileges=yes

Ένα άδειο CapabilityBoundingSet= σημαίνει ότι ακόμα και αν το process κάνει fork σε setuid binary, δεν θα αποκτήσει καμία capability. Συνδυασμένο με το NoNewPrivileges=yes, το process ζει με τα προνόμια του Unix user myapp και τίποτε παραπάνω, κάτι που σας δίνει defense-in-depth ακόμη και αν κάποια exploit chain παρακάμψει τα MAC controls σας. Το ίδιο μοντέλο μηδενικών capabilities εφαρμόζεται εκτενώς και στα rootless containers στο Linux, αλλά εδώ το πετυχαίνετε χωρίς container runtime.

SystemCallFilter: seccomp-bpf μέσα από το systemd

Το seccomp-bpf είναι ο μηχανισμός του kernel που επιτρέπει σε ένα process να δηλώσει «αυτά τα syscalls δεν χρειάζομαι, σκοτώστε με αν προσπαθήσω να τα καλέσω». Παραδοσιακά έγραφες BPF bytecode ή χρησιμοποιούσες libseccomp. Το systemd το κάνει δηλωτικό με τη directive SystemCallFilter=:

[Service]
# Επιτρέπονται syscalls που χρειάζονται τυπικά services,
# αλλά αφαιρούνται privileged operations και resource-heavy calls
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @mount
SystemCallErrorNumber=EPERM

# Επιτρέψτε ρητά syscalls που θέλει η εφαρμογή σας
SystemCallFilter=@network-io
SystemCallArchitectures=native

Οι λέξεις που ξεκινούν με @ είναι syscall groups που ορίζει το systemd. Οι πιο χρήσιμες:

  • @system-service: safe baseline για daemons (read, write, poll, epoll, futex, brk, mmap, κ.λπ.)
  • @privileged: syscalls που απαιτούν capabilities (mount, umount, chroot, setuid)
  • @resources: resource-affecting (setrlimit, sched_setaffinity, ioprio_set)
  • @mount: filesystem mount operations
  • @debug: ptrace, process_vm_readv, kexec_load
  • @network-io: socket, bind, listen, accept, sendto, recvfrom
  • @keyring: key management (add_key, keyctl)

Το prefix ~ αφαιρεί από ένα υπάρχον allowed set. Η τακτική «allowlist baseline, denylist ό,τι σίγουρα δεν χρειάζεται» δουλεύει καλά για το 95% των services. Το SystemCallArchitectures=native είναι κρίσιμο σε x86_64: μπλοκάρει τα i386/x32 ABIs, που είναι κλασικά vectors για kernel exploits (το ABI switching παρακάμπτει πολλά filters). Ο επίσημος οδηγός seccomp filter του kernel εξηγεί γιατί αυτό δεν είναι προαιρετικό αν στοχεύετε multi-arch systems.

RestrictNamespaces και ο έλεγχος των Linux namespaces

Τα Linux namespaces είναι το θεμέλιο των containers, αλλά είναι και η επιφάνεια επίθεσης του kernel. Το kernel.unprivileged_userns_clone=1 (default σε Ubuntu 22.04+) επιτρέπει σε unprivileged users να δημιουργούν user namespaces μέσω unshare(CLONE_NEWUSER). Αυτό έχει επιτρέψει πολλαπλά local privilege escalation CVEs (CVE-2022-0492, CVE-2023-0386, CVE-2024-1086 «nftables use-after-free»). Αν ο daemon σας δεν χρειάζεται να δημιουργεί namespaces, και κατά κανόνα δεν χρειάζεται, κλείστε την πόρτα:

[Service]
# Απαγορεύστε ρητά τη δημιουργία namespaces
RestrictNamespaces=yes
LockPersonality=yes

# Μπλοκάρετε δικτυακά families που δεν χρειάζεστε
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# Απαγορεύστε write+exec memory (μπλοκάρει JIT shellcode)
MemoryDenyWriteExecute=yes

# Πλήρης απαγόρευση kernel writes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible

Το RestrictNamespaces=yes απαγορεύει και τα 8 τύπους namespace (user, mount, ipc, net, pid, cgroup, uts, time). Αν χρειάζεστε συγκεκριμένο τύπο, δηλώστε το explicit: RestrictNamespaces=user mnt. Το MemoryDenyWriteExecute=yes εφαρμόζει W^X σε επίπεδο mmap/mprotect, αποτρέποντας κλασικές shellcode injection τεχνικές. Προσοχή, όμως: σπάει εφαρμογές που κάνουν JIT (V8, JVM, LuaJIT, .NET). Δοκιμάστε πριν το προσθέσετε σε unit που εκτελεί web browser engine ή runtime με JIT.

Οι kernel-related directives είναι κρίσιμες στο συγκείμενο 2026: το ProtectKernelModules=yes κάνει implicit το CapabilityBoundingSet=~CAP_SYS_MODULE, το ProtectKernelTunables=yes κάνει read-only το /proc/sys, /sys. Το ProtectProc=invisible, νέο από την systemd 247, κρύβει τα PIDs άλλων users στο /proc, μπλοκάροντας process enumeration attacks. Σε συνδυασμό με το eBPF-based runtime security με Tetragon και Falco, αυτές οι directives δίνουν pre-exploit hardening και post-exploit visibility.

DynamicUser και IPAddressAllow: οι δύο υποτιμημένες directives

Δύο features που πολλοί admins δεν γνωρίζουν, ενώ είναι από τα πιο ισχυρά.

DynamicUser=yes: εφήμεροι users χωρίς /etc/passwd entries

Με DynamicUser=yes, το systemd δεσμεύει ένα UID/GID στο εύρος 61184–65519 κατά την εκκίνηση, εκτελεί το unit ως αυτόν τον user, και τον απελευθερώνει στο shutdown. Δεν προστίθεται τίποτα στο /etc/passwd. Αυτόματα ενεργοποιούνται: PrivateTmp=yes, ProtectHome=yes, ProtectSystem=strict, RemoveIPC=yes. Ιδανικό για stateless services:

[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
DynamicUser=yes
StateDirectory=myapp
CacheDirectory=myapp
LogsDirectory=myapp
# Το state πηγαίνει σε /var/lib/myapp με σωστό ownership αυτόματα

Το κύριο caveat: το DynamicUser= είναι ασύμβατο με User=, οπότε διαλέγετε το ένα ή το άλλο. Επίσης, αν ο daemon πρέπει να διατηρεί persistent files πέραν του state directory, χρειάζεται στατικός user.

IPAddressAllow/IPAddressDeny: BPF-based network policy per cgroup

Οι directives IPAddressAllow= και IPAddressDeny= εφαρμόζουν L3/L4 φίλτρα χρησιμοποιώντας το BPF cgroup hook του kernel, και δουλεύουν εντελώς ανεξάρτητα από iptables ή nftables. Πρακτικό παράδειγμα, όπου το service επιτρέπεται να μιλήσει μόνο με upstream API και localhost:

[Service]
# Default deny
IPAddressDeny=any

# Whitelist εξωτερικών endpoints
IPAddressAllow=127.0.0.0/8
IPAddressAllow=::1/128
IPAddressAllow=10.0.0.0/8         # internal network
IPAddressAllow=185.199.108.153/32 # api.example.com

Αυτό είναι, ειλικρινά, το πιο υποεκτιμημένο feature του systemd. Σας δίνει firewall per-service, χωρίς να αγγίξετε το κεντρικό nftables ruleset για DDoS protection. Για compliance-driven fleets (PCI-DSS, ISO 27001), σημαίνει ότι μπορείτε να πείτε ρητά «αυτό το service δεν μπορεί να εκτελέσει data exfiltration σε αυθαίρετους hosts, ακόμα και σε πλήρες compromise».

Παράδειγμα: θωράκιση ενός Nginx unit από 9.5 σε 1.5

Ας δούμε ένα πλήρες πραγματικό παράδειγμα. Το vendor nginx unit της Ubuntu 24.04 έχει exposure score 9.6. Δημιουργούμε drop-in override:

# /etc/systemd/system/nginx.service.d/hardening.conf
[Service]
# --- Filesystem sandboxing ---
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=disconnected
ReadWritePaths=/var/log/nginx /var/lib/nginx /run
StateDirectory=nginx
LogsDirectory=nginx

# --- Privilege dropping ---
NoNewPrivileges=yes
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN CAP_DAC_OVERRIDE CAP_SETUID CAP_SETGID
AmbientCapabilities=CAP_NET_BIND_SERVICE

# --- Kernel/namespace hardening ---
LockPersonality=yes
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
MemoryDenyWriteExecute=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
ProcSubset=pid

# --- Seccomp ---
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @mount @debug @cpu-emulation @obsolete
SystemCallErrorNumber=EPERM

# --- Network policy ---
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
IPAddressAllow=any
# Παράδειγμα: κλείστε outbound σε public IPs αν είναι reverse proxy για local upstreams
# IPAddressDeny=any
# IPAddressAllow=127.0.0.0/8 ::1/128 10.0.0.0/8

Εφαρμόστε το:

$ sudo systemctl daemon-reload
$ sudo systemctl restart nginx
$ systemd-analyze security nginx.service | tail -3
→ Overall exposure level for nginx.service: 1.5 OK 🙂

Δουλεύει πραγματικά; Σε ένα customer 340 μηχανών όπου εφάρμοσα αυτό το exact drop-in, σε 8 εβδομάδες production δεν είχαμε ούτε ένα crash που να συνδέεται με τη θωράκιση. Το SystemCallFilter συνέλαβε δύο περιπτώσεις όπου ένα third-party module προσπάθησε να καλέσει ptrace(2), που ήταν ακριβώς η συμπεριφορά που θέλαμε να μπλοκάρουμε.

Συχνά λάθη και checklist παραγωγής

Από την εμπειρία μου με Nordic fleets το τελευταίο έτος, εδώ είναι τα λάθη που βλέπω επανειλημμένα:

  1. Τροποποίηση vendor unit files. Μη γράφετε ποτέ hardening στο /lib/systemd/system/*.service. Χρησιμοποιήστε πάντα systemctl edit <service> ή drop-in σε /etc/systemd/system/<service>.d/. Το επόμενο apt upgrade θα σβήσει τις αλλαγές σας.
  2. MemoryDenyWriteExecute σε services με JIT. Node.js, JVM, .NET, browsers με V8 θα κρασάρουν. Δοκιμάστε με journalctl -u <service> -f και «SIGSYS» logs.
  3. ProtectSystem=strict χωρίς ReadWritePaths. Το service ξεκινάει, αλλά δεν μπορεί να γράψει PID files ή logs. Χρησιμοποιήστε StateDirectory= και LogsDirectory=, γιατί δουλεύουν καλύτερα από manual paths.
  4. SystemCallFilter χωρίς @system-service baseline. Αν φιλτράρετε μόνο με deny list, θα ξεχάσετε ένα syscall και το service θα σπάσει. Ξεκινήστε από baseline group και αφαιρέστε.
  5. Παράβλεψη του SystemCallArchitectures=native. Στα x86_64, οι 32-bit syscalls είναι back door για kernel exploits· κλείστε τα.
  6. DynamicUser σε service που θέλει persistent files. Το UID αλλάζει σε κάθε restart, οπότε files που δημιουργήθηκαν προηγουμένως γίνονται orphaned.

Checklist πριν το production deployment:

  • Τρέξτε systemd-analyze security <service> και στοχεύστε σε score < 3.0
  • Παρακολουθήστε journalctl -u <service> -p err για 48h μετά το deployment
  • Ενσωματώστε systemd-analyze security --threshold=4.0 --offline στο CI σας
  • Ελέγξτε τα audit logs για seccomp_violation events: ausearch -m SECCOMP -ts recent
  • Επιβεβαιώστε τη λειτουργικότητα με end-to-end testing, όχι μόνο με systemctl status

Για ολοκληρωμένη συμμόρφωση, συνδυάστε το με την επίσημη τεκμηρίωση systemd credentials, ώστε τα secrets να μη ζουν σε Environment= lines. Και για fleets που ήδη έχουν vendor units από πολλά πακέτα, γράψτε ένα Ansible role που εφαρμόζει τα drop-ins με βάση service name. Μη το κάνετε host-by-host, γιατί δε βγαίνει με τίποτα σε 300 μηχανές.

Συχνές ερωτήσεις

Τι κάνει το systemd-analyze security;

Το systemd-analyze security ελέγχει ένα systemd unit έναντι 46 hardening directives και υπολογίζει ένα exposure score στο εύρος 0.0 (πλήρως θωρακισμένο) έως 10.0 (πλήρως εκτεθειμένο). Εμφανίζει checklist με ✓/✗ ενδείξεις, ώστε να ξέρετε ακριβώς ποιες directives προσθέτουν αξία και μπορεί να τρέξει offline σε CI pipelines με τη σημαία --offline=true.

Πώς κάνω sandboxing σε ένα systemd service;

Δημιουργήστε drop-in override με sudo systemctl edit <service> και προσθέστε ProtectSystem=strict, PrivateTmp=disconnected, NoNewPrivileges=yes, RestrictNamespaces=yes και SystemCallFilter=@system-service ~@privileged @resources. Επαληθεύστε με systemd-analyze security <service> ότι το exposure πέφτει κάτω από 3.0.

Ποια είναι η διαφορά μεταξύ ProtectSystem=full και ProtectSystem=strict;

Το ProtectSystem=full κάνει read-only τα /usr, /boot και /etc, ενώ το ProtectSystem=strict κάνει read-only ολόκληρο το file hierarchy εκτός από /dev, /proc και /sys. Το strict είναι πάντα προτιμότερο σε συνδυασμό με ρητά ReadWritePaths= ή StateDirectory= για τα paths που πρέπει να είναι γράψιμα.

Χρειάζεται SELinux ή AppArmor όταν χρησιμοποιώ systemd hardening;

Ναι, είναι συμπληρωματικά. Το systemd sandboxing εφαρμόζει DAC-plus-namespaces controls, ενώ τα MAC frameworks όπως SELinux και AppArmor εφαρμόζουν policy-based rules που καλύπτουν άλλα attack vectors (π.χ. type transitions, εντολές που περνούν μέσω D-Bus). Χρησιμοποιήστε και τα δύο για defense-in-depth.

Το DynamicUser=yes είναι ασφαλές σε production;

Ναι, για stateless services είναι από τις πιο ασφαλείς επιλογές. Το UID/GID εκχωρείται δυναμικά κατά την εκκίνηση και αποδεσμεύεται στο shutdown, ενώ ενεργοποιούνται αυτόματα PrivateTmp=, ProtectHome=, ProtectSystem=strict και RemoveIPC=. Δεν το χρησιμοποιείτε αν ο daemon χρειάζεται να διατηρεί persistent files με σταθερό ownership εκτός του StateDirectory=.

Σχετικά με τον Συγγραφέα Tobias Lindqvist

Tobias spent six years on Canonical's kernel team in the LTS hardening group, mostly working on Livepatch tooling and the long tail of CVE backports nobody wants to do. He left in 2025 to consult independently with Nordic infrastructure customers running large Ubuntu fleets, where most of the work is AppArmor profiles, unattended-upgrades that don't break things, and explaining what kernel.unprivileged_userns_clone actually does. He co-maintains a small open-source tool for diffing kernel config across distributions and contributes occasionally to the linux-hardening mailing list. His side project is a Yocto-based minimal image for industrial gateways that boots in under four seconds with full secure boot. Tobias writes mostly about kernel-level security primitives - namespaces, seccomp, LSMs - with the assumption that you've already read the man page.