SELinux và AppArmor: lớp phòng thủ bắt buộc

File đã chmod 777, chủ sở hữu đã đúng, vậy mà dịch vụ vẫn báo permission denied. Đến đây phần lớn người dùng sẽ tra Google và gặp lời khuyên tệ nhất trong quản trị Linux: tắt SELinux đi. Thứ đang chặn bạn là một lớp kiểm soát nằm trên cả quyền file thông thường, và nó tồn tại vì một lý do rất chính đáng.

DAC và MAC khác nhau ở đâu

Mô hình quyền quen thuộc — chủ sở hữu, nhóm, người khác, ba bit đọc ghi thực thi — gọi là DAC, kiểm soát truy cập tùy quyền. Đặc điểm của nó là chủ sở hữu file được toàn quyền quyết định ai đọc được file của mình, và root thì bỏ qua mọi kiểm tra.

MAC, kiểm soát truy cập bắt buộc, lật ngược điều đó: chính sách của hệ thống quyết định, người dùng không tự nới ra được, và ngay cả tiến trình chạy dưới root cũng bị ràng buộc. Hai lớp hoạt động nối tiếp nhau — DAC phải cho phép trước, rồi MAC mới xét tiếp. Chỉ cần một lớp từ chối là thao tác bị chặn. Đó chính là lý do chmod 777 không cứu được bạn.

Bản nào dùng cái nào

  • SELinux — mặc định trên RHEL, Rocky, AlmaLinux, Fedora. Hoạt động theo nhãn gắn vào từng file và từng tiến trình.
  • AppArmor — mặc định trên Ubuntu, Debian, openSUSE. Hoạt động theo đường dẫn, mỗi chương trình một hồ sơ.

SELinux bao phủ rộng hơn nhưng khái niệm nhiều hơn. AppArmor dễ đọc dễ sửa, đổi lại chỉ ràng buộc những chương trình đã có hồ sơ — phần còn lại chạy tự do.

SELinux: mọi thứ đều có nhãn

ls -Z /var/www/html
ps -eZ | grep nginx
id -Z

Nhãn có bốn phần user:role:type:level, ví dụ system_u:object_r:httpd_sys_content_t:s0. Trong thực tế bạn chỉ cần quan tâm phần type, cái đuôi _t. Chính sách nói đại ý: tiến trình mang type httpd_t được đọc file mang type httpd_sys_content_t. File đúng chỗ nhưng sai nhãn thì bị từ chối, bất kể quyền file là gì.

Từ đây suy ra một cái bẫy rất hay gặp:

cp /home/an/index.html /var/www/html/     # nhan moi theo thu muc dich - CHAY
mv /home/an/index.html /var/www/html/     # GIU nhan cu cua /home - BI CHAN

cp tạo file mới nên nhận nhãn mặc định của thư mục đích. mv chỉ đổi tên, nhãn cũ đi theo. Đây là nguyên nhân của vô số lỗi 403 xuất hiện ngay sau khi ai đó chuyển file vào thư mục web.

Ba chế độ và cách chuyển

getenforce                 # Enforcing / Permissive / Disabled
sestatus                   # chi tiet hon

sudo setenforce 0          # tam chuyen sang permissive
sudo setenforce 1          # tro lai enforcing

Permissive là công cụ chẩn đoán quan trọng nhất: không chặn gì nhưng vẫn ghi log đầy đủ mọi thứ đáng lẽ đã bị chặn. Chuyển sang permissive, tái hiện lỗi, thu hết danh sách từ chối trong một lần chạy, vá chính sách rồi bật lại enforcing.

setenforce chỉ có tác dụng tới lần khởi động sau. Chế độ lâu dài nằm ở /etc/selinux/config:

SELINUX=enforcing        # nen giu
# SELINUX=permissive     # tam thoi khi go roi
# SELINUX=disabled       # tranh - xem giai thich ben duoi

Đọc log từ chối của SELinux

sudo ausearch -m AVC,USER_AVC -ts recent
sudo ausearch -m AVC -ts today -i          # -i doi so hieu thanh ten
sudo grep AVC /var/log/audit/audit.log | tail

Mỗi bản ghi AVC cho biết tiến trình nào (scontext), đụng vào đối tượng nào (tcontext), thao tác gì (denied { read }). Ba mẩu thông tin đó đủ để biết phải sửa gì. Nếu máy có cài setroubleshoot, còn có bản diễn giải bằng câu chữ kèm gợi ý lệnh sửa:

sudo sealert -a /var/log/audit/audit.log
sudo journalctl -t setroubleshoot

Sửa đúng cách: nhãn, boolean, cổng

Ba loại vấn đề, ba cách sửa. Thứ nhất, sai nhãn file — khai báo luật rồi áp lại:

sudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
sudo restorecon -Rv /srv/web

Có lệnh chcon đổi nhãn trực tiếp, nhưng thay đổi đó sẽ mất khi hệ thống gán lại nhãn. Dùng chcon để thử nghiệm, dùng semanage fcontext khi đã chắc.

Thứ hai, hành vi bị chính sách khóa sẵn — mở bằng boolean. Chính sách có sẵn hàng trăm công tắc cho các tình huống thường gặp:

getsebool -a | grep httpd
sudo setsebool -P httpd_can_network_connect on

Cờ -P ghi vào chính sách nên còn hiệu lực sau khi khởi động lại. Thiếu nó, mọi thứ chạy tốt cho tới lần reboot kế tiếp — một kiểu lỗi rất mất thời gian truy.

Thứ ba, dịch vụ nghe ở cổng khác thường lệ:

sudo semanage port -l | grep http_port_t
sudo semanage port -a -t http_port_t -p tcp 8080

Lệnh semanage nằm trong gói policycoreutils-python-utils trên RHEL và Rocky; cài thêm nếu chưa có.

audit2allow: dùng được nhưng phải đọc

sudo ausearch -m AVC -ts recent | audit2allow -M ungdung_cuc_bo
cat ungdung_cuc_bo.te                       # DOC TRUOC KHI NAP
sudo semodule -i ungdung_cuc_bo.pp

Công cụ này sinh ra một module cho phép đúng những gì đã bị từ chối. Rất tiện, và cũng rất dễ lạm dụng: gom cả log của một lần chạy hỗn loạn thì nó có thể sinh ra luật mở toang những quyền chẳng liên quan gì tới lỗi ban đầu. Luôn mở file .te ra đọc trước khi nạp.

AppArmor: hồ sơ theo đường dẫn

sudo aa-status
ls /etc/apparmor.d/

Hồ sơ được đặt tên theo đường dẫn của chương trình với dấu gạch chéo đổi thành dấu chấm — /usr/sbin/nginx thành usr.sbin.nginx. Nội dung là danh sách đường dẫn kèm quyền, đọc thẳng được không cần công cụ dịch.

Mỗi hồ sơ ở một trong hai chế độ: enforce chặn thật, complain chỉ ghi log — tương đương permissive nhưng áp cho từng chương trình riêng lẻ:

sudo aa-complain /etc/apparmor.d/usr.sbin.nginx
# ... tai hien loi ...
sudo aa-logprof                        # doc log, de xuat sua ho so
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
sudo systemctl reload apparmor

Các lệnh aa-* nằm trong gói apparmor-utils. aa-logprof đáng giá nhất: nó đọc log từ chối rồi hỏi bạn từng mục nên cho phép hay không.

Đọc log từ chối của AppArmor

sudo journalctl -k | grep 'apparmor="DENIED"'
sudo grep 'apparmor="DENIED"' /var/log/syslog
sudo dmesg | grep -i apparmor

Dòng log nói thẳng tới mức không cần diễn giải: operation= là thao tác, profile= là hồ sơ đang chặn, name= là đường dẫn bị từ chối, requested_mask= là quyền chương trình xin. Nhìn bốn trường đó là biết phải thêm dòng nào vào hồ sơ.

Nếu buộc phải vô hiệu một hồ sơ, hãy tắt đúng hồ sơ đó thay vì tắt cả AppArmor:

sudo ln -s /etc/apparmor.d/usr.sbin.nginx /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.nginx

Vì sao đừng tắt hẳn khi gặp lỗi

  1. Nó là lớp phòng thủ cuối. Mọi lớp khác đều giả định ứng dụng còn nguyên vẹn. MAC là lớp duy nhất vẫn còn tác dụng sau khi ứng dụng đã bị chiếm quyền: kẻ tấn công chiếm được tiến trình web vẫn không đọc được /etc/shadow hay ghi vào thư mục hệ thống.
  2. Tắt thì dễ, bật lại thì đắt. Đặt SELinux về disabled khiến hệ thống ngừng cập nhật nhãn cho file mới. Muốn quay lại enforcing phải gán nhãn lại toàn bộ hệ thống file rồi khởi động lại:
    sudo touch /.autorelabel
    sudo reboot
  3. Đã có sẵn chế độ để gỡ rối. Permissive và complain cho bạn đúng thứ cần: hệ thống chạy được, log đầy đủ, chính sách còn nguyên để bật lại.
  4. Lỗi thường là do bạn. Tuyệt đại đa số trường hợp là file sai nhãn, cổng chưa khai báo, hoặc một boolean chưa bật — đều sửa trong vài phút khi đã đọc được log.

Lần tới khi một hướng dẫn cài đặt bảo bạn sửa /etc/selinux/config thành disabled, hãy coi đó là dấu hiệu tài liệu viết ẩu. Chạy getenforce trên máy chủ của bạn ngay bây giờ — nếu kết quả là Disabled, đó là một việc đáng đưa vào danh sách cần xử lý.

Lên đầu trang