systemd: switch to deny-everything-by-default (TemporaryFileSystem=/) with explicit BindReadOnlyPaths + BindPaths allowlist
build-and-publish / build (debian:13, NEXUS_PASS_TRIXIE, NEXUS_REPO_TRIXIE, NEXUS_USER_TRIXIE, trixie) (push) Successful in 3m49s
build-and-publish / build (ubuntu:26.04, NEXUS_PASS_RACCOON, NEXUS_REPO_RACCOON, NEXUS_USER_RACCOON, raccoon) (push) Successful in 3m12s

This commit is contained in:
root
2026-05-15 18:22:16 +00:00
parent 9e8d14bd5d
commit a8966ac108
2 changed files with 64 additions and 20 deletions
+32 -10
View File
@@ -15,10 +15,37 @@ ExecStop=/bin/sh -c "/bin/kill -s QUIT $(/bin/cat /run/nginx.pid)"
TimeoutStartSec=10 TimeoutStartSec=10
LimitNOFILE=65535 LimitNOFILE=65535
# === hardening (compatible with LuaJIT + nginx workers + raweb agent) === # === hardening: deny-everything by default, allowlist via bind mounts ===
# TemporaryFileSystem=/ replaces the visible filesystem with an empty tmpfs.
# Everything not bind-mounted below is invisible to nginx workers — even
# read access. Compromise of a worker can no longer enumerate /etc/passwd,
# /home/*, /var/lib/*, /root, /opt, etc.
TemporaryFileSystem=/
# Read-only: nginx binary, dynamic linker, all linked libs, system config,
# CA bundles, Let's Encrypt certs (live/ + archive/ both under /etc).
BindReadOnlyPaths=/usr
BindReadOnlyPaths=/lib
BindReadOnlyPaths=/lib64
BindReadOnlyPaths=/bin
BindReadOnlyPaths=/sbin
BindReadOnlyPaths=/etc
# Read-only: vhost docroots + app projects. Add a new line here when you
# add a vhost whose root isn't under one of these parents.
BindReadOnlyPaths=/raweb
BindReadOnlyPaths=/srv
BindReadOnlyPaths=/hostdata
# Read-write: nginx runtime state.
# /run nginx.pid, nginx.lock, /run/nginx/temp/*, PHP-FPM sock
# /var/log/nginx access.log, error.log
# /nginx config dir (read-mostly but reload writes some state)
BindPaths=/run
BindPaths=/var/log/nginx
BindPaths=/nginx
NoNewPrivileges=true NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true ProtectKernelTunables=true
ProtectKernelModules=true ProtectKernelModules=true
ProtectKernelLogs=true ProtectKernelLogs=true
@@ -35,16 +62,11 @@ LockPersonality=true
SystemCallArchitectures=native SystemCallArchitectures=native
SystemCallFilter=@system-service SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM SystemCallErrorNumber=EPERM
# ProtectSystem=strict makes the entire filesystem read-only EXCEPT these.
# /run covers nginx.pid, nginx.lock, and the temp/ subdir (all tmpfs).
ReadWritePaths=/run /var/log/nginx /nginx /hostdata
# Read-only paths nginx legitimately accesses. ProtectSystem=strict already
# allows reads everywhere by default — these are documented for the operator's
# benefit (and so they survive future hardening tightening).
ReadOnlyPaths=/raweb /srv /etc/letsencrypt
# NOTE deliberately OFF: # NOTE deliberately OFF:
# MemoryDenyWriteExecute=true breaks LuaJIT (JIT writable+executable pages) # MemoryDenyWriteExecute=true breaks LuaJIT (JIT writable+executable pages)
# SystemCallFilter=~@resources breaks nginx workers' prlimit64() # SystemCallFilter=~@resources breaks nginx workers' prlimit64()
# ProtectSystem and ProtectHome are redundant under TemporaryFileSystem=/.
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
+32 -10
View File
@@ -15,10 +15,37 @@ ExecStop=/bin/sh -c "/bin/kill -s QUIT $(/bin/cat /run/nginx.pid)"
TimeoutStartSec=10 TimeoutStartSec=10
LimitNOFILE=65535 LimitNOFILE=65535
# === hardening (compatible with LuaJIT + nginx workers + raweb agent) === # === hardening: deny-everything by default, allowlist via bind mounts ===
# TemporaryFileSystem=/ replaces the visible filesystem with an empty tmpfs.
# Everything not bind-mounted below is invisible to nginx workers — even
# read access. Compromise of a worker can no longer enumerate /etc/passwd,
# /home/*, /var/lib/*, /root, /opt, etc.
TemporaryFileSystem=/
# Read-only: nginx binary, dynamic linker, all linked libs, system config,
# CA bundles, Let's Encrypt certs (live/ + archive/ both under /etc).
BindReadOnlyPaths=/usr
BindReadOnlyPaths=/lib
BindReadOnlyPaths=/lib64
BindReadOnlyPaths=/bin
BindReadOnlyPaths=/sbin
BindReadOnlyPaths=/etc
# Read-only: vhost docroots + app projects. Add a new line here when you
# add a vhost whose root isn't under one of these parents.
BindReadOnlyPaths=/raweb
BindReadOnlyPaths=/srv
BindReadOnlyPaths=/hostdata
# Read-write: nginx runtime state.
# /run nginx.pid, nginx.lock, /run/nginx/temp/*, PHP-FPM sock
# /var/log/nginx access.log, error.log
# /nginx config dir (read-mostly but reload writes some state)
BindPaths=/run
BindPaths=/var/log/nginx
BindPaths=/nginx
NoNewPrivileges=true NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true ProtectKernelTunables=true
ProtectKernelModules=true ProtectKernelModules=true
ProtectKernelLogs=true ProtectKernelLogs=true
@@ -35,16 +62,11 @@ LockPersonality=true
SystemCallArchitectures=native SystemCallArchitectures=native
SystemCallFilter=@system-service SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM SystemCallErrorNumber=EPERM
# ProtectSystem=strict makes the entire filesystem read-only EXCEPT these.
# /run covers nginx.pid, nginx.lock, and the temp/ subdir (all tmpfs).
ReadWritePaths=/run /var/log/nginx /nginx /hostdata
# Read-only paths nginx legitimately accesses. ProtectSystem=strict already
# allows reads everywhere by default — these are documented for the operator's
# benefit (and so they survive future hardening tightening).
ReadOnlyPaths=/raweb /srv /etc/letsencrypt
# NOTE deliberately OFF: # NOTE deliberately OFF:
# MemoryDenyWriteExecute=true breaks LuaJIT (JIT writable+executable pages) # MemoryDenyWriteExecute=true breaks LuaJIT (JIT writable+executable pages)
# SystemCallFilter=~@resources breaks nginx workers' prlimit64() # SystemCallFilter=~@resources breaks nginx workers' prlimit64()
# ProtectSystem and ProtectHome are redundant under TemporaryFileSystem=/.
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target