فشل client-connect في OpenVPN: هل تنظيف disconnect مضمون؟
العميل المرفوض لا يتراجع عن قاعدة جدار ناري ولا عن سجل مكتوب. قارن عقد الاستدعاء بالتراجع الذي يجب أن ينفذه الخطاف من تلقاء نفسه.
عندما ينتهي سكربت client-connect بقيمة غير صفرية، يرفض OpenVPN العميل، ويفترض المشغّلون عادةً أن سكربت client-disconnect المقابل سيرتّب الأمور. غالبًا لا يعمل هذا الأخير أبدًا، لأن معالجة disconnect مشروطة بما تمكنت مرحلة connect من إنجازه. اعتبار الرفض والتراجع آلية واحدة هو ما يحوّل تسجيل دخول فاشل إلى قاعدة جدار ناري مسرّبة أو سجل يتيم. إنهما التزامان منفصلان، وواحد فقط منهما تلقائي. اقرأ مسار الخروج الخاص بالخطاف قبل أن تلوم الخادم على ترك حالة خلفه.
الخروج بقيمة غير صفرية يرفض العميل، لكنه لا يتراجع عن عملك
حالة خروج client-connect هي قرار قبول. الصفر يعني القبول، وأي قيمة أخرى تعني هدم النسخة قبل أن تُثبَّت بالكامل. لا شيء في ذلك العقد يفحص ما فعله السكربت بالفعل بالعالم الخارجي. إذا أضاف السكربت قاعدة iptables، أو كتب صفًا، أو رفع عدّادًا، فإن تلك التغييرات تنجو من الرفض.
ينطبق التباين نفسه على الإضافات، حيث يمكن أن تعمل عدة دوال connect لنسخة واحدة. الفشل اللاحق لا يلغي بأثر رجعي نجاحًا سابقًا، والدالة السابقة لا سبيل لها إلى معرفة أن دالة شقيقة فشلت.
الرفض تلقائي، أما التنظيف فليس كذلك
يوثّق دليل OpenVPN 2.6 معالجة client-disconnect بنجاح معالجة connect السابقة. هناك استثناء موثّق يغطي السلاسل المتتالية: إذا كانت إحدى خطوات connect قد أعادت النجاح، فإن خطوات disconnect المطابقة تعمل رغم ذلك عند هدم النسخة، حتى لو فشلت خطوة connect لاحقة. ذلك الاستثناء ضيق ويعتمد على عدد الاستدعاءات المهيأة، ولهذا فإن اختبار سكربت shell واحد لا يخبرك شيئًا عن سلسلة.
ارسم الخريطة الفعلية للإعداد قبل التصميم بناءً عليه: أي سكربت، وأي إضافة، وبأي ترتيب تعمل. يسرد الجدول أدناه ما يتركه إعداد نصف مكتمل خلفه.
| What the hook did before failing | What the client sees | What stays behind |
|---|---|---|
| Created a firewall entry | A rejection, with no tunnel | A rule matching an address nobody owns |
| Wrote a state row | A rejection, with no tunnel | A record of a session that never started |
| Allocated a tunnel address | A rejection, with no tunnel | An address marked busy until something releases it |
ينتهي كل صف بالطريقة نفسها بالنسبة للعميل وبطريقة مختلفة بالنسبة للمضيف. لا يستطيع العميل معرفة أيّها حدث، لذا على الخطاف أن يتراجع عمّا فعله قبل إعادة حالة الفشل.
عوّض داخل السكربت الذي فشل
تحقّق من كل ما يمكنك التحقق منه قبل لمس الحالة المشتركة: حقول الشهادة، وتوفر العنوان، والمجموعة التي ينتمي إليها العميل. أرخص رفض هو الذي يحدث قبل أي تخصيص. وبمجرد أن يمتد الإعداد عبر عدة عمليات خارجية، احتفظ بسجل لما اكتمل منها وتراجع عنه بالترتيب العكسي قبل إعادة حالة الفشل.
حالة ملموسة: ينشئ السكربت إدخال جدار ناري لكل عميل، ثم يفشل في كتابة صف الحالة الذي يصفه. على الفرع الفاشل أن يحذف الإدخال الذي أنشأه للتو ثم يخرج بقيمة غير صفرية. انتظار حدث disconnect قد لا يُجدول أبدًا هو ما يجعل قاعدة تعيش أطول من الجلسة التي كُتبت من أجلها.
اجعل التعويض متسامحًا مع التكرار ومملوكًا
يجب أن يكون التنظيف آمنًا للتشغيل مرتين ودقيقًا بشأن ما يجوز له حذفه. لا ينبغي لحدث متأخر من محاولة سابقة أن يحذف تخصيص جلسة أحدث لمجرد أن الاثنين يتشاركان اسمًا شائعًا أو عنوان نفقًا معادًا استخدامه. سجّل المقبض أو معرّف العملية المحدد مع المورد، واحذف بذلك المفتاح لا بالاسم. معاملات قاعدة البيانات تساعد فقط في تغييرات قاعدة البيانات نفسها؛ أما قاعدة الجدار الناري خارج تلك الحدود فتحتاج خطوة تعويض صريحة.
افترض أن العملية قد تموت بين التغيير والتنظيف، وأضف مُصالحًا يقارن الحالة الخارجية بقائمة العملاء المقبولين وفق جدول زمني.
اختبر المسارات الثلاثة التي تختلف فعلًا
مارس ثلاث حالات: رفض قبل تخصيص أي شيء، ورفض بعد تخصيص جزئي، وجلسة مقبولة عادية تنتهي لاحقًا. تنتج تلك الثلاث سطور سجل مختلفة وبقايا مختلفة. تحقّق من الحالة الخارجية بعد كل منها، لا من رسالة فشل العميل فقط، لأن فشل العميل هو النتيجة المتوقعة في اثنتين من الثلاث.
سجّل مرحلة الخطاف، وحالة الخروج، وما تم تخصيصه أو تحريره، مع استبعاد بيانات الاعتماد. سجل الخطاف الذي يسمّي المورد الذي أنشأه هو ما يجعل قاعدة مسرّبة قابلة للاكتشاف بعد ستة أشهر.