คำตอบสั้น ๆ: เข้าใจ Label ของ SELinux อ่าน AVC Denial และแก้ปัญหา Permission denied ด้วย restorecon, semanage และ setsebool โดยไม่ต้องปิด SELinux
คู่มือนี้อธิบาย AlmaLinux ทีละขั้นตอน พร้อมคำสั่งที่ใช้ได้จริงและปัญหาที่พบบ่อย
SELinux (Security-Enhanced Linux) เปิดใช้งานมาตั้งแต่ติดตั้งบน AlmaLinux และ Rocky Linux เป็นชั้นป้องกันที่ทำงานเพิ่มจากสิทธิ์ไฟล์แบบปกติ แม้โปรแกรมจะรันเป็นผู้ใช้ที่อ่านไฟล์ได้ตาม chmod แต่ถ้านโยบายของ SELinux ไม่อนุญาต การเข้าถึงนั้นก็จะถูกปฏิเสธ ประโยชน์ที่ชัดเจนคือ ถ้าเว็บไซต์ถูกเจาะ ผู้โจมตีที่ได้สิทธิ์ของ Nginx หรือ Apache จะยังถูกจำกัดให้อยู่ในขอบเขตที่เว็บเซิร์ฟเวอร์ควรทำได้เท่านั้น
คำแนะนำในอินเทอร์เน็ตจำนวนมากบอกให้ "ปิด SELinux" ทันทีที่เจอ Permission denied บทความนี้แสดงวิธีที่ถูกต้องกว่า คือหาว่า SELinux ปฏิเสธอะไร แล้วแก้เฉพาะจุดด้วยเครื่องมือมาตรฐานสามตัว ได้แก่ File Context, Boolean และ Port Label ซึ่งครอบคลุมปัญหาที่พบบนเว็บเซิร์ฟเวอร์เกือบทั้งหมด
สิ่งที่ต้องเตรียม (AlmaLinux)
- AlmaLinux หรือ Rocky Linux 8 หรือ 9 และผู้ใช้ที่มีสิทธิ์
sudo - ติดตั้งเครื่องมือจัดการ SELinux:
sudo dnf install -y policycoreutils-python-utils setroubleshoot-server
policycoreutils-python-utils ให้คำสั่ง semanage และ audit2why ส่วน setroubleshoot-server ให้คำสั่ง sealert ที่อธิบายสาเหตุเป็นภาษาคนอ่านเข้าใจ
ขั้นตอนที่ 1: ตรวจสถานะ SELinux (AlmaLinux)
getenforce
sestatus
| โหมด | ความหมาย |
|---|---|
Enforcing | บังคับใช้นโยบาย ปฏิเสธและบันทึก Log (ค่าที่ควรใช้) |
Permissive | ไม่ปฏิเสธ แต่บันทึก Log ว่าจะปฏิเสธอะไร ใช้ตรวจหาปัญหาชั่วคราว |
Disabled | ปิดทั้งหมด ไม่มีการบันทึก |
ขั้นตอนที่ 2: เข้าใจ Label (AlmaLinux)
SELinux ตัดสินจาก Label (Context) ที่ติดอยู่กับทุกไฟล์และทุกโปรเซส ดูได้ด้วยตัวเลือก -Z:
ls -Z /var/www/html
ps -eZ | grep -E "nginx|httpd"
ตัวอย่างผลลัพธ์:
unconfined_u:object_r:httpd_sys_content_t:s0 index.html
system_u:system_r:httpd_t:s0 1320 ? 00:00:00 nginx
ส่วนที่สำคัญคือส่วนที่สาม เรียกว่า Type โปรเซส Nginx และ Apache รันใน Type httpd_t และนโยบายอนุญาตให้ httpd_t อ่านไฟล์ที่เป็น httpd_sys_content_t ได้ ถ้าไฟล์เว็บมี Type อื่น เช่น user_home_t หรือ default_t เว็บเซิร์ฟเวอร์จะอ่านไม่ได้ แม้สิทธิ์ chmod จะเปิดเต็มที่ก็ตาม
Type ที่ใช้บ่อยกับเว็บ:
| Type | ใช้กับ |
|---|---|
httpd_sys_content_t | ไฟล์เว็บที่อ่านอย่างเดียว |
httpd_sys_rw_content_t | โฟลเดอร์ที่เว็บต้องเขียน เช่น wp-content/uploads, storage ของ Laravel |
httpd_log_t | ไฟล์ Log ของเว็บเซิร์ฟเวอร์ |
ขั้นตอนที่ 3: หาว่า SELinux ปฏิเสธอะไร (AlmaLinux)
เมื่อเว็บขึ้น 403 หรือ 502 และ Log ของแอปแจ้ง Permission denied ทั้งที่สิทธิ์ไฟล์ถูกต้อง ให้ดูบันทึกการปฏิเสธ (AVC Denial):
sudo ausearch -m AVC,USER_AVC -ts recent
ตัวอย่างบรรทัดที่พบ:
type=AVC msg=audit(...): avc: denied { read } for pid=1320 comm="nginx"
name="index.html" scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
อ่านได้ว่า โปรเซส nginx (Type httpd_t) ถูกปฏิเสธการ read ไฟล์ที่มี Type user_home_t ให้ส่งผลนี้เข้า audit2why หรือใช้ sealert เพื่อดูคำแนะนำ:
sudo ausearch -m AVC -ts recent | audit2why
sudo sealert -a /var/log/audit/audit.log
sealert มักบอกคำสั่งที่ควรใช้แก้มาให้ด้วย แต่ให้อ่านและเข้าใจก่อนทำตาม
ขั้นตอนที่ 4: แก้ปัญหาไฟล์ด้วย File Context
สาเหตุที่พบบ่อยที่สุดคือ ใช้ mv ย้ายไฟล์ จาก Home Directory ไปไว้ที่โฟลเดอร์เว็บ mv จะพา Label เดิม (user_home_t) ติดไปด้วย ส่วน cp จะให้ Label ตามโฟลเดอร์ปลายทาง วิธีแก้คือรีเซ็ต Label ให้ตรงกับนโยบาย:
sudo restorecon -Rv /var/www/example.com
ผลลัพธ์จะแสดงบรรทัด Relabeled ... from ... to ... สำหรับไฟล์ที่ถูกแก้
ถ้าเว็บอยู่นอกโฟลเดอร์มาตรฐาน เช่น /srv/www หรือ /data/sites ต้องเพิ่มกฎถาวรก่อน แล้วค่อย restorecon:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www
โฟลเดอร์ที่เว็บต้องเขียนได้ ให้กำหนดเฉพาะโฟลเดอร์นั้น ไม่ใช่ทั้งเว็บ:
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/srv/www/example.com/wp-content/uploads(/.*)?"
sudo restorecon -Rv /srv/www/example.com/wp-content/uploads
ดูกฎที่คุณเพิ่มไว้ทั้งหมดด้วย sudo semanage fcontext -l -C อย่าใช้ chcon เป็นวิธีถาวร เพราะค่าจาก chcon จะหายเมื่อมีการ Relabel ทั้งระบบ
ขั้นตอนที่ 5: เปิดความสามารถด้วย Boolean
Boolean คือสวิตช์เปิดปิดพฤติกรรมที่นโยบายเตรียมไว้ให้ ดูรายการของเว็บเซิร์ฟเวอร์ได้ด้วย:
getsebool -a | grep httpd
ที่ใช้บ่อย:
| Boolean | เปิดเมื่อ |
|---|---|
httpd_can_network_connect | เว็บต้องเชื่อมต่อออกไปยังเครือข่าย เช่น Nginx เป็น Reverse Proxy ให้แอป Node.js ที่พอร์ต 3000 หรือ PHP เรียก API ภายนอก |
httpd_can_network_connect_db | เว็บต้องต่อฐานข้อมูลผ่าน TCP (เช่น ฐานข้อมูลอยู่อีกเครื่อง) |
httpd_can_sendmail | PHP ต้องส่งอีเมลผ่าน sendmail ในเครื่อง |
httpd_enable_homedirs | ต้องให้บริการไฟล์จาก Home Directory ของผู้ใช้ |
เปิดแบบถาวรด้วยตัวเลือก -P (ไม่มี -P ค่าจะหายเมื่อรีบูต):
sudo setsebool -P httpd_can_network_connect on
อาการที่บ่งบอกว่าต้องเปิด Boolean ตัวแรก คือ Nginx แจ้ง 502 Bad Gateway และ Error Log มีข้อความ connect() to 127.0.0.1:3000 failed (13: Permission denied) ซึ่งพบบ่อยในการตั้ง Node.js กับ Nginx Reverse Proxy