Cấu hình SELinux trên Rocky Linux: Đừng tắt đi khi chưa hiểu nó làm gì

CentOS tutorial - IT technology blog
CentOS tutorial - IT technology blog

Lúc 2 giờ sáng và cái bẫy setenforce 0

Hồi CentOS 8 EOL, mình phải migrate gấp 5 server sang Rocky Linux trong vòng 1 tuần. Đêm đầu tiên, Nginx không chịu bind vào port mới, PHP-FPM báo lỗi permission dù chown đã đúng hết. Tay run, mắt mờ, mình gõ setenforce 0 rồi mọi thứ chạy. Xong việc, đi ngủ.

Sáng hôm sau nhìn lại thì giật mình: server production đang chạy với SELinux tắt. Không phải một server — cả 5 server. Và mình biết nếu để vậy thì sau này khi audit bảo mật, cái đó sẽ là điểm trừ đầu tiên.

Từ đó mình tự đặt câu hỏi: SELinux block cái gì, tại sao, và fix thế nào mà không phải tắt hẳn? Bài này là kết quả — cách mình đã đưa cả 5 server về Enforcing mode mà không break gì thêm.

Ba cách tiếp cận SELinux — và tại sao 2 trong 3 không nên dùng trên production

Nhìn qua forum và StackOverflow, phần lớn sysadmin xử lý SELinux theo một trong 3 hướng:

Cách 1: Tắt SELinux hoàn toàn (SELINUX=disabled)

Cách này phổ biến trong tutorial cũ, đặc biệt các bài hướng dẫn cài LAMP stack từ 2018 trở về trước. Sửa /etc/selinux/config, reboot, xong.

# /etc/selinux/config
SELINUX=disabled

Ưu điểm duy nhất: mọi thứ chạy ngay, không cần debug. Nhược điểm thì đáng kể hơn — tắt hẳn một layer bảo mật ở tầng kernel, và khi bật lại sau này cần relabel toàn bộ filesystem: server 150GB của mình mất gần 18 phút chờ bước này.

Cách 2: Chế độ Permissive — ghi log nhưng không chặn

SELinux vẫn chạy, vẫn kiểm tra policy, nhưng thay vì chặn thì chỉ ghi vào audit log. Hiểu đơn giản: một kiểu dry run — phát hiện đủ vi phạm nhưng chưa làm gì cả.

setenforce 0        # Tạm thời (mất sau reboot)
getenforce          # Kiểm tra: sẽ ra "Permissive"

Để persistent qua reboot:

vi /etc/selinux/config
# SELINUX=permissive

Cách 3: Enforcing — bảo mật thực sự

Mode này mới là cái cần hướng tới. Vi phạm policy bị chặn ngay, không warning, không exception. Đây là default của Rocky Linux khi cài fresh.

setenforce 1
getenforce          # Output: Enforcing

Phân tích thực tế: Cái nào phù hợp với trường hợp nào?

Mode Bảo mật Debug dễ? Dùng khi nào
Disabled Không có Không cần Không bao giờ trên production
Permissive Không chặn Dễ nhất Dev/staging, hoặc khi đang debug
Enforcing Đầy đủ Cần biết audit log Production — mục tiêu cuối cùng

Nếu bạn đang vận hành server thật, Disabled là lựa chọn tệ nhất. Permissive ổn cho môi trường test, nhưng không phải giải pháp lâu dài. Enforcing mới là nơi cần đến.

Quyết định: Đi từ Permissive → Enforcing có kiểm soát

Cách tiếp cận mình dùng khi migrate 5 server đó là:

  1. Bật Permissive trước
  2. Chạy service bình thường vài ngày để SELinux log đủ denial
  3. Dùng audit2allow để tạo policy tùy chỉnh
  4. Load policy và chuyển sang Enforcing

Mất thêm vài ngày chờ log tích lũy, nhưng đổi lại thì không mò mẫm trong tối — biết chính xác service nào cần permission gì trước khi bật cứng.

Hướng dẫn triển khai thực tế

Bước 1: Kiểm tra trạng thái hiện tại

sestatus
# Output mẫu:
# SELinux status:                 enabled
# SELinuxfs mount:                /sys/fs/selinux
# SELinux mount point:            /sys/fs/selinux
# Loaded policy name:             targeted
# Current mode:                   permissive
# Mode from config file:          enforcing
# Policy MLS status:              enabled
# Policy deny_unknown status:     allowed
# Memory protection checking:     actual (secure)
# Max kernel policy version:      33

Chú ý phân biệt “Current mode” (runtime) và “Mode from config file” (sau reboot). Hai cái này có thể khác nhau — đặc biệt khi vừa dùng setenforce mà chưa sửa config file.

Bước 2: Đọc AVC denial log

Ở Permissive mode, mọi vi phạm đều được log vào /var/log/audit/audit.log:

# Xem denial gần nhất
ausearch -m avc -ts recent

# Hoặc dùng grep thô
grep "avc:  denied" /var/log/audit/audit.log | tail -20

Mẫu một AVC denial điển hình:

type=AVC msg=audit(1709712345.123:456): avc:  denied  { read } for  pid=12345 comm="nginx" \
  name="app.sock" dev="tmpfs" ino=67890 \
  scontext=system_u:system_r:httpd_t:s0 \
  tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=1

Đọc theo thứ tự: process nào (comm), đang làm gì (read), với file nào (name), bị từ chối vì type không match.

Bước 3: Phân tích với sealert (dễ đọc hơn)

Audit log đọc bằng mắt thường khá mệt. setroubleshoot-server parse ra thành ngôn ngữ người:

dnf install -y setroubleshoot-server

# Chạy trên audit log
sealert -a /var/log/audit/audit.log

sealert sẽ đưa ra gợi ý cụ thể — thường là lệnh setsebool hoặc semanage cần chạy, kèm giải thích lý do.

Bước 4: Tạo policy tùy chỉnh với audit2allow

Khi service có behavior đặc biệt — ví dụ Nginx serve file từ thư mục non-standard — cần tạo policy riêng:

# Tạo policy module từ audit log
ausearch -m avc -ts recent | audit2allow -M my_nginx_custom

# Load policy vừa tạo
semodule -i my_nginx_custom.pp

# Kiểm tra đã load chưa
semodule -l | grep my_nginx

File .te là text source, .pp là compiled policy. Giữ file .te lại — sau này nếu cần sửa policy thì edit file đó thay vì làm lại từ đầu.

Bước 5: Dùng boolean thay vì tạo policy mới

Nhiều trường hợp phổ biến đã có boolean sẵn, dùng boolean nhanh và gọn hơn:

# Cho phép httpd kết nối ra ngoài (gọi API, proxy)
setsebool -P httpd_can_network_connect 1

# Cho phép httpd đọc home directory
setsebool -P httpd_enable_homedirs 1

# Cho phép nginx/apache bind non-standard port
semanage port -a -t http_port_t -p tcp 8080

# Xem tất cả boolean liên quan httpd
getsebool -a | grep httpd

Bước 6: Relabel file context nếu cần

Copy file vào thư mục mới, hoặc tạo file ngoài path chuẩn, SELinux context thường bị sai. Fix như sau:

# Xem context hiện tại
ls -Z /var/www/html/

# Gán đúng context cho thư mục web
semanage fcontext -a -t httpd_sys_content_t "/data/web(/.*)?";
restorecon -Rv /data/web/

# Hoặc nếu chỉ sửa 1 file
chcon -t httpd_sys_content_t /data/web/index.php

Bước 7: Chuyển sang Enforcing

Log không còn denial mới sau vài ngày Permissive? Đó là lúc chuyển:

# Tạm thời (test ngay)
setenforce 1

# Theo dõi log 30 phút
tail -f /var/log/audit/audit.log | grep denied

# Nếu ổn, cập nhật config để persistent
vi /etc/selinux/config
# SELINUX=enforcing

Mình thường mở thêm một tab terminal chạy tail -f audit.log ngay sau khi chuyển Enforcing. Đợi ~30 phút, test hết chức năng chính của app. Không có denial mới là xong.

Quick reference: Các lệnh SELinux cần nhớ

# Trạng thái
sestatus
getenforce

# Đổi mode runtime
setenforce 0   # Permissive
setenforce 1   # Enforcing

# Xem context
ls -Z /path/to/file
ps auxZ | grep nginx

# Sửa context
restorecon -Rv /path/     # Restore về mặc định theo policy
chcon -t TYPE /file       # Sửa tạm thời

# Port
semanage port -l | grep http
semanage port -a -t http_port_t -p tcp 8443

# Log
ausearch -m avc -ts recent
sealert -a /var/log/audit/audit.log

# Policy
audit2allow -M mypolicy < /var/log/audit/audit.log
semodule -i mypolicy.pp
semodule -l

Kết

Cái bẫy của SELinux là dễ tắt, khó bật lại. Sau lần migrate đó, mình tự đặt ra quy tắc: server nào lên production mà không có SELinux Enforcing thì coi như chưa xong. Mất thêm 1-2 giờ debug ban đầu, nhưng đổi lại là một layer bảo mật thực sự — không phải security theater.

Rocky Linux giữ nguyên behavior SELinux của RHEL. Tất cả lệnh trên dùng được y chang trên AlmaLinux hay bất kỳ RHEL clone nào khác.

Share: