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

اسم المستخدم في البيئة ليس مفتاحًا للهوية

يقرّر يونكس ما يمكن للعملية لمسه انطلاقًا من معرّف المستخدم الفعّال، لا من السلاسل النصية في بيئتها. تعيين USER=alice أو تصدير LOGNAME يغيّر ما تطبعه البرمجيات المهملة في مخرجاتها؛ ولا يغيّر شيئًا في فحوص ملكية الملفات. الخطاف الذي يعمل بصفة الجذر مع ضبط ذلك المتغيّر يظل يفتح ملفات أليس بصفة الجذر.

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

إذًا فالسؤال الأول ليس «أي مستخدم متصل» بل «أي حساب محلي يُسمح له بتنفيذ هذا الإجراء». أجب عن ذلك من قاعدة حساباتك أنت، لا مما أرسله العميل:

id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json

الأمر الثالث هو الذي يتخطاه الناس. إذا كان الملف الهدف مملوكًا للجذر لأن الخطاف السابق أنشأه، فإن إجراء مستخدم مضبوطًا بشكل صحيح سيفشل بخطأ أذونات يبدو كمشكلة في systemd وليس كذلك.

البرنامج الخفي يبقى في مكانه

تغيير User= الخاص بخدمة VPN إلى أليس هو الرافعة الخاطئة. فهو يغيّر طريقة ربط البرنامج الخفي للواجهات، وقراءته للمفاتيح، وكتابته لملف حالته، وإدارته للمسارات، وينقل كل هذا الخطر إلى وحدة كانت تعمل. البرنامج الخفي يحتاج امتيازاته؛ الإجراء اللاحق لا يحتاجها.

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

systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server

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

ثلاثة أجزاء مسماة، ومُشغِّل واحد ضيق

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

الإجراء نفسه وحدة خدمة عادية، والهوية توجيه واحد بداخلها:

[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i

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

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

تحقّق من الهوية التي عملت فعلًا

مراجعة الإعدادات ليست دليلًا. بعد اتصال اختباري معتمد، أكّد معرّف المستخدم الذي لمس الملف ومعرّف المستخدم الذي ما زال البرنامج الخفي يستخدمه:

stat -c '%U %G %y %n' /home/alice/.local/state/vpn-status.json
ps -o user=,pid=,cmd= -p "$(pgrep -o -f 'openvpn --daemon')"
systemd-run --uid=alice --pipe /usr/local/bin/refresh-vpn-status --dry-run

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

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

ما لا يزال حدث الاتصال لا يثبته

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

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

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

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