סקריפט auth-user-pass-verify של OpenVPN: קוד היציאה הנכון
סטטוס היציאה של ה-hook הוא ההחלטה, לא שורת הלוג שלו. מפו תוצאות מאמת ל-0, 1 או דחוי, קראו את קובץ האישורים כנתונים, והיכשלו סגור.
OpenVPN פועל לפי פלט אחד בלבד של סקריפט התחברות: סטטוס היציאה. כל השאר שהסקריפט עושה הוא קישוט. hook יכול להגיע למאמת הנכון, לנתח את התגובה ולהדפיס שורה ידידותית ובכל זאת להחזיר סטטוס שמשמעותו הפוכה ממה שהמחבר התכוון. קראו קודם את החוזה וכתבו את המתאם סביבו, וכל מחלקת הבאגים הזו נעלמת.
החוזה קטן: זהו את החשבון שנשלח, שאלו את המאמת המאושר את השאלה המיועדת, וקבלו רק את התוצאה החיובית המתועדת של אותו מאמת עבור אותה בקשה. כל השאר הוא תשתית.
שלושת הסטטוסים שה-hook יכול להחזיר
עבור auth-user-pass-verify, המדריך של OpenVPN 2.6 מגדיר קוד יציאה 0 כקבלה, 1 כדחייה ו-2 כאותנטיקציה נדחית. אלו שלוש התשובות היחידות שהשרת מבין, ולכל אחת מהן השלכה שונה על החיבור. סקריפט שיוצא עם הסטטוס של הפקודה האחרונה שלו למעשה נתן לקריאת לוג או לשלב ניקוי להצביע. כתבו את הסטטוס במפורש בכל ענף, כולל בענפים שמעולם לא הגיעו למאמת.
הצלחת HTTP והצלחת אימות הן שכבות נפרדות, וערבוב שלהן הוא הדרך הנפוצה ביותר שבה ה-hook הזה נכשל. תגובת 202 פירושה שהבקשה התקבלה לעיבוד, לא שסיסמה אומתה. הגדירו את הסטטוס הצפוי, צורת התגובה והפסיקה הסופית מתוך חוזה ה-API שקיבלתם, ואל תמציאו שם שדה כי הוא נראה סביר.
אם התשובה באמת מגיעה מאוחר יותר, זהו המסלול הנדחה והוא חייב להיבנות ככזה. ניסיון נדחה שלעולם אינו מושלם מחזיק במשאבים עד שפג הזמן הקצוב.
קראו את קובץ האישורים בדיוק כפי שהוא כתוב
בשיטת הקובץ המתועדת, השרת כותב את שם המשתמש והסיסמה כשתי השורות הראשונות של קובץ זמני, מעביר את הנתיב לסקריפט ומוחק את הקובץ כשהסקריפט חוזר. קראו את שתי השורות כנתונים: לעולם אל תעשו source לקובץ, לעולם אל תעריכו אותו, ולעולם אל תדביקו את תוכנו לשורת פקודה שבה טבלת התהליכים יכולה לראות אותו. שמרו את הסיסמה בייט אחר בייט, כי נרמול כפי הייתם מנרמלים שם משתמש משנה את האישור בשקט. הספרייה של הקובץ הזמני הזה היא החלטת פריסה, לכן ודאו שהיא קיימת ושאינה קריאה לכולם במארח.
הפרידו את האישור של המאמת עצמו מזה של המשתמש: הוא שייך לאינטגרציית השער, חי במאגר הסודות של הפלטפורמה וזקוק רק להרשאת אימות, ולכן אינו ראיה שאדם מסוים אומת. אל תתנו ל-hook להפוך גם למאמת סיסמאות שני. שכתוב ידני של השוואת hash אינו הופך לבטוח על ידי אופטימיזציה של הלולאה, ומאמת מתוחזק כבר מבין את הסכמה המומלחת והיקרה של מאגר החשבונות.
החליטו מה משמעותה של תשובה שאינה ניתנת לסיווג
פסקי זמן, שגיאות תעבורה, תגובות פגומות ו-backend לא זמין חולקים תכונה אחת: אף אחד מהם אינו אימות חיובי. ה-hook צריך להיכשל סגור בכל אחד מהם ולתעד סיבה שמפעיל יכול לפעול לפיה. הסיבה הזו שייכת ללוג מוגן, לא להודעה שמוחזרת לאדם שמנסה להתחבר.
היו זהירים באותה מידה במסלולים שלעולם אינם מגיעים למאמת כלל. ערך תצורה חסר, סוד שאינו קריא או תלות שאינה מותקנת אמורים להפיק דחייה עם סמן תפעולי מובחן במקום קבלה מקרית. בדקו את הענפים האלה על ידי הסרת התלות במעבדה במקום לנמק עליהם. במקום שבו כשל בתיעוד יכול לשנות את סטטוס היציאה, התיעוד הפך לחלק מההחלטה וחייב להיעשות בלתי מזיק.
תצורת השרת חשובה גם כאן: סקריפטים מוגדרים על ידי המשתמש מכובדים רק כאשר התצורה הרצה מאפשרת אותם ברמת האבטחה הנדרשת, והגדרה זו היא בחירה מכוונת ולא ברירת מחדל שיש להעלות.
אמתו את הפסיקה, לא את השורה המודפסת
בנו בדיקה שבוחנת את סטטוס היציאה ישירות. כתבו קובץ אישורים לדוגמה, הריצו את ה-hook עליו עם חשבון ידוע כטוב וחשבון ידוע כרע, וקראו את הסטטוס שהמעטפת מדווחת ולא את הטקסט שהסקריפט מדפיס. הבדיקה האחת הזו תופסת את רוב באגי החוזה לפני שלקוח בכלל מתחבר.
לאחר מכן הריצו את אותם מקרים דרך לקוח אמיתי מול מאזין שאינו ייצורי ואשרו מה השרת תיעד: התקבל, נדחה או נדחה למועד מאוחר. כסו חשבון מושבת, סיסמה שגויה, תגובת מאמת פגומה, פסק זמן ומאמת שאינו נגיש. אם מקרה כלשהו נוחת במצב שאינכם יכולים לתת לו שם, המתאם אינו גמור.
שמרו את המיפוי מתוצאת המאמת לסטטוס היציאה לצד הסקריפט במאגר, כדי שהמהנדס הבא יוכל לקרוא את החוזה בלי הנדסה לאחור של הקוד.