Tăng cường bảo mật (Hardening) cho dịch vụ systemd
(roguesecurity.dev)- systemd cung cấp khả năng quản lý dịch vụ mạnh mẽ, nhưng cấu hình mặc định được tối ưu cho tính tiện dụng hơn là bảo mật, nên cần áp dụng thêm các tùy chọn hardening riêng
- Có thể dùng lệnh
systemd-analyze securityđể phân tích chỉ số mức độ phơi lộ bảo mật của toàn bộ dịch vụ hoặc một dịch vụ cụ thể, từ đó xác định thứ tự ưu tiên - Có nhiều tùy chọn bảo mật có thể áp dụng ở cấp độ đơn vị dịch vụ, và có thể chỉnh riêng qua
/etc/systemd/system/ServiceName.service.d/override.conf - Các tùy chọn chính gồm
ProtectSystem,PrivateTmp,NoNewPrivileges,SystemCallFilter,MemoryDenyWriteExecute... nhằm giới hạn quyền tiến trình và truy cập tài nguyên - Thay vì nhắm tới bảo mật tuyệt đối, nên ưu tiên hardening cho các dịch vụ công khai ra bên ngoài để giảm rủi ro; cách này cũng rất hiệu quả trong môi trường self-hosting
Tổng quan về systemd
- systemd cung cấp một phương thức quản lý dịch vụ rất hoàn thiện và vững chắc
- Tuy nhiên, vì ưu tiên khả năng dùng ngay hơn là bảo mật nên cấu hình mặc định khá nới lỏng
- Tài liệu này giới thiệu nhiều tùy chọn tăng cường bảo mật có thể áp dụng cho unit dịch vụ systemd và podman quadlet nhằm giảm khả năng bị xâm phạm, đồng thời thu hẹp phạm vi thiệt hại nếu sự cố xảy ra
- Đây không phải hướng dẫn áp dụng đồng loạt cho mọi dịch vụ; cần thử nghiệm riêng, kiểm tra log và điều chỉnh theo đặc tính cũng như yêu cầu chức năng của từng dịch vụ
- Trách nhiệm bảo mật hạ tầng hoàn toàn thuộc về người vận hành, và tài liệu này chỉ là công cụ tham khảo
Phân tích bảo mật systemd
- Có thể dùng lệnh
systemd-analyze securityđể kiểm tra trạng thái bảo mật của toàn bộ dịch vụ hoặc phân tích cấu hình chi tiết của một dịch vụ cụ thể (ví dụ:sshd.service)- Kết quả đầu ra gồm trạng thái kiểm tra, tên tính năng, mô tả và điểm Exposure; Exposure càng cao thì rủi ro càng lớn
- Có thể thêm cấu hình tùy chọn bảo mật vào phần
[Service](systemd) hoặc[Container](podman quadlet) - Khuyến nghị dùng
systemctl edit ServiceName.serviceđể tạo file override; nếu gặp lỗi, cần kiểm tra quyền cần thiết rồi điều chỉnh
Các tùy chọn bảo mật dịch vụ
- systemd cung cấp nhiều từ khóa tùy chọn bảo mật, có thể tra cứu qua
man systemd.exec 5,man capabilities 7... - Một số tùy chọn bảo mật tiêu biểu
ProtectSystem→ tùy chọn giới hạn hệ thống tệp ở chế độ chỉ đọcProtectHome→ tùy chọn chặn truy cập/home,/root,/run/userPrivateDevices→ tùy chọn chặn truy cập thiết bị vật lý, chỉ cho phép thiết bị ảo như/dev/nullProtectKernelTunables,ProtectKernelModules,ProtectKernelLogs→ tùy chọn chặn truy cập tài nguyên kernelNoNewPrivileges→ tùy chọn ngăn giành thêm đặc quyền mới qua setuid/setgid...MemoryDenyWriteExecute→ chặn việc dùng đồng thời bộ nhớ có thể ghi và thực thi; có thể gây vấn đề với một số ngôn ngữ JITSystemCallFilter→ tùy chọn giới hạn các system call được phép; khi vi phạm có thể kết thúc tiến trình hoặc trả về EPERM
Giải thích từng tùy chọn
- ProtectSystem: khi đặt
strict, toàn bộ hệ thống tệp được mount chỉ đọc;/dev,/proc,/syscần thêm tùy chọn bảo vệ riêng - ReadWritePaths: chỉ cho phép ghi lại trên một số đường dẫn nhất định
- ProtectHome: chặn truy cập
/home,/root,/run/user - PrivateDevices: vô hiệu hóa truy cập thiết bị vật lý, chỉ cho phép thiết bị pseudo như
/dev/null - ProtectKernelTunables: đặt
/proc,/sysở chế độ chỉ đọc - ProtectControlGroups: chỉ cho phép truy cập cgroup ở chế độ chỉ đọc
- ProtectKernelModules: cấm nạp kernel module một cách tường minh
- ProtectKernelLogs: giới hạn truy cập bộ đệm log kernel
- ProtectProc: khi đặt
invisible, sẽ ẩn các tiến trình thuộc người dùng khác khỏi/proc/ - ProcSubset: chặn nội dung trong
/procngoài các mục liên quan tới PID cụ thể - NoNewPrivileges: chặn leo thang đặc quyền mới qua setuid, setgid, hoặc capability của hệ thống tệp
- ProtectClock: chặn ghi vào đồng hồ hệ thống/phần cứng
- SystemCallArchitectures: khi đặt
native, chỉ cho phép syscall native như x86-64 - RestrictNamespaces: giới hạn namespace chuyên biệt cho container
- RestrictSUIDSGID: chặn thiết lập bit setuid, setgid cho tệp
- LockPersonality: ngăn thay đổi execution domain (chỉ cần cho một số ứng dụng cũ)
- RestrictRealtime: giới hạn lập lịch thời gian thực (chỉ cần cho một số dịch vụ mục đích đặc biệt)
- RestrictAddressFamilies: giới hạn các socket address family được phép (ví dụ: AF_INET, AF_INET6, AF_UNIX...)
- MemoryDenyWriteExecute: chặn việc tạo thêm vùng nhớ vừa ghi vừa thực thi (dịch vụ dùng JIT cần lưu ý)
- ProtectHostname: cấm sử dụng syscall sethostname, setdomainname
- SystemCallFilter: cấu hình cho phép/chặn syscall theo từng dịch vụ, có thể lọc rất chi tiết
- Có thể điều chỉnh theo nhóm, syscall riêng lẻ, cũng như cách thức allow/deny
- Khi vi phạm, cũng hỗ trợ cấu hình trả về mã lỗi như EPERM thay vì kết thúc tiến trình
- Có thể xem toàn bộ danh sách qua
systemd-analyze syscall-filterhoặcman systemd.exec(5) - Có thể phủ định toàn bộ danh sách bằng tiền tố
~(ví dụ:CapabilityBoundingSet=~CAP_SETUID...)
Quy trình điều chỉnh giới hạn SystemCallFilter
- Có thể dùng
auditdđể kiểm tra trong log syscall nào đã bị chặn khi dịch vụ thất bại- Chạy
sudo ausearch -i -m SECCOMP -ts recent, rồi kiểm tra giá trị syscall - Thêm syscall tương ứng hoặc nhóm liên quan vào
SystemCallFilterđể xử lý sự cố theo từng bước
- Chạy
Thứ tự ưu tiên áp dụng hardening và mẹo vận hành
- Không cần áp dụng tất cả cho mọi dịch vụ
- Mô hình đe dọa và quản trị rủi ro là cốt lõi; đặc biệt các dịch vụ phơi ra bên ngoài (httpd, nginx, ssh...) cần được cân nhắc bắt buộc
- Các lệnh tùy biến, timer unit (thay thế cron kiểu cũ) cũng rất hiệu quả nếu áp dụng sớm
- Dịch vụ càng ít phức tạp thì khả năng tinh chỉnh chi tiết càng cao
Checklist: tổ hợp tùy chọn bảo mật khuyến nghị (ưu tiên áp dụng ban đầu)
ProtectSystem=strictPrivateTmp=yesProtectHome=yeshoặcProtectHome=tmpfsProtectClock=yes,ProtectKernelLogs=yes,ProtectKernelModules=yesRestrictSUIDGUID=yesUMask=0077LockPersonality=yesRestrictRealtime=yesMemoryDenyWriteExecute=yesDynamicUser=yeshoặc chỉ địnhUserlà một người dùng cụ thể không phải root
- Các mục trên nhìn chung là tổ hợp có thể dùng cho dịch vụ mà hầu như không gây ảnh hưởng lớn
- Nếu muốn áp dụng thêm lọc syscall (
SystemCallFilter) thì cần kiểm thử chi tiết
Ví dụ cấu hình Traefik
- Đây là ví dụ chạy dịch vụ Traefik dạng container bằng systemd quadlet và áp dụng nhiều tùy chọn bảo mật
- Áp dụng
ProtectSystem=full,ProtectHome=yes,SystemCallFilter=@system-service @mount @privileged... - Loại bỏ một số quyền bằng
CapabilityBoundingSet=~CAP_SETUID CAP_SETPCAP - Áp dụng giới hạn truy cập mạng như
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
- Áp dụng
Kết luận
- Các tùy chọn tăng cường bảo mật của systemd là một phương tiện thực dụng đáng có trong hộp công cụ của quản trị viên hệ Unix
- Đây không phải biện pháp bảo mật hoàn hảo mà nên được dùng như công cụ giảm rủi ro; không cần áp dụng cấu hình bảo mật một cách bừa bãi cho mọi dịch vụ
- Đặc biệt với quản trị viên trong môi trường self-hosting, chúng có thể giúp nâng cao mức độ bảo mật rất đáng kể
- Hãy ưu tiên "tính thực dụng hơn sự hoàn hảo" và khuyến nghị áp dụng từng phần trong phạm vi phù hợp với công việc và môi trường
1 bình luận
Ý kiến trên Hacker News
Tôi thấy khá thú vị ở chỗ có thể triển khai hardening dịch vụ systemd tự động thông qua profiling bằng strace
https://github.com/desbma/shh
Tôi có một cách hay tìm ra được: trong ví dụ không dùng ProtectSystem=, nhưng nếu làm như
TemporaryFileSystem=/:ro, BindReadOnly=/usr/bin/binary /lib /lib64 /usr/lib usr/lib64 thì
có thể chỉ bao gồm binary mong muốn và các đường dẫn cần quyền đọc
Hiện tại ProtectSystem= không tương thích với cách làm này
Xem thêm chi tiết tại đây
Tôi nghĩ cách này có thể gây vấn đề với các dịch vụ cần hành vi bổ sung như gửi email khi phát sinh lỗi
So với bài về hardening systemd đăng hôm qua thì bài này thực tế hơn nhiều và có rất nhiều mẹo hay có thể áp dụng ngay
Hôm qua tôi đã cố đưa ra ví dụ thực tiễn hơn trong phần bình luận bài đó, còn bài hôm nay tổng hợp nội dung thực dụng rất tốt và chỉ ra các cách tăng cường cô lập cùng bảo mật bằng systemd một cách nhanh chóng, dễ dàng
Tôi nghĩ đây là một bài viết xuất sắc
Để tham khảo, xin để lại bài hôm qua
https://us.jlcarveth.dev/post/hardening-systemd.md
https://news.ycombinator.com/item?id=44928504
Trên một số trình duyệt, lỗi chứng chỉ khiến không thể truy cập được luôn
Cảm ơn vì đã chia sẻ
Nếu dùng systemd-analyze kèm cờ --user thì có thể kiểm tra bảo mật của các user unit của systemd ("systemd-analyze --user security")
Từ khi chuyển container sang Podman, tôi bắt đầu dùng systemd nhiều hơn, và công cụ này sẽ rất hữu ích để cải thiện bảo mật cho các dịch vụ unit/container của systemd
Các script init ngày xưa đều mỗi nơi một kiểu nên không thể thực hiện hardening nhất quán như thế này
Tôi đến với Linux khá muộn nên khó mà tưởng tượng một hệ thống không có systemd, và hệ thống không có systemd thực sự rất bất tiện để vận hành
Gần đây tôi phát hiện ra công cụ "unshare", nhờ đó có thể thử nghiệm những việc như remount toàn bộ /nix thành RW mà không ảnh hưởng tới tiến trình khác
Dù khả năng sử dụng của systemd có phần hơi thô, thành thật mà nói với tôi lựa chọn thay thế chỉ có Windows
Tôi tự hỏi vì sao các bản phân phối Linux không bật mặc định nhiều công tắc bảo mật kiểu này hơn
Tôi nghĩ liệu có nhược điểm nào khi hardening theo hướng bảo thủ không
Với nhiều người dùng, số lượng thiết lập có thể quá nhiều và quá phức tạp
Nếu thay đổi thiết lập quá mạnh tay thì có thể vô tình làm hỏng cấu hình hiện có
Ví dụ nếu hardening NetworkManager thì phải xác minh từng thứ như IPv4 và IPv6 đều kết nối được, các chế độ dns=systemd-resolved và dns=default hoạt động bình thường, tích hợp ModemManager và mạng di động, plugin openvpn hay cisco anyconnect, các hook của NetworkManager-dispatcher, v.v.
Ngoài ra còn vấn đề là có bao nhiêu người quản lý bản phân phối thực sự dám chắc có thể thay đổi công tắc của gói mình quản lý đến mức nào mà không làm hỏng hơn 0.01% môi trường người dùng
Nếu các cờ này do bản phân phối quản lý thì mỗi lần upstream phát hành lại kéo theo vấn đề tương thích, còn nếu upstream cấu hình thì vì tương thích ngược nên họ buộc phải thận trọng hơn nhiều
Câu hỏi này khá giống với "vì sao các bản phân phối không mặc định dùng MAC (SELinux v.v.) một cách chặt chẽ hơn?"
Những thứ như sshd đúng là sẽ tốt hơn nếu bị hạn chế nhiều hơn
Vì những lý do đó, đây là gánh nặng lớn với các bản phân phối lớn
Tương tự, với SELinux và AppArmor, nhiều maintainer cũng cho rằng hiệu quả không tương xứng với công sức đầu tư
Một lý do lớn nữa là họ không có đủ năng lực hay tài nguyên để tích hợp test riêng từng tham số nhằm xác minh các dịch vụ hệ thống cốt lõi vẫn hoạt động bình thường
Thảo luận liên quan
https://news.ycombinator.com/item?id=29995566
Kết quả của systemd-analyze security cũng khác nhau giữa các bản phân phối
desbma/shh tự động sinh rule theo từng nhóm như SyscallFilter dựa trên nội dung thu thập bằng strace, khá giống audit2allow của SELinux
Tuy vậy, việc cài strace vào môi trường production có thể gây tranh cãi
https://github.com/desbma/shh
Tôi cũng không rõ lắm, nhưng có thể một số thiết lập là mới được thêm vào nên nhiều người dùng chưa biết
Đâu phải ai cũng là cao thủ systemd, và nếu bật các thiết lập đó thì có nguy cơ không hoạt động bình thường trên các phiên bản systemd cũ hơn
Có nhiều tính năng như SELinux, AppArmor, nhưng nhiều bản phân phối, nhà phát triển và người dùng không cảm thấy thực sự cần thiết, nên khó tự động áp dụng rộng rãi
Có quá nhiều tùy chọn để hardening bảo mật, nên tôi nghĩ sẽ rất hay nếu có một kho lưu các ví dụ hardening phổ biến theo từng loại dịch vụ
Người dùng thường tận dụng các script hardening được áp dụng rộng rãi, nhưng rồi lại phát hiện rằng muốn tránh các tình huống ngoại lệ thì đôi khi phải cấp quyền rộng hơn tưởng tượng
thì cách hữu ích nhất là tham khảo cách các bản phân phối mainstream đóng gói và hardening
Những cách hardening đó thường đã được kiểm thử khá đầy đủ, nên nếu muốn xem ví dụ hardening cho postgresql hay tương tự thì nên bắt đầu từ các gói của Debian, Ubuntu, RHEL
Một trong những tính năng bảo mật tuyệt vời mà systemd cung cấp là quản lý credentials
Nhờ đó có thể truyền thông tin xác thực cho ứng dụng an toàn hơn so với việc lưu trong biến môi trường hoặc trên hệ thống tệp
Trong những môi trường không có Vault, chẳng hạn dự án cá nhân, tôi luôn ưu tiên cách này
Tôi cũng tự viết một package Go tích hợp với tính năng đó
credentials trong systemd
package credential-go
Nghe giống văn hóa của nodejs hay npm, nơi đoạn code 2 dòng cũng được đóng thành package
Thực tế chỉ là
còn chẳng phức tạp bằng left-pad
Tôi cứ nghĩ trong cộng đồng Go, việc giảm dependency và tránh abstraction không cần thiết (như gọi thêm hàm) vốn được xem là điều tốt
Những thao tác đơn giản kiểu này trước đây ai cũng thường tự viết ngay tại chỗ
Tôi tò mò không biết cơ chế truyền credential này ngăn việc tiến trình con được fork kế thừa thông tin xác thực bằng cách nào
Đây thực sự là một bài rất hữu ích
Tôi cũng thích danh sách từng tùy chọn của systemd và lời khuyên kiểu "hãy đọc man rồi chúc may mắn"
systemd thực sự rất tuyệt và tôi muốn triển khai nó tích cực trên các máy chủ của mình
Một mẹo nhỏ thôi: cách viết đúng trong tiêu đề là systemd
Không phải SystemD, system D hay system d, mà là systemd
Lý do là vì nó là system daemon, nên theo truyền thống Unix/Linux thì tên kết thúc bằng chữ d thường viết thường
Tôi thường thấy nhiều người viết là systemD, nên khá tò mò tại sao cách đó lại phổ biến đến vậy
Mẹo debug các vấn đề syscall trong systemd thực sự rất hữu ích