يعمل الخطّاف عند تشغيله يدويًا بصلاحيات root ويُعيد Operation not permitted لحظة أن تبدأه الخدمة. لم يتغيّر الملف بين هذين التشغيلين؛ بل تغيّرت البيئة. يمنح systemd العملية عالمًا أصغر: قدرات أقل، و /devمختلفًا، ومسارات نظام ملفات للقراءة فقط، وسقفًا للمهام. اقرأ الفشل كحدٍّ رفض عملية واحدة محدّدة، لا كنص برمجي تعطّل.

ابدأ من العملية التي رُفضت

سطر سجل يقول إن الوحدة فشلت لا يخبرك بشيء تقريبًا. السطر المفيد يسمّي الاستدعاء الذي رُفض: عقدة جهاز تعذّر فتحها، أو دليل تعذّر الكتابة فيه، أو مساحة أسماء تعذّر إنشاؤها. اعثر على ذلك الاستدعاء قبل أن تمسّ أي توجيه، لأن كل واحد منها يقابل قيدًا مختلفًا. ثم اقرأ الوحدة التي عملت فعلًا، بما فيها ملفات drop-in، بدلًا من الملف الذي شحنه الحزمة.

يستعلم الأمر أدناه عن مسار المقطع وأدلة 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'

لا شيء في ذلك المقطع يكتب. إذا كان الاستدعاء المرفوض غائبًا من السجل، فارفع مستوى تفصيل العميل لتشغيل واحد بدلًا من تخمين التوجيهات. عملية تعمل تُثبت أن الوحدة بدأت، لا أن الخطّاف عمل بالامتيازات التي يحتاجها.

مساحة الأسماء تحتاج صلاحيات تركيب لا يستخدمها جهاز TUN أبدًا

عمليتان تبدوان متشابهتين داخل خطّاف واحد لهما ملفّا أذونات مختلفان. فتح /dev/net/tun وإنشاء الواجهة يحتاج إلى إدارة الشبكة وإلى الوصول إلى تلك العقدة. كما أن إنشاء مساحة أسماء شبكية مسمّاة يحتاج إلى عمل متعلّق بالتركيب، لأن أداة مساحة الأسماء تُركّب /sys عرضًا جديدًا وتربط دليل حالة في مكانه. وحدة العميل الأصلية تترك إدارة التركيب عمدًا خارج مجموعة حدودها، ولهذا تُرفض خطوة مساحة الأسماء بينما كان النفق نفسه سيعمل.

لهذا لا يُنقذ منح عقدة الجهاز النص البرمجي. إذن جهاز واحد يجيب عن سؤال واحد: هل يجوز لهذه العملية فتح هذه العقدة؟ وسؤال مساحة الأسماء سؤال آخر. طابق الاستدعاء الفاشل مع قدرته قبل أن توسّع أي شيء.

اختبار في صدفتك يرث كل قدرة يملكها root غير المقيّد، والوحدة ترث ما احتفظت به مجموعة حدودها. التشغيلان تجربتان مختلفتان حتى عندما يكون الملف التنفيذي نفسه.

الأجهزة والمسارات المحمية وسقوف المهام

بعد معرفة العملية المرفوضة، تشرح ثلاث مجموعات من التوجيهات معظم حالات الفشل. يُحكَم الوصول إلى الأجهزة بـ PrivateDevices، وهي تحلّ محل /dev بمجموعة دنيا من الأجهزة الوهمية، وبـ DevicePolicy مع DeviceAllow، وهما تحدّدان ما تسمح به cgroup. /dev/net/tun ليس جزءًا من تلك المجموعة الدنيا، لذا فإن وحدة تحمل PrivateDevices=yes لا تستطيع فتحه مهما قالت سياسة الأجهزة.

حماية نظام الملفات تجيب عن سؤال ثانٍ. ProtectHome=yes يجعل /home, /root و /run/user غير قابلة للوصول، و ProtectSystem=full يجعل /usr, /boot و /etc للقراءة فقط. لذلك يعمل الخطّاف الذي يكتب بجانب ملف إعداداته في صدفتك ويفشل تحت الوحدة، بنفس شكل الإذن الذي يظهر عند غياب قدرة. انقل المُدخل القابل للكتابة إلى مسار تسمح به السياسة، أو أضف مدخلًا ReadWritePaths= ضيّقًا.

حدود الموارد هي المجموعة الثالثة، وأقلها وضوحًا. TasksMax= يحدّد عدد المهام التي يجوز للوحدة إنشاؤها، وسقف واصفات الملفات يعمل بالطريقة نفسها للمقابس والمقابض. والنشر الذي تجاوز السقف يفشل بخطأ موارد بدلًا من خطأ أذونات، ولهذا تبقى الرسالة الدقيقة مهمة.

أخرج خطوة مساحة الأسماء من الخطّاف

التحضير الذي يحتاج إلى امتياز لا يجوز أن يملكه العميل ينتمي إلى وحدته الخاصة. وحدة oneshot تعمل قبل العميل وتحتفظ بنتيجتها وتُرتَّب قبل الاتصال تمنح خطوة مساحة الأسماء ما تحتاجه دون تسليم تلك الحقوق إلى عملية النفق. ثم يشير العميل إلى تلك الوحدة بدلًا من استدعاء النص البرمجي من خطّاف up.

القالب أدناه شكل بدئي، وليس شيئًا يُطبَّق دون قراءة. يحفظ التحضير حيث يقيم الامتياز ويترك وحدة النفق دون مساس:

# /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 بدلًا من تعديل الوحدة المشحونة، حتى لا يُسقط ترقيةٌ تغييرك بصمت. الدليل مفتاحه اسم النسخة، لذا يحتاج ملف التعريف الثاني إلى ملفه الخاص:

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

أعد الاستدعاء نفسه بعد التغيير

لا يُؤكَّد الإصلاح إلا عندما يُنتج الاستدعاء نفسه نتيجة مختلفة. أعد تشغيل الوحدتين، ونفّذ العملية التي فشلت، وقارن نتيجتها بسطر السجل الذي حفظته قبل التغيير. الوحدة التي تبدأ بينما لا يعمل الخطّاف مرة أخرى ليست إصلاحًا.

التسلسل أدناه يحفظ المقارنة صادقة، لأنه يفحص الاستدعاء المرفوض لا لون الوحدة. احفظ ناتجه بجانب سطر السجل الذي التقطته قبل التغيير:

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

عُدّ حالات الرفض بدلًا من قراءة السطر الأخير، لأن الخطّاف قد يفشل ثم يعيد المحاولة ثم ينجح، والعدّ وحده يُظهر هل تحرّكت الحدود. اترك تعليقًا في ملف drop-in يسمّي التوجيه الذي خفّفته وسببه، لأن الترقية التالية تقارن إعدادك بالافتراضي المشحون. الوحدة التي لا تبدأ إلا بعد تعطيل كل حماية لم تُصلح.

مقالات ذات صلة