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

مستند هنا الثابت أسطورة

يثبّت عميل Cisco على Linux برنامجًا صغيرًا في /opt/cisco/anyconnect/bin/vpn، وما زال Secure Client 5 يوفّره. ما دامت خدمة vpnagentd تعمل، يقرأ الخيار s- الإجابات من المدخل القياسي بالترتيب الذي يطلبها به العميل. وهذا الترتيب ليس ثابتًا بين الخوادم.

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

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

ما ينتظره العميل

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

/opt/cisco/anyconnect/bin/vpn -s connect vpn.example.net <<EOF
0
alice
$VPN_PASSWORD
y
EOF

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

استخدم expect عندما لا يكون الترتيب متوقعًا

حين يتغيّر ترتيب المطالبات، توقّف عن عدّ الأسطر وانتظر المطالبة بدلًا من ذلك. يشغّل سكربت expect أمر connect، وينتظر مطالبة اسم المستخدم، ويرسل، ثم ينتظر مطالبة كلمة المرور، ويرسل، ثم ينتظر حالة الاتصال. ولكل انتظار مهلة خاصة به.

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

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

على السر أن يسكن في مكان ما

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

install -m 600 /dev/null ~/.vpn-secret
umask 077; VPN_PASSWORD=$(cat ~/.vpn-secret) /opt/cisco/anyconnect/bin/vpn -s connect vpn.example.net

ملف بالوضع 600 يملكه الحساب هو الحد الأدنى، وهو مع ذلك سر نصي صريح على القرص. يقرأ secret-tool lookup من حلقة مفاتيح Linux، وعلى macOS يؤدّي security find-generic-password -w المهمة نفسها. وحيث تدعم المؤسسة المصادقة بالشهادة أو SAML، فضّلها، لأن كلمة مرور مخزّنة مع MFA لا تزال عاجزة عن إتمام تسجيل الدخول دون إنسان.

أكّد النفق، ثم تولَّ الفصل

لا تثق بغياب نص الخطأ. اسأل العميل عن حالته وافحص الواجهة بصورة مستقلة:

/opt/cisco/anyconnect/bin/vpn state
ip -brief address

ينبغي أن تقرأ الحالة Connected، وأن تحمل واجهة النفق عنوانًا. ثم اطلب موردًا واحدًا يُفترض أن يبلغه النفق، لأن حالة متصلة بلا مسار نتيجة واقعية ويسهل إغفالها.

افصل بـ vpn disconnect، ودع السكربت الذي بدأ النفق يتولى الفصل. فالمهمة الفاشلة التي تترك جلسة مفتوحة تستهلك مقعد ترخيص وتبدو كمشكلة مصادقة للشخص التالي الذي يحاول.

ترتيب العمل: أكّد vpnagentd، والتقط ترتيب المطالبات الفعلي، واختر مستند هنا أو expect، وأبقِ السر خارج argv والسجل، وتحقق من الحالة ومورد واحد، ثم افصل في السكربت نفسه.

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