התחברות ל-Cisco Secure Client ממעטפת Linux, בלי ה-GUI
here-document אינו פרוטוקול התחברות. רשום את סדר ההנחיות האמיתי, השתמש ב-expect כשהוא משתנה, והרחק את הסיסמה מ-argv ומ-history.
ההנחה היא ש-here-document עם ארבע שורות הוא התחברות לא-אינטראקטיבית מלאה, והיא מחזיקה בדיוק עד שהשער מציע שתי קבוצות, מדפיס באנר או מבקש טוקן אחרי הסיסמה. ואז אותו סקריפט מקליד את הסיסמה לתוך תפריט שציפה למספר. לקוח שורת הפקודה ב-Linux ניתן לסקריפט, אבל רק אם נותנים לו את מה שהוא באמת ממתין לו, ולא את מה שהמדריך הניח.
ה-here-document הקבוע הוא מיתוס
לקוח ה-Linux של Cisco מתקין תוכנית קטנה ב-/opt/cisco/anyconnect/bin/vpn, ו-Secure Client 5 עדיין מספק אותה. כששירות vpnagentd פועל, הדגל -s קורא תשובות מקלט תקני בסדר שבו הלקוח מבקש אותן. הסדר הזה אינו קבוע בין שרתים.
תפריט קבוצות מוסיף שורה אחת. באנר התחברות מוסיף עוד אחת. בקשת טוקן אחרי הסיסמה שוברת לגמרי סקריפט בן ארבע שורות, כי השורה החמישית מעולם לא נועדה להיות הטוקן. הכשל שקט: הלקוח מקבל מחרוזת שגויה, ניסיון החיבור מסתיים, והסקריפט לא מדווח דבר שימושי.
הרץ את הלקוח ידנית פעם אחת מול אותו שער ואותה גרסה, ורשום את ההנחיות בסדר שבו הן מופיעות. התמלול הזה הוא המפרט שהסקריפט שלך חייב למלא.
למה הלקוח ממתין
במקרה הרגיל של קבוצה אחת הרצף הוא שם משתמש, סיסמה ולפעמים אישור באנר. השורה הראשונה של here-document הופכת לאינדקס תפריט רק כשהשרת באמת מציג תפריט, ולכן אותו סקריפט עובד באתר אחד ונכשל בבא אחריו.
/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, תעד את סדר ההנחיות האמיתי, בחר here-document או expect, הרחק את הסוד מ-argv ומ-history, אמת מצב ומשאב אחד, ואז התנתק באותו סקריפט.