SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-09-05

SELinux ทำให้ nginx 403 ทั้งที่ permission ถูกต้อง

nginx ส่ง 403 ทั้งที่ permission ถูกต้องใช่หรือไม่ ตรวจ SELinux denial และแก้ label ด้วย semanage กับ restorecon โดยคงโหมด enforcing ไว้

เหตุใด nginx จึงส่ง 403 เมื่อไฟล์มี permission ถูกต้อง

หาก nginx ส่ง 403 ทั้งที่ permission bits ของไฟล์ถูกต้อง สาเหตุเกือบทุกครั้งคือ SELinux (security-enhanced Linux) ปฏิเสธการอ่าน SELinux ตรวจสอบกฎอีกชุดหนึ่งหลังจากผ่านการตรวจสอบ permission ปกติแล้ว และ web server จะอ่านได้เฉพาะไฟล์ที่มี label สำหรับ web content เท่านั้น ไฟล์ของคุณมี label อื่น จึงเปิดไฟล์ไม่สำเร็จและ nginx ไม่มีข้อมูลที่จะส่ง

ให้ดู label ไม่ใช่ดูเฉพาะ mode:

ls -ldZ /data/www /data/www/index.html
drwxr-xr-x. root root unconfined_u:object_r:default_t:s0 /data/www
-rw-r--r--. root root unconfined_u:object_r:default_t:s0 /data/www/index.html

จุดที่แสดงหลัง drwxr-xr-x หมายความว่าไฟล์มี SELinux label default_t คือค่าที่ path ได้รับเมื่อ policy ไม่เคยรู้จัก path นั้น และไม่มีกฎของ web server ที่อนุญาตให้อ่าน type ดังกล่าว error log แสดงข้อผิดพลาดแบบ Unix ทั่วไป จึงทำให้ปัญหานี้ดูเหมือนเป็นปัญหา permission:

2026/08/11 09:14:02 [error] 1183#1183: *1 open() "/data/www/index.html" failed (13: Permission denied), client: 203.0.113.9, server: _, request: "GET / HTTP/1.1"

kernel จะคืนค่า 13: Permission denied สำหรับการปฏิเสธทั้ง 2 แบบ คือแบบปกติและแบบที่เกิดจาก SELinux ดังนั้นขั้นตอนแรกคือต้องตรวจสอบว่า layer ใดเป็นผู้ปฏิเสธ อย่าเริ่มจาก setenforce 0

ส่วนของโมเดลที่คุณต้องใช้

SELinux คือการควบคุมการเข้าถึงแบบบังคับ ซึ่งโดยทั่วไปเขียนย่อว่า MAC ทุก process ทำงานอยู่ภายใน domain เช่น httpd_t สำหรับ web server ไฟล์ทุกไฟล์และ network port ทุกพอร์ตมี type กำกับ เช่น httpd_sys_content_t policy คือรายการชุดค่าผสมของ domain, type และ action ที่อนุญาต และทุกอย่างที่ไม่อยู่ในรายการนี้จะถูกปฏิเสธ SELinux ทำงานหลังจากการตรวจสอบแบบ Unix ดั้งเดิม ดังนั้น permission bits ใน drwxr-xr-x ก็ยังต้องอนุญาตการเข้าถึงก่อน ทั้งสองชั้นต้องอนุญาตจึงจะเข้าถึงได้

context แบบเต็มมี 4 ฟิลด์คั่นด้วยเครื่องหมายโคลอน เช่น system_u:system_r:httpd_t:s0 ได้แก่ SELinux user, role, type และ level สำหรับ server คุณจะใช้งานเกือบทั้งหมดอยู่กับฟิลด์ที่ 3 คือ type ใช้ 2 คำสั่งต่อไปนี้เพื่อแสดงค่าที่ใช้งานอยู่:

ps -eZ | grep nginx
id -Z

worker ของ nginx จะแสดง context ที่ลงท้ายด้วย httpd_t ส่วน login shell ของคุณจะแสดง unconfined_u:unconfined_r:unconfined_t:s0 เนื่องจาก policy เริ่มต้นของ targeted จะจำกัดขอบเขตของ service และไม่จำกัดผู้ใช้แบบโต้ตอบ เรื่องนี้สำคัญ เพราะ SELinux ไม่ได้เข้ามาแทนที่ การเรียกใช้ service ด้วย user ที่มีสิทธิ์เท่าที่จำเป็น แต่จะจำกัดสิ่งที่ service เข้าถึงได้หลังจากมีผู้บุกรุกเข้าไปใน service นั้นแล้ว

โหมดทั้ง 3 แบบ และอิมเมจใดมี SELinux

sestatus
getenforce

Enforcing จะบล็อกและบันทึกเหตุการณ์ Permissive จะอนุญาตทุกอย่างและบันทึกสิ่งที่ระบบควรจะบล็อก Disabled จะไม่โหลด policy ใดเลย getenforce แสดงโหมดปัจจุบัน sestatus แสดงโหมดจาก /etc/selinux/config ด้วย ซึ่งเป็นโหมดที่จะกลับมาใช้หลัง reboot

Rocky Linux, AlmaLinux, Fedora และ RHEL มาพร้อม SELinux ในโหมด enforcing และใช้ policy targeted ค่าเริ่มต้นเดียวกันนี้สืบทอดมาจากต้นกำเนิดร่วมกัน ไม่ใช่เรื่องบังเอิญ เพราะทั้ง 4 ระบบพัฒนามาจาก สายการพัฒนาเดียวกันของ Red Hat ซึ่งมี CentOS อยู่ก่อน Rocky Linux และ AlmaLinux ระบบใดใน 2 ระบบที่เลือกใช้ไม่มีผลต่อเนื้อหาในหน้านี้ เพราะทั้งคู่มาพร้อม policy และเครื่องมือชุดเดียวกัน ดังนั้น การเลือกระหว่าง Rocky Linux กับ AlmaLinux จึงขึ้นอยู่กับคำรับรองด้าน compatibility และการรองรับ CPU รุ่นเก่า มากกว่าค่าเริ่มต้นด้านความปลอดภัย Ubuntu และ Debian มาพร้อม AppArmor แทน ซึ่งทำหน้าที่เดียวกันด้วยกลไกที่แตกต่างกัน (ส่วนสุดท้ายจะอธิบายเรื่องนี้) ดังนั้นแอปพลิเคชันเดียวกันอาจติดตั้งได้ตามปกติบนเซิร์ฟเวอร์เครื่องหนึ่ง แต่ส่งคืน 403 บนอีกเครื่องหนึ่ง

ติดตั้งเครื่องมือก่อนที่จะต้องใช้งาน

sudo dnf install -y policycoreutils-python-utils setroubleshoot-server

บน image แบบ minimal หาก semanage: command not found หมายความว่าไม่มี policycoreutils-python-utils: package ดังกล่าวมี semanage และ audit2allow อยู่ ส่วน setroubleshoot-server จะเพิ่ม sealert และเขียนสรุปสาเหตุการปฏิเสธแต่ละรายการเป็นภาษาที่เข้าใจง่ายลงใน journal ให้ติดตั้งทั้งสองรายการบนเซิร์ฟเวอร์ใหม่ เพราะเมื่อถึงเวลาที่ต้องใช้เครื่องมือเหล่านี้ มักเป็นช่วงที่ระบบมีบางอย่างขัดข้องอยู่แล้ว

วิธีอ่านการปฏิเสธของ SELinux ใน audit log

การปฏิเสธแต่ละครั้งจะถูกบันทึกโดย audit daemon เป็นข้อความ AVC (access vector cache):

sudo ausearch -m AVC,USER_AVC,SELINUX_ERR -ts recent
type=AVC msg=audit(1754896442.881:412): avc:  denied  { read } for  pid=1183 comm="nginx" name="index.html" dev="vda1" ino=17203 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0

ฟิลด์ 4 รายการนี้อธิบายเหตุการณ์ทั้งหมด comm คือโปรแกรมที่ถูกบล็อก scontext คือ source context หรือ domain ที่ process กำลังทำงานอยู่ tcontext คือ target context หรือ label ของสิ่งที่ process พยายามเข้าถึง tclass คือชนิดของ object ซึ่งในกรณีนี้คือไฟล์ เมื่ออ่านรวมกัน หมายความว่า process ใน httpd_t พยายามอ่านไฟล์ที่มี label เป็น default_t และ permissive=0 ระบุว่าคำขอนั้นถูกบล็อกจริง ไม่ใช่เพียงถูกบันทึกไว้เท่านั้น

หาก ausearch ไม่แสดงผลใด ๆ audit daemon อาจไม่ได้ทำงาน การปฏิเสธจะถูกส่งไปยัง kernel ring buffer แทน:

sudo journalctl -k | grep -i avc

จากนั้นเปลี่ยน record ให้เป็นประโยคภาษาอังกฤษ:

sudo ausearch -m AVC -ts recent | audit2why
sudo journalctl -t setroubleshoot --since today
sudo sealert -a /var/log/audit/audit.log

audit2why อ่าน record เดียวกันและระบุสาเหตุที่ตรวจพบ เช่น boolean ที่ปิดอยู่, label ที่ไม่ตรงกับ policy หรือไม่มี rule รองรับเลย sealert ตรวจสอบ log ทั้งหมดและพิมพ์คำสั่งที่แนะนำสำหรับการปฏิเสธแต่ละรายการ ให้ถือว่าคำแนะนำเป็นเพียงแนวทาง ข้อความจะแตกต่างกันระหว่างแต่ละ release และบางครั้ง sealert เสนอให้สร้าง custom policy module ทั้งที่การแก้ label เพียงบรรทัดเดียวเป็นวิธีที่ถูกต้อง

มีอีกประเด็นที่ควรทราบ policy มี rule dontaudit ที่ซ่อนการปฏิเสธซึ่งถือว่าไม่มีอันตราย ดังนั้นโปรแกรมอาจทำงานผิดปกติขณะที่ log ยังคงว่างอยู่ ให้แสดงการปฏิเสธเหล่านี้ตลอดการทดสอบ 1 ครั้ง:

sudo semodule -DB
# reproduce the problem, then read the log again
sudo semodule -B

แก้ไข path ที่ติดป้ายกำกับผิดด้วย semanage fcontext และ restorecon

ใช้ 2 คำสั่ง และลำดับมีความสำคัญ semanage fcontext -a บันทึกว่าป้ายกำกับของ path ควรเป็นอะไร restorecon ใช้ค่าเริ่มต้นที่บันทึกไว้นั้นกับไฟล์บนดิสก์

sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?"
sudo restorecon -Rv /data/www
ls -ldZ /data/www/index.html

path นี้เป็น regular expression (/.*)? ครอบคลุมทั้ง directory และทุกอย่างภายใน ซึ่งเป็นสิ่งที่ document root ต้องการ ตรวจสอบก่อนว่าการเปลี่ยนแปลงจะมีอะไรบ้าง: sudo restorecon -Rvn /data/www แสดงรายการ relabel ที่วางแผนไว้ เนื่องจาก -n หมายถึงไม่ดำเนินการใด ๆ หลังจากใช้ restorecon จริงแล้ว ป้ายกำกับจะแสดงเป็น httpd_sys_content_t และข้อผิดพลาด 403 จะหายไปโดยไม่ต้อง restart service

ใช้ chcon เฉพาะสำหรับการทดสอบ chcon -t httpd_sys_content_t index.html กำหนดป้ายกำกับโดยตรง และ restorecon, การอัปเดต package ครั้งถัดไป หรือการ relabel ทั้งระบบจะรีเซ็ตค่า เพราะ policy ยังคงระบุว่า path นี้ควรเป็นอย่างอื่น บนเครื่องที่ dnf-automatic ใช้ตัวตั้งเวลาเพื่อใช้ security update การรีเซ็ตนั้นจะเกิดขึ้นตามกำหนดเวลา แทนที่จะเกิดขึ้นขณะที่คุณกำลังนั่งอยู่หน้าเครื่อง ดังนั้นเว็บไซต์อาจใช้งานไม่ได้หลายชั่วโมงหลังจากการเปลี่ยนแปลงล่าสุดที่คุณทำ semanage fcontext เป็นเวอร์ชันที่คงอยู่ถาวร แสดงรายการค่าที่บันทึกไว้ด้วย sudo semanage fcontext -l | grep '^/data'

เนื้อหาที่ service ต้องเขียนลงไปต้องใช้ type อื่น ใช้ httpd_sys_rw_content_t สำหรับ directory ที่รับไฟล์อัปโหลดหรือ cache และจำกัด type นี้ไว้เฉพาะ path ดังกล่าว: หากเว็บไซต์ที่ควรอ่านอย่างเดียวอยู่ภายใต้ writable type จะทำให้แอปพลิเคชันเข้าถึงได้มากเกินความจำเป็น

เหตุใดป้ายกำกับจึงผิดตั้งแต่แรก? เกือบทุกครั้งเกิดจากวิธีที่ไฟล์ถูกนำเข้ามา mv จะคงป้ายกำกับเดิมของไฟล์ไว้ ดังนั้นเว็บไซต์ที่ย้ายออกจาก /root จะมาพร้อมป้ายกำกับ admin_home_t และคงป้ายกำกับนั้นไว้ การใช้ cp แบบปกติจะกำหนดป้ายกำกับเริ่มต้นของ directory ปลายทางให้กับไฟล์ใหม่ ซึ่งโดยทั่วไปเป็นสิ่งที่ต้องการ ส่วน cp -a และ rsync -X จะคัดลอกป้ายกำกับจาก source มาพร้อมกับไฟล์ การใช้ git clone ไปยัง directory ระดับบนสุดแห่งใหม่จะสร้าง default_t เมื่อหน้าเว็บโหลดได้ตามปกติจาก /usr/share/nginx/html แต่โหลดไม่ได้จาก directory ของคุณเอง นี่คือสาเหตุ

แก้พฤติกรรมบางประเภทด้วย boolean

ความล้มเหลวบางอย่างไม่ได้เกิดจากปัญหาของ label reverse proxy บนเครื่อง Rocky หรือ AlmaLinux ที่เพิ่งติดตั้งอาจส่งคืน 502 และ error log ระบุว่า:

2026/08/11 10:02:55 [crit] 1183#1183: *3 connect() to 127.0.0.1:3000 failed (13: Permission denied) while connecting to upstream

upstream ของคุณทำงานปกติ แต่ domain httpd_t ไม่ได้รับอนุญาตให้เปิดการเชื่อมต่อเครือข่ายขาออกตามค่าเริ่มต้น ดังนั้นการเรียก connect() จึงถูกปฏิเสธก่อนถึง loopback interface สวิตช์ตัวเดียวควบคุมพฤติกรรมทั้งหมดนี้:

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

-P คือ flag ที่สำคัญ เพราะจะเขียนค่าเก็บไว้ใน disk หากไม่มี -P การเปลี่ยนแปลงจะหายไปเมื่อ reboot ครั้งถัดไป ทำให้ service ทำงานได้จนกว่าเครื่องจะ restart ให้ยืนยันด้วย semanage boolean -l | grep httpd_can_network_connect ซึ่งจะแสดงค่าที่กำลังทำงานอยู่ถัดจากค่าที่เก็บไว้

ควรใช้ boolean แทนการเขียน rule เองเมื่อมีตัวเลือกนี้อยู่แล้ว Booleans มากับ distribution policy จึงได้รับการดูแล มีเอกสารอธิบาย และค้นหาได้ง่ายสำหรับผู้ดูแลคนถัดไป getsebool -a จะแสดง boolean ทั้งหมดในระบบ

ให้ service listen บนพอร์ตที่ไม่ใช่ค่ามาตรฐาน

พอร์ตมี label กำกับเช่นกัน เมื่อย้าย nginx ไปยัง 8081 แล้ว service จะเริ่มทำงานไม่ได้:

nginx: [emerg] bind() to 0.0.0.0:8081 failed (13: Permission denied)

httpd_t อาจ bind พอร์ตที่มี label เป็น http_port_t ได้ และ 8081 ไม่ได้อยู่ในรายการดังกล่าว ให้เพิ่มพอร์ตนี้:

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

ตรวจสอบรายการก่อน พอร์ตที่มีหมายเลขสูงหลายรายการได้รับอนุญาตอยู่แล้ว รวมถึง 8008 และ 8443 และการเพิ่มรายการเดิมซ้ำจะล้มเหลวด้วย ValueError: Port tcp/8081 already defined หากพอร์ตนั้นมี type อื่นใช้งานอยู่แล้ว ให้เปลี่ยนด้วย semanage port -m -t http_port_t -p tcp 8081 แทนการเพิ่มรายการใหม่

คำสั่งเดียวกันนี้ใช้ทำให้พอร์ต SSH ที่ย้ายไปแล้วใช้งานได้ Bind to port 2222 on 0.0.0.0 failed: Permission denied ใน journalctl -u sshd หมายความว่า 2222 ไม่มีอยู่ใน ssh_port_t ดังนั้นให้เรียกใช้ sudo semanage port -a -t ssh_port_t -p tcp 2222 ก่อน restart daemon และปิด session ของคุณ นี่เป็นขั้นตอนที่มักถูกข้ามเมื่อทำตามคู่มือทั่วไปเรื่อง การทำให้ SSH บน VPS มีความปลอดภัยมากขึ้น บน image ตระกูล Red Hat SELinux ไม่ใช่ firewall ดังนั้นพอร์ตยังต้องเปิดอยู่ด้วย: ใช้ sudo firewall-cmd --permanent --add-port=8081/tcp && sudo firewall-cmd --reload ในกรณีนี้ หรือใช้ ufw บน image Debian หรือ Ubuntu flag --permanent นั้นมีข้อควรระวังเรื่อง reboot เช่นเดียวกับ -P เมื่อใช้กับ boolean และควรอ่านเรื่อง zones ซึ่งกำหนดว่า rule จะมีผลกับ interface ใดสักครั้งใน พื้นฐาน firewalld สำหรับ Rocky หรือ AlmaLinux VPS

เมื่อไม่มี boolean และไม่มี label ให้เปลี่ยน

กรณีนี้พบไม่บ่อยบนเซิร์ฟเวอร์ทั่วไป และเป็นจุดที่ผู้ดูแลอาจทำให้ระบบเสียหายได้ audit2allow สามารถสร้าง policy module จากรายการ denials ใน log ได้ดังนี้:

sudo ausearch -m AVC -ts recent -c nginx | audit2allow -M nginx_local
cat nginx_local.te
sudo semodule -i nginx_local.pp

อ่าน nginx_local.te ก่อนติดตั้ง ใช้แนวทาง 2 ข้อเพื่อให้การดำเนินการนี้ปลอดภัย กรอง input ให้เหลือเฉพาะโปรแกรมที่กำลังแก้ไขด้วย -c เพราะการส่งรายการ denials ที่ไม่เกี่ยวข้องตลอด 1 สัปดาห์เข้า audit2allow จะทำให้กฎทั้งหมดได้รับอนุญาตพร้อมกัน และอย่าติดตั้ง module ที่สร้างจาก denial ซึ่งคุณอธิบายไม่ได้ กฎที่อนุญาตให้ httpd_t อ่านไฟล์ทุกไฟล์บนระบบนั้นสร้างได้ง่าย แต่ตรวจพบได้ยากเมื่อเวลาผ่านไปหลายเดือน ลบ module ด้วย sudo semodule -r nginx_local

Permissive เป็นโหมดวินิจฉัย ไม่ใช่การแก้ไข

sudo setenforce 0
# reproduce the problem once, all the way through
sudo ausearch -m AVC -ts recent
sudo setenforce 1

โหมด Permissive อนุญาตการเข้าถึงและบันทึกเหตุการณ์นั้นไว้ใน log คุณค่าที่แท้จริงของโหมดนี้คือการเก็บข้อมูลได้ครบถ้วน เมื่อใช้โหมด enforcing service จะหยุดที่การปฏิเสธครั้งแรก คุณจึงแก้ไขปัญหานั้นแล้วเริ่ม service ใหม่ จากนั้นจึงพบการปฏิเสธครั้งที่สอง แต่ในโหมด Permissive การทำงานจะดำเนินต่อไป และ log จะรวบรวมการปฏิเสธทั้งหมดได้ภายในการทำงานครั้งเดียว จากนั้นคุณจึงสลับกลับและแก้ไขทั้งหมดพร้อมกัน

setenforce ไม่มีผลกับ /etc/selinux/config ดังนั้นเมื่อ reboot ระบบจะกลับไปใช้โหมด enforcing นี่คือกลไกความปลอดภัย และเป็นเหตุผลที่ "การแก้ไข" ซึ่งมีเพียงการใช้ setenforce 0 จะกลับมาแสดงผลในช่วงเวลาที่เลวร้ายที่สุด หากมี service หนึ่งต้องผ่อนปรนข้อจำกัดระหว่างที่คุณแก้ไข ให้กำหนดเฉพาะ domain นั้นแทนการเปลี่ยนทั้งเครื่อง: sudo semanage permissive -a httpd_t จะทำให้ส่วนอื่นยังคงใช้โหมด enforcing และ sudo semanage permissive -d httpd_t ใช้ยกเลิกการตั้งค่านั้น

การปิดใช้งาน SELinux มีต้นทุนสูงกว่าการแก้ label

การตั้งค่า SELINUX=disabled ใน /etc/selinux/config คือการแลกการแก้ label เพียงบรรทัดเดียวกับการทำให้เซิร์ฟเวอร์มีความปลอดภัยลดลงอย่างถาวร ความแตกต่างจะเห็นได้ชัดในวันที่ web application ถูกเจาะระบบ เมื่ออยู่ในโหมด enforcing โค้ดของผู้โจมตีจะทำงานภายใต้ httpd_t จึงอาจอ่านเนื้อหาเว็บได้ แต่ policy จะปฏิเสธการอ่าน /etc/shadow หรือการเขียน systemd unit ไม่ว่า Unix user จะมีสิทธิ์ทำสิ่งนั้นหรือไม่ก็ตาม หากไม่มี policy โหลดอยู่ โค้ดเดียวกันจะได้รับสิทธิ์ทั้งหมดที่ service account มี

การปิดใช้งานยังมีต้นทุนที่ต้องจ่ายภายหลัง ขณะที่ไม่มี policy โหลดอยู่ ไฟล์ใหม่จะถูกสร้างโดยไม่มี label ทำให้สถานะของ filesystem ค่อย ๆ ไม่สอดคล้องกับ policy เมื่อเปิด SELinux กลับมาใช้งาน จะต้องทำ full relabel หรือไม่เช่นนั้น service จำนวนมากจะล้มเหลวพร้อมกัน:

sudo fixfiles -F onboot
sudo reboot

คำสั่งนี้จะเขียน /.autorelabel และทำ relabel ให้ filesystem ทุกตัวระหว่างการบูตครั้งถัดไป สำหรับ disk ขนาดใหญ่ กระบวนการนี้ใช้เวลานาน และ console อาจดูเหมือนค้าง จึงควรเริ่มทำเมื่อสามารถรอได้ เนื่องจากเครื่องกำลังจะ shutdown อยู่แล้ว จึงควรตรวจสอบก่อนว่ามีสิ่งใดถูกจัดคิวไว้ให้ restart อีกบ้าง โดย needs-restarting จะแจ้งรายการหลังจาก dnf update ทิ้ง kernel และ library รุ่นเก่าไว้ในหน่วยความจำ บน Rocky Linux และ AlmaLinux 9 ไฟล์ config จะไม่ปิดส่วนของ kernel ให้เองอีกต่อไป และวิธีที่มีการระบุไว้สำหรับปิด SELinux อย่างสมบูรณ์คือการใช้ kernel argument (sudo grubby --update-kernel ALL --args selinux=0) การรู้จักคำสั่งนี้มีประโยชน์เมื่อคุณรับช่วงดูแลเซิร์ฟเวอร์ของผู้อื่น คำสั่งนี้ไม่ใช่วิธีแก้ปัญหา 403

Containers เพิ่ม label อีกชั้น

บนโฮสต์ตระกูล Red Hat กระบวนการของ container ทำงานภายใต้ container_t และอาจอ่านได้เฉพาะไฟล์ที่มี label container_file_t เท่านั้น bind mount จากโฮสต์จึงล้มเหลวด้วย Permission denied ภายใน container แม้ว่า ls -l บนโฮสต์จะดูเป็นปกติดีก็ตาม suffix :Z จะบอกให้ runtime เปลี่ยน label ของ mount:

docker run -d -v /data/appdata:/var/lib/app:Z myimage

:Z จะกำหนด label ให้ไดเรกทอรีสำหรับ container นี้เท่านั้น ส่วน :z จะกำหนด label เพื่อให้ container หลายตัวใช้ร่วมกันได้ หากกำหนด :Z ให้ชี้ไปยังไดเรกทอรีที่ service อื่นใช้งานอยู่ ระบบจะเปลี่ยน label ของไดเรกทอรีนั้นแบบ recursive ซึ่งทำให้ service เหล่านั้นทำงานผิดพลาด ดังนั้นควรจัดเตรียม path แยกสำหรับ container หากยังไม่ได้ติดตั้ง engine บนเครื่อง โปรดทราบว่าใน distribution เหล่านี้ คำสั่ง docker มักเป็น podman ที่ตอบสนองต่อชื่อนี้ ซึ่งเป็นรายละเอียดที่ ขั้นตอนการติดตั้ง Rocky และ AlmaLinux จัดการไว้ก่อนที่คุณจะพบปัญหานี้ ส่วนอื่นของการตั้งค่าจะเหมือนกับ image อื่นทั่วไป ตามที่อธิบายไว้ใน การรัน Docker บน VPS

Ubuntu และ Debian มี AppArmor

ทำงานเดียวกัน แต่ใช้แนวทางต่างกัน AppArmor จำกัดสิทธิ์ของโปรแกรมตาม path ของ executable โดยใช้ profile ภายใต้ /etc/apparmor.d/ แทนการกำหนด label ให้ไฟล์บนดิสก์ จึงไม่มีสิ่งที่ต้องกำหนด label ใหม่ และไม่มี restorecon ให้เริ่มจากจุดนี้:

sudo aa-status
sudo journalctl -k | grep -i apparmor

การปฏิเสธจะแสดงเป็น apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/data/www/index.html" requested_mask="r" ขั้นตอนการตรวจสอบมีลักษณะเดียวกัน: อ่าน denial, ค้นหา profile แล้วแก้ rule sudo apt install apparmor-utils ให้คำสั่ง aa-complain (ตั้งเป็น permissive สำหรับ profile เดียว) และ aa-enforce เพื่อเปลี่ยนกลับ Ubuntu จำกัดสิทธิ์บริการบางส่วนที่ติดตั้งมาพร้อมแพ็กเกจ และปล่อยบริการอื่นให้ไม่มีการจำกัดสิทธิ์ ดังนั้นให้อ่าน aa-status เพื่อดูว่าสิ่งใดกำลังทำงานอยู่จริง แทนการคาดเดา

มีแนวทางหนึ่งที่ใช้ได้กับทั้ง 2 ระบบ เมื่อ service รายงาน Permission denied กับสิ่งที่ดูเหมือนตั้งค่าถูกต้อง ให้ดู security log ก่อนแก้ไข permission ปัญหามักไม่ได้เกิดจาก permission ซ้ำเป็นครั้งที่ 2

FAQ

เหตุใด nginx จึงตอบกลับ 403 ทั้งที่สิทธิ์ของไฟล์ถูกต้อง?

เพราะ SELinux ปฏิเสธการอ่าน ไม่ใช่เพราะ permission bits เว็บเซิร์ฟเวอร์ทำงานอยู่ในโดเมน httpd_t และอาจอ่านได้เฉพาะไฟล์ที่มี label สำหรับเนื้อหาเว็บเท่านั้น ดังนั้นไฟล์ที่มี label default_t หรือ admin_home_t จึงถูกปฏิเสธ และ nginx ไม่มีเนื้อหาให้บริการ ตรวจสอบได้ด้วย sudo ausearch -m AVC -ts recent ซึ่งจะแสดง scontext ที่ลงท้ายด้วย httpd_t และ tcontext ที่มี type ไม่ถูกต้อง จากนั้นบันทึก label ที่ถูกต้องและนำไปใช้ด้วยคำสั่งต่อไปนี้: sudo semanage fcontext -a -t httpd_sys_content_t "/data/www(/.*)?" ตามด้วย sudo restorecon -Rv /data/www

การใช้ setenforce 0 เพื่อให้บริการทำงานปลอดภัยหรือไม่?

setenforce 0 เป็นขั้นตอนสำหรับการวินิจฉัย ไม่ใช่การแก้ไข ใช้เพื่อทำให้เกิดปัญหาซ้ำ 1 ครั้ง เพื่อให้ log รวบรวมการปฏิเสธทั้งหมดไว้ในการตรวจสอบรอบเดียว อ่านรายการเหล่านั้นด้วย sudo ausearch -m AVC -ts recent จากนั้นเรียกใช้ sudo setenforce 1 และแก้ไขสาเหตุ หากปล่อยเซิร์ฟเวอร์ไว้ในโหมด permissive ระบบจะบันทึกการปฏิเสธทุกรายการแต่จะไม่บล็อกใดเลย คุณจึงยังคงได้รับ log จำนวนมากและสูญเสียการป้องกัน หากต้องเปิดช่องให้บริการหนึ่งชั่วคราวระหว่างแก้ไข ให้ใช้ sudo semanage permissive -a httpd_t เพื่อให้ส่วนอื่นของเครื่องยังคงทำงานในโหมด enforcing

จะให้บริการทำงานบนพอร์ตที่ไม่ใช่พอร์ตมาตรฐานขณะ SELinux อยู่ในโหมด enforcing ได้อย่างไร?

เพิ่มพอร์ตลงใน type ที่บริการนั้นได้รับอนุญาตให้ bind สำหรับเว็บเซิร์ฟเวอร์ที่ใช้พอร์ต 8081: sudo semanage port -a -t http_port_t -p tcp 8081 สำหรับ SSH ที่ใช้พอร์ต 2222: sudo semanage port -a -t ssh_port_t -p tcp 2222 ตรวจสอบรายการปัจจุบันก่อนด้วย sudo semanage port -l | grep -w http_port_t เนื่องจากพอร์ตที่มีอยู่ในรายการแล้วจะทำให้ ValueError: Port tcp/8081 already defined หากไม่ทำขั้นตอนนี้ daemon จะหยุดทำงานขณะเริ่มต้นพร้อม bind() ... Permission denied แม้จะไม่มี process อื่นใช้งานพอร์ตนั้น

Ubuntu มี SELinux หรือไม่?

ไม่มี Ubuntu และ Debian มาพร้อม AppArmor ซึ่งบังคับใช้ profile ที่ผูกกับ path ของ executable แทนการใช้ label กับไฟล์ ตรวจสอบได้ด้วย sudo aa-status และค้นหาบรรทัด apparmor="DENIED" ใน sudo journalctl -k Ubuntu จำกัดสิทธิ์บริการที่ติดตั้งจากแพ็กเกจไว้เพียงบางส่วน ดังนั้นโปรแกรมจำนวนมากจึงทำงานแบบ unconfined โดยค่าเริ่มต้น ส่วน Rocky Linux และ AlmaLinux เป็นระบบที่พบ SELinux ในโหมด enforcing ตั้งแต่ติดตั้ง เช่นเดียวกับ Fedora และ RHEL