הפניית מארח וירטואלי של Subversion לכתובת VPN משנה איזה אתר עונה לבקשה, לא למי מותר לשלוח אותה. בדיקת הגישה מתרחשת מאוחר יותר, לפי הכללים בקטע שהתאים לכתובת, ושם מארח אינו נכנס להחלטה הזו לעולם. אם ServerName היה השינוי היחיד שלכם, המאגר נגיש בדיוק כמו אתמול. יש לענות על שתי שאלות נפרדות בתצורה: איזה שקע קיבל את החיבור ואיזו זהות נשאה הבקשה.

מה ServerName באמת קובע

אחסון וירטואלי מבוסס שם משווה את כותרת Host לערכי ServerName ו-ServerAlias, בוחר מארח אחד ומאפשר לו לטפל בבקשה. החיבור עצמו התקבל קודם לכן בשקע Listen השייך לשרת, לא למארח. לכן שם שנראה פרטי אינו מציע הגנה: כל לקוח שמגיע לשקע יכול לשלוח את כותרת ה-Host הזו. השם הוא מפתח ניתוב, ומפתחות ניתוב נבחרים בידי הקורא.

אפשר לראות את המיפוי בלי לנחש. apachectl -S מדפיס כל מארח, את הפורט שלו ואת שמותיו, ומסמן איזה מהם ברירת המחדל עבור הכתובת.

RequireAll הוא המכל שהופך את שתי הבדיקות לחובה

Apache 2.4 עוטף את הוראות Require של קטע אחד ב-RequireAny מרומז, כך ששתי הוראות חשופות פירושן שאחת מהן מספיקה. הוסיפו את המכל והמשמעות מתהפכת:

<Location /svn/repo>
    AuthType Basic
    AuthName "Subversion"
    AuthBasicProvider file
    AuthUserFile /etc/apache2/svn.htpasswd
    <RequireAll>
        Require ip 10.8.0.0/24
        Require valid-user
    </RequireAll>
</Location>

RequireAll דורש את שני התנאים: כתובת מקור בתוך טווח המנהרה ומשתמש שהמאגר מקבל. זה מה שאמור להיות במשמעות VPN-בלבד עבור כתובת Subversion. התיעוד של mod_authz_core מתאר את כללי המכל. השאירו את הבלוק בתוך ה-Location הקיים של המאגר והשאירו במקומן את הוראות ה-DAV שכבר עובדות, כי הקטע הזה אינו מחליף לא את AuthType ולא את קובץ הסיסמאות ולא הרשאה ברמת נתיב.

בדיקת המקור מגיעה מ-mod_authz_host, לכן ודאו שהוא נטען לפני שתנפו כל דבר אחר: apachectl -M | grep authz_host.

הכתובת ש-Apache מעריך היא כתובת השקע

Require ip משווה לכתובת ש-Apache תיעד עבור החיבור, וזו אינה תמיד הכתובת שהלקוח חושב שיש לו.

כשה-VPN מנתב את כתובותיו בלי תרגום, Apache רואה את כתובת המנהרה ו-10.8.0.0/24 מתאים. כששער מתרגם, Apache רואה את השער והכלל אינו מתאים דבר בשקט; אותו דבר קורה מאחורי מאזן עומסים שמסיים TLS לפני המארח. למקרה הזה קיים mod_remoteip. RemoteIPHeader X-Forwarded-For יחד עם RemoteIPTrustedProxy עבור המאזן מחליף את הכתובת שתועדה, ורק אז יש לטווח במה להשוות.

לתת אמון בכותרת העברה שרירותית גרוע מאין כלל בכלל. כל לקוח יכול לשלוח X-Forwarded-For המכיל 10.8.0.9 ולספק בדיקת טווח שמעולם לא עוגנה בקפיצה מהימנה.

שכבו את המאזין ומדיניות המאגר

כריכה היא בקרה נפרדת ולעיתים הפשוטה יותר. Listen 10.8.0.1:443 קובע אילו שקעים קיימים, ובקשה אינה יכולה לבחור מארח בשקע שמעולם לא נפתח. המחיר הוא סדר האתחול: אם ממשק המנהרה כבוי כשמתחיל Apache, הכריכה נכשלת והשירות לא עולה. צמדו אותה לסדר היחידות, או כרכו לכתובת שקיימת בלי תלות במנהרה.

הרשאות נתיב נשארות ב-AuthzSVNAccessFile. חשבון תקף לאתר אינו אותה הרשאה כמו גישת כתיבה לנתיב פרויקט, וכלל הרשת אינו משנה זאת.

שימו לב למיזוג קטעים כשהבלוק נמצא בקובץ נכלל. Apache ממזג מכלי הרשאה בין קטעים מקוננים והקטע המתאים הספציפי ביותר יכול להחליף את הדרישה במקום להוסיף לה, כך שכלל על /svn עשוי לא להגיע ל-/svn/repo/trunk ברגע שקטע צר יותר מגדיר משלו.

הוכיחו את שתי הדחיות לפני שאתם קוראים לזה גמור

בדקו ארבע בקשות, לא אחת. בתוך ה-VPN עם חשבון תקף אתם אמורים לקבל את רשימת המאגר. בתוך ה-VPN עם סיסמה שגויה אתם אמורים לקבל 401 ולא רשימה. מחוץ ל-VPN עם פרטי התחברות תקפים אתם אמורים לקבל 403, ואותה בקשה ציבורית למארח חלופי חייבת להיכשל באותה צורה.

פקודה אחת לכל מקרה מספיקה: curl -sI -u user:pass https://svn.example.com/svn/repo/, ואחר כך קראו את שורת המצב. חזרו גם על המקרה העובד דרך הלקוח האמיתי, כי svn info מתרגל את שיטות ה-DAV שבהן ישתמשו קומיטים ועדכונים.

אם אחת מארבע התוצאות אינה תואמת למדיניות שרציתם, התצורה אינה גמורה, ושינוי שם המארח לא ישנה זאת.

מאמרים קשורים