hook ทำงานเมื่อคุณรันด้วยมือในฐานะ root และคืนค่าOperation not permittedในขณะที่บริการเริ่มมัน ไฟล์ไม่ได้เปลี่ยนระหว่างการรันเหล่านั้น สภาพแวดล้อมต่างหากที่เปลี่ยน systemd มอบโลกที่เล็กลงให้กระบวนการ: ความสามารถน้อยลง/devที่แตกต่าง พาธระบบไฟล์ที่อ่านได้อย่างเดียว และเพดานจำนวนงาน อ่านความล้มเหลวเป็นขอบเขตที่ปฏิเสธการดำเนินการหนึ่งโดยเฉพาะ ไม่ใช่สคริปต์ที่พัง

เริ่มจากการดำเนินการที่ถูกปฏิเสธ

บรรทัด log ที่บอกว่า unit ล้มเหลวแทบไม่บอกอะไรคุณเลย บรรทัดที่มีประโยชน์ระบุชื่อการเรียกที่ถูกปฏิเสธ: โหนดอุปกรณ์ที่เปิดไม่ได้ ไดเรกทอรีที่เขียนไม่ได้ namespace ที่สร้างไม่ได้ ค้นหาการเรียกนั้นก่อนที่คุณจะแตะคำสั่งใด ๆ เพราะแต่ละอย่างแมปกับข้อจำกัดที่ต่างกัน จากนั้นอ่าน unit ที่รันจริง รวมถึง drop-in แทนไฟล์ที่แพ็กเกจจัดส่งมา

คิวรีด้านล่างพิมพ์พาธ fragment ไดเรกทอรี drop-in และพร็อพเพอร์ตี้ที่สำคัญสำหรับการปฏิเสธอุปกรณ์หรือระบบไฟล์ อ่านทั้งหมดก่อนตัดสินว่าขอบเขตใดเกี่ยวข้อง:

systemctl show "[email protected]" -p FragmentPath -p DropInPaths -p PrivateDevices -p DevicePolicy -p DeviceAllow -p ProtectHome -p ProtectSystem -p TasksMax
journalctl -u "[email protected]" -b --no-pager | grep -iE 'operation not permitted|permission denied|cannot open|failed'

ไม่มีอะไรในบล็อกนั้นเขียน ถ้าการเรียกที่ถูกปฏิเสธหายไปจาก journal ให้เพิ่มความละเอียดของไคลเอนต์สำหรับการรันหนึ่งครั้งแทนการเดาคำสั่ง กระบวนการที่กำลังรันพิสูจน์ว่า unit เริ่มแล้ว ไม่ใช่ว่า hook รันด้วยสิทธิ์ที่ต้องการ

namespace ต้องการสิทธิ์ mount ที่อุปกรณ์ TUN ไม่เคยใช้

การดำเนินการสองอย่างที่ดูคล้ายกันภายใน hook เดียวมีโปรไฟล์สิทธิ์ต่างกัน การเปิด/dev/net/tunและการสร้างอินเทอร์เฟซต้องใช้การบริหารเครือข่ายบวกการเข้าถึงโหนดนั้น การสร้าง namespace เครือข่ายที่ตั้งชื่อก็ต้องทำงานเกี่ยวกับ mount เช่นกัน เพราะเครื่องมือ namespace mount/sysวิวใหม่และผูกไดเรกทอรีสถานะเข้าที่ unit ไคลเอนต์ต้นทาง จงใจปล่อยการบริหาร mount ออกจากชุดขอบเขตของตน ดังนั้นขั้นตอน namespace จึงถูกปฏิเสธในขณะที่อุโมงค์เองจะทำงานได้

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

การทดสอบใน shell ของคุณสืบทอดทุกความสามารถที่ root ไม่จำกัดมี และ unit สืบทอดสิ่งที่ชุดขอบเขตของมันเก็บไว้ การรันทั้งสองเป็นการทดลองที่ต่างกัน แม้ไบนารีจะเหมือนกัน

อุปกรณ์ พาธที่ป้องกัน และเพดานงาน

เมื่อรู้การดำเนินการที่ถูกปฏิเสธแล้ว คำสั่งสามกลุ่มอธิบายความล้มเหลวส่วนใหญ่ การเข้าถึงอุปกรณ์ถูกควบคุมโดยPrivateDevicesซึ่งแทนที่/devด้วยชุดอุปกรณ์เทียมขั้นต่ำ และโดยDevicePolicyกับDeviceAllowซึ่งตัดสินว่า cgroup อนุญาตอะไร/dev/net/tunไม่ใช่ส่วนหนึ่งของชุดขั้นต่ำนั้น ดังนั้น unit ที่มีPrivateDevices=yesเปิดมันไม่ได้ไม่ว่าปโยบายอุปกรณ์จะว่าอย่างไร

การป้องกันระบบไฟล์ตอบคำถามที่สองProtectHome=yesทำให้/home, /rootและ/run/userเข้าถึงไม่ได้ และProtectSystem=fullทำให้/usr, /bootและ/etcอ่านได้อย่างเดียว ดังนั้น hook ที่เขียนข้างไฟล์กำหนดค่าของตัวเองจึงทำงานใน shell ของคุณและล้มเหลวภายใต้ unit ด้วยรูปร่างสิทธิ์แบบเดียวกับความสามารถที่หายไป ย้ายอินพุตที่เขียนได้ไปยังพาธที่ปโยบายอนุญาต หรือเพิ่มReadWritePaths=รายการแคบ ๆ

ขีดจำกัดทรัพยากรเป็นกลุ่มที่สาม และเห็นได้ชัดน้อยที่สุดTasksMax=จำกัดจำนวนงานที่ unit สร้างได้ และเพดานตัวอธิบายไฟล์ทำงานแบบเดียวกันกับซ็อกเก็ตและแฮนเดิล การติดตั้งที่โตเกินเพดานล้มเหลวด้วยข้อผิดพลาดทรัพยากรแทนข้อผิดพลาดสิทธิ์ ซึ่งเป็นเหตุผลว่าข้อความที่แน่นอนยังคงสำคัญ

ย้ายขั้นตอน namespace ออกจาก hook

การเตรียมการที่ต้องใช้สิทธิ์ที่ไคลเอนต์ไม่ควรถือเป็นของ unit ของตัวเอง oneshot ที่รันก่อนไคลเอนต์ เก็บผลลัพธ์ไว้ และถูกจัดลำดับก่อนการเชื่อมต่อ ให้ขั้นตอน namespace สิ่งที่ต้องการโดยไม่มอบสิทธิ์เหล่านั้นให้กระบวนการอุโมงค์ จากนั้นไคลเอนต์อ้างอิง unit นั้นแทนการเรียกสคริปต์จาก up hook ของตน

เทมเพลตด้านล่างเป็นรูปร่างเริ่มต้น ไม่ใช่สิ่งที่นำไปใช้โดยไม่อ่าน มันเก็บการเตรียมการไว้ที่ซึ่งสิทธิ์อาศัยอยู่และปล่อย unit อุโมงค์ไว้ไม่แตะต้อง:

# /etc/systemd/system/[email protected]
[Unit]
Description=Namespace preparation for %i
Before=openvpn-client@%i.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/netns-up.sh %i
CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_ADMIN
[Install]
WantedBy=multi-user.target

เพิ่มลำดับด้วย drop-in แทนการแก้ไข unit ที่แพ็กเกจมา เพื่อให้การอัปเกรดไม่สามารถทิ้งการเปลี่ยนแปลงของคุณอย่างเงียบ ๆ ไดเรกทอรีผูกกับชื่ออินสแตนซ์ ดังนั้นโปรไฟล์ที่สองต้องมีไฟล์ของตัวเอง:

sudo mkdir -p /etc/systemd/system/[email protected]
sudo tee /etc/systemd/system/[email protected]/10-namespace.conf >/dev/null <<'CONF'
[Unit]
[email protected]
[email protected]
CONF
sudo systemctl daemon-reload

ทำการเรียกเดิมซ้ำหลังการเปลี่ยนแปลง

การซ่อมได้รับการยืนยันเมื่อการเรียกเดิมให้ผลลัพธ์ที่ต่างออกไปเท่านั้น รีสตาร์ททั้งสอง unit รันการดำเนินการที่ล้มเหลว และเปรียบเทียบผลลัพธ์กับบรรทัด journal ที่คุณบันทึกไว้ก่อนการเปลี่ยนแปลง unit ที่เริ่มในขณะที่ hook ไม่เคยรันอีกไม่ใช่การซ่อม

ลำดับด้านล่างทำให้การเปรียบเทียบซื่อสัตย์ เพราะมันตรวจสอบการเรียกที่ถูกปฏิเสธไม่ใช่สีของ unit บันทึกเอาต์พุตของมันข้างบรรทัด journal ที่คุณจับไว้ก่อนการเปลี่ยนแปลง:

sudo systemctl restart "[email protected]" "[email protected]"
systemctl status "[email protected]" --no-pager
journalctl -u "[email protected]" -b --no-pager | grep -c 'Operation not permitted'
ip -brief addr show tun0

นับการปฏิเสธแทนการอ่านบรรทัดสุดท้าย เพราะ hook อาจล้มเหลว ลองใหม่ และสำเร็จ และมีเพียงจำนวนที่นับได้เท่านั้นที่แสดงว่าขอบเขตขยับหรือไม่ ทิ้งความคิดเห็นไว้ใน drop-in โดยระบุชื่อคำสั่งที่คุณผ่อนปรนและเหตุผล เนื่องจากการอัปเกรดครั้งถัดไปจะเปรียบเทียบการกำหนดค่าของคุณกับค่าเริ่มต้นที่แพ็กเกจมา unit ที่เริ่มได้เฉพาะเมื่อปิดการป้องกันทุกอย่างแล้วยังไม่ได้รับการแก้ไข

บทความที่เกี่ยวข้อง