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ý:
| Directive | Path thường tạo | Dùng cho |
|---|---|---|
StateDirectory=myapp | /var/lib/myapp | Dữ liệu bền vững |
LogsDirectory=myapp | /var/log/myapp | Log nếu không gửi journal |
RuntimeDirectory=myapp | /run/myapp | PID, socket, state tạm theo boot |
CacheDirectory=myapp | /var/cache/myapp | Dữ 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=yescấp một/devtối giản và loại quyền truy cập nhiều thiết bị vật lý.ProtectKernelTunables=yeschặn sửa các kernel tunable như phần thuộc/proc/sys.ProtectKernelModules=yeschặn explicit module load/unload và loại capability liên quan.ProtectControlGroups=yeslàm cgroup hierarchy chỉ đọc trong phạm vi service.ProtectKernelLogs=yeshạn chế đọc kernel log buffer.LockPersonality=yesngă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 catvà 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.




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