Hệ điều hành · 02/10/2026

Hardening dịch vụ Linux bằng systemd sandbox: Từ baseline đến production

Một daemon chạy bằng user riêng vẫn có thể nhìn thấy quá nhiều filesystem, thiết bị, process và kernel interface của máy chủ. systemd cung cấp nhiều sandbox directive để thu hẹp quyền của từng service mà không cần đóng gói nó thành container: biến hệ thống thành chỉ đọc, tạo /tmp riêng, loại capability, giới hạn address family và chặn nhóm system call không cần thiết.

Hardening dịch vụ Linux bằng systemd sandbox: Từ baseline đến production

Một daemon chạy bằng user riêng vẫn có thể nhìn thấy quá nhiều filesystem, thiết bị, process và kernel interface của máy chủ. systemd cung cấp nhiều sandbox directive để thu hẹp quyền của từng service mà không cần đóng gói nó thành container: biến hệ thống thành chỉ đọc, tạo /tmp riêng, loại capability, giới hạn address family và chặn nhóm system call không cần thiết.

Hardening hiệu quả không phải là chép một block cấu hình thật dài. Quy trình đúng là đo baseline, xác định tài nguyên service thực sự cần, thêm từng lớp nhỏ, kiểm thử workload thật và giữ cách rollback. Bài này xây một lộ trình như vậy cho system service trên Linux.

1. Xác định threat model trước khi sửa unit

Sandbox nhằm giảm hậu quả khi daemon bị khai thác hoặc dependency bị chiếm quyền. Nó có thể ngăn process ghi vào /usr//etc, đọc home directory, tải kernel module hoặc mở loại socket không cần thiết. Nhưng sandbox không sửa lỗi ứng dụng, không thay cập nhật bảo mật, firewall, SELinux/AppArmor hay phân quyền dữ liệu nghiệp vụ.

Trước khi cấu hình, trả lời bốn câu hỏi:

  • Service phải đọc và ghi những path nào?
  • Nó cần network Internet, UNIX socket hay chỉ loopback?
  • Nó có thực sự cần chạy root hoặc giữ Linux capability nào?
  • Nó có dùng JIT, namespace, thiết bị, ptrace hoặc thao tác kernel đặc biệt không?
Mục tiêu là quyền tối thiểu có thể chứng minh bằng test, không phải điểm số thấp nhất bằng mọi giá.

2. Đọc cấu hình hiệu lực và tạo baseline

systemd --version
systemctl cat myapp.service
systemctl show myapp.service -p User -p Group -p DynamicUser \
  -p ExecStart -p FragmentPath -p DropInPaths
systemd-analyze security myapp.service

systemctl cat hiển thị vendor unit và mọi drop-in đang được ghép. Không sửa trực tiếp file trong /usr/lib/systemd/system hoặc /lib/systemd/system, vì package upgrade có thể ghi đè. Dùng:

sudo systemctl edit myapp.service

Lệnh này tạo drop-in dưới /etc/systemd/system/myapp.service.d/. Lưu output systemd-analyze security làm baseline, nhưng đừng coi nó là vulnerability scanner. Công cụ đánh giá mức sử dụng các cơ chế isolation theo model chung; một service có nhiệm vụ đặc quyền hợp lệ có thể có exposure score cao hơn daemon thông thường.

3. Lớp đầu tiên: identity và ngăn tăng quyền

[Service]
User=myapp
Group=myapp
UMask=0077
NoNewPrivileges=yes
RestrictSUIDSGID=yes

Chạy service bằng account riêng giới hạn quyền DAC. UMask=0077 khiến file mới mặc định không mở cho group/other; nếu service cần file chia sẻ, chọn mask phù hợp thay vì dùng 0777 sau đó.

Theo tài liệu systemd.exec chính thức, NoNewPrivileges=yes bảo đảm process và con của nó không thể nhận quyền mới qua execve(), ví dụ từ setuid/setgid bit hoặc file capability. Đây là lớp hiệu quả, nhưng có thể làm hỏng chương trình cố gọi một helper setuid. RestrictSUIDSGID=yes còn ngăn tạo file có bit SUID/SGID trong phạm vi service.

Nếu service phải bind port thấp hoặc thực hiện một thao tác đặc quyền nhỏ, cân nhắc cấp capability tối thiểu thay vì chạy root:

CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

Với daemon không cần capability nào, đặt CapabilityBoundingSet= và AmbientCapabilities= rỗng. Hãy test kỹ: capability bị loại khỏi bounding set không thể lấy lại trong service.

4. Biến filesystem thành chỉ đọc, rồi mở đúng nơi cần ghi

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

StateDirectory=myapp
LogsDirectory=myapp
RuntimeDirectory=myapp

ProtectSystem=strict làm gần như toàn bộ filesystem hierarchy chỉ đọc trong mount namespace của service, ngoại trừ các API filesystem cần thiết. ProtectHome=yes làm /home, /root và /run/user không truy cập được. PrivateTmp=yes cấp namespace /tmp và /var/tmp riêng; file tạm không còn dùng để trao đổi với service khác.

Các directive directory là cách an toàn để cấp vùng ghi do systemd quản lý:

DirectivePath thường tạoDùng cho
StateDirectory=myapp/var/lib/myappDữ liệu bền vững
LogsDirectory=myapp/var/log/myappLog nếu không gửi journal
RuntimeDirectory=myapp/run/myappPID, socket, state tạm theo boot
CacheDirectory=myapp/var/cache/myappDữ liệu có thể tạo lại

Nếu ứng dụng bắt buộc ghi một path legacy, dùng allowlist nhỏ:

ReadWritePaths=/srv/myapp/uploads
ReadOnlyPaths=/etc/myapp

ReadWritePaths= chỉ mở quyền ghi trong namespace; nó không vượt qua owner/mode/ACL và không làm writable một filesystem vốn mount read-only ở host. Tránh mở cả /var chỉ vì chưa biết ứng dụng ghi ở đâu; dùng audit/log hoặc strace trong môi trường thử nghiệm để tìm path thật.

5. Tách thiết bị, process và kernel interface

[Service]
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
LockPersonality=yes
RestrictRealtime=yes

Nhóm này phù hợp với phần lớn network daemon không quản trị host:

  • PrivateDevices=yes cấp một /dev tối giản và loại quyền truy cập nhiều thiết bị vật lý.
  • ProtectKernelTunables=yes chặn sửa các kernel tunable như phần thuộc /proc/sys.
  • ProtectKernelModules=yes chặn explicit module load/unload và loại capability liên quan.
  • ProtectControlGroups=yes làm cgroup hierarchy chỉ đọc trong phạm vi service.
  • ProtectKernelLogs=yes hạn chế đọc kernel log buffer.
  • LockPersonality=yes ngăn đổi ABI personality.

Không áp dụng mù quáng cho container runtime, hardware agent, VPN daemon, NTP service hoặc công cụ giám sát mức thấp. Những service đó có thể cần thiết bị, network namespace, clock, cgroup hoặc kernel API mà daemon web không cần.

6. Giới hạn socket theo giao thức thật sự sử dụng

[Service]
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

Ví dụ này cho phép UNIX socket, IPv4 và IPv6 nhưng loại các address family khác như packet socket. Nếu service chỉ giao tiếp qua UNIX socket, có thể giữ mỗi AF_UNIX. Nếu bỏ AF_INET6, cần kiểm tra DNS result, bind dual-stack và kết nối tới dependency trả IPv6.

PrivateNetwork=yes tạo network namespace riêng và thường chỉ còn loopback nội bộ chưa được cấu hình. Nó rất mạnh nhưng không phù hợp cho daemon cần mạng nếu chưa thiết kế veth/socket activation tương ứng. Đừng bật chỉ để cải thiện score rồi phát hiện service không thể gọi database hoặc API.

7. System call filtering: thêm sau, không thêm đầu tiên

[Service]
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM

SystemCallFilter=@system-service là allowlist nhóm syscall thường cần cho system service. Đây là lớp mạnh nhưng dễ gây lỗi phụ thuộc runtime, thư viện, kiến trúc và phiên bản. Go, Java, Node.js, database, profiler hoặc thư viện mới có thể gọi syscall khác với test ban đầu.

Dùng SystemCallErrorNumber=EPERM trong giai đoạn thử giúp ứng dụng nhận lỗi thay vì bị terminate ngay, nhưng cũng có thể che khuất nơi policy bị chạm. Kiểm tra journal và workload dài hạn. Với ứng dụng phức tạp, bắt đầu bằng denylist nhóm rủi ro đã hiểu rõ hoặc trì hoãn lớp này cho đến khi filesystem, identity và capability đã ổn định.

MemoryDenyWriteExecute=yes giảm khả năng tạo memory vừa writable vừa executable, nhưng không tương thích với JIT và một số trampoline/runtime. Không bật cho PHP JIT, Java, JavaScript engine hoặc workload sinh mã động trước khi kiểm chứng.

8. Một drop-in mẫu cho HTTP daemon thông thường

[Service]
User=myapp
Group=myapp
UMask=0077

NoNewPrivileges=yes
RestrictSUIDSGID=yes
CapabilityBoundingSet=
AmbientCapabilities=

ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
StateDirectory=myapp
RuntimeDirectory=myapp

ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
LockPersonality=yes
RestrictRealtime=yes

RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallArchitectures=native

Đây là template để review, không phải cấu hình universal. Nếu binary ghi log riêng, thêm LogsDirectory=. Nếu bind port dưới 1024, dùng capability phù hợp hoặc socket activation. Nếu đọc certificate/key, cấp read access đúng path và giữ secret ngoài environment variable khi có thể.

9. Xác minh trước và sau khi restart

sudo systemd-analyze verify myapp.service
sudo systemctl daemon-reload
sudo systemctl restart myapp.service

systemctl status myapp.service --no-pager
journalctl -u myapp.service -b --since "10 minutes ago"
systemd-analyze security myapp.service

verify bắt lỗi cú pháp và một số dependency, nhưng không chứng minh workload hoạt động. Hậu kiểm phải gồm health endpoint, request đọc/ghi, upload, job nền, log rotation, restart, graceful shutdown, kết nối database/cache và các luồng ít dùng.

Đừng chỉ nhìn service ở trạng thái active. Một daemon có thể start thành công nhưng mất quyền ghi cache, tạo UNIX socket hoặc chạy tác vụ định kỳ sau nhiều giờ. Canary một host trước, quan sát đủ chu kỳ vận hành rồi mới rollout toàn fleet.

10. Đọc systemd-analyze security đúng cách

systemd-analyze security liệt kê các cơ chế protection và tạo exposure score để so sánh. Nó hữu ích cho inventory và phát hiện directive bỏ quên, nhưng:

  • Điểm số không xác nhận ứng dụng không có lỗ hổng.
  • Mọi directive không có trọng số đúng với threat model riêng của bạn.
  • Service quản trị host hợp lệ sẽ cần quyền rộng hơn.
  • Khác biệt giữa system service và user service ảnh hưởng khả năng dùng namespace.
  • Kernel, systemd và distro quyết định directive nào có hiệu lực.

Ghi lại lý do cho mỗi mục còn exposure thay vì ép score về 0. Một ngoại lệ có owner và test tốt an toàn hơn một directive bật lên nhưng chưa ai hiểu.

11. Rollback và vận hành cấu hình

Mỗi đợt chỉ nên thêm một nhóm directive. Lưu drop-in trong configuration management, review như code và gắn với phiên bản service. Nếu canary lỗi, rollback đúng drop-in thay vì sửa vendor unit hoặc nới toàn bộ sandbox.

  • Ghi snapshot systemctl cat và security report trước thay đổi.
  • Có health check ngoài process và SLO/cảnh báo nghiệp vụ.
  • Thử restart/reboot, không chỉ reload.
  • Kiểm tra bản nâng cấp ứng dụng vì path và syscall có thể đổi.
  • Rà soát exception định kỳ; xóa quyền không còn dùng.

12. Checklist hardening production

  • Service chạy bằng identity riêng và không cần root nếu có thể.
  • NoNewPrivileges, capability bounding set và umask đã được thiết kế theo nhu cầu.
  • Filesystem mặc định chỉ đọc; mọi path ghi đều hữu hạn và có owner đúng.
  • Home, temporary directory, device và kernel interface đã được hạn chế.
  • Address family bám theo giao thức thật; network namespace không làm mất dependency.
  • System call hoặc W^X restriction chỉ bật sau kiểm thử runtime/JIT.
  • Unit pass systemd-analyze verify, workload test và canary.
  • Exposure score được giải thích, không được dùng thay threat model.
  • Có rollback, monitoring và kiểm thử lại sau package upgrade.

Sandbox systemd tốt nhất là sandbox đủ chặt để giảm blast radius nhưng đủ rõ để vận hành lâu dài. Bắt đầu với identity, NoNewPrivileges, filesystem chỉ đọc và directory do systemd quản lý; sau đó mới thêm kernel, network và syscall restriction dựa trên bằng chứng từ workload.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.