10.12. CORS ו-CSRF

CORS ו-CSRF הם שני אמצעי ההגנה בצד הדפדפן שמצלמה הפונה לאינטרנט הפתוח זקוקה להם לצד HTTPS והתחברות. כל אחד דורש כמה שורות להגדרה. הסעיפים שלהלן מגדירים את המונח ומציגים את האינטגרציה עם microdot.

10.12.1. מה CORS עושה

Cross-Origin Resource Sharing (CORS) הוא מנגנון הדפדפן המאפשר לשרת להצטרף מרצון ולתת למקורות אחרים ספציפיים לקרוא את תגובותיו. מדיניות אותו-מקור (same-origin policy) ברירת המחדל של הדפדפן חוסמת קריאה זו: JavaScript ב-https://example.com לא יכול לקרוא תגובות מ-https://yard-cam.example.com, מכיוון שמארח שונה נחשב כמקור שונה. CORS הוא הדרך מצד השרת להעניק חריגים לעמיתים נבחרים.

אם לוח הבקרה מוגש מהמצלמה עצמה, כל בקשה היא אותו-מקור ו-CORS אינו עושה דבר. ההגדרה חשובה כאשר לוח הבקרה נמצא במקום אחר – URL ציבורי כמו https://app.example.com המדבר עם מצלמה ב-https://yard-cam.example.com:

from microdot.cors import CORS

cors = CORS(
    app,
    allowed_origins=['https://app.example.com'],
    allow_credentials=True,
    max_age=86400,
)

allowed_origins היא רשימת המקורות שמורשים לקרוא את תגובות המצלמה. המקור של לוח הבקרה ו-רק המקור של לוח הבקרה – לא * – כך שאתר צד שלישי לא יוכל לקרוא בטעות את תגובות המצלמה.

allow_credentials=True מאפשר לבקשות חוצות-מקור לכלול את עוגיית ההפעלה, וזה מה שלוח הבקרה צריך כדי להישאר מחובר מעבר לגבול מקור.

max_age=86400 אומר לדפדפן שהוא יכול לשמור במטמון את תוצאת ה-preflight למשך יום. דפדפנים שולחים בקשת OPTIONS נוספת לפני כל קריאה חוצת-מקור המשתמשת במתודות שאינן GET/HEAD/POST או שולחת כותרות מותאמות אישית; max_age חותך את התקורה הזו ל-preflight אחד ליום לכל מסלול.

10.12.2. מה CSRF עושה

Cross-Site Request Forgery (CSRF) הוא ההתקפה שבה דף זדוני גורם לדפדפן של המשתמש לשלוח בקשה מאומתת אל שרת מהימן. אפילו עם CORS קיים, <form> נסתר ב-evil.com המבצע POST אל https://yard-cam.example.com/config יגיע אל המצלמה, והדפדפן יצרף את עוגיית ההפעלה של המצלמה – עוגיות עוקבות אחר מארח היעד, לא אחר מקור הדף המבצע את הבקשה – ולכן המצלמה מעבדת בשמחה את ה-POST כאילו היה של הבעלים.

הגנת CSRF דוחה את הבקשות הללו. microdot.csrf.CSRF מוסיף middleware הבודק את כותרת ה-Sec-Fetch-Site בכל בקשה משנת-מצב ודוחה כל דבר שאינו מסומן same-origin (או מגיע ממקור המורשה ב-CORS):

from microdot.csrf import CSRF

CSRF(app, cors=cors)

העברת המופע cors מאפשרת ל-middleware להחיל פטור היסטורי על המקור המורשה של לוח הבקרה – המצלמה עדיין מקבלת בקשות POST מלוח הבקרה אף שהן חוצות-מקור.

Sec-Fetch-Site מוגדר על ידי דפדפנים מודרניים אוטומטית; המצלמה אינה צריכה לעשות דבר בצד הלקוח. עבור דפדפנים ישנים יותר שאינם שולחים את הכותרת, רשימת ההיתר של CORS היא בדיקת הגיבוי.

10.12.3. פטור ל-webhooks

אם המצלמה צריכה נקודת קצה של webhook כדי לקבל בקשות POST משירות ענן צד שלישי – callback מספק הארכיון, למשל – סמן את המסלול @csrf.exempt כך שה-middleware יאפשר לו לעבור. המטפל אחראי לאמת את הבקשה בדרך אחרת – בדרך כלל Hash-based Message Authentication Code (HMAC) על המטען, המחושב עם סוד שהמצלמה והצד השלישי חולקים, המוכיח שהבקשה הגיעה ממישהו שידע את הסוד. מצלמת החצר אינה מחזיקה אף אחד מאלה, אך הדקורטור קיים כשתזדקק לו.

10.12.4. קו הבסיס בן ארבע השורות

ברגע ש-HTTPS קיים, המחסנית המומלצת לכל פריסת מצלמה הפונה לאינטרנט היא:

Session(app, secret_key=SECRET,
        cookie_options={'http_only': True, 'secure': True})
login = Login()
cors = CORS(app, allowed_origins=[...], allow_credentials=True)
CSRF(app, cors=cors)

הפעלה והתחברות קודם בפרק, CORS ו-CSRF כאן, HTTPS מהנושא הקודם. ארבעת החלקים נערמים זה על זה ונשארים מחוץ לדרכו של כל מסלול.

המצלמה כעת בטוחה לפנות אל האינטרנט הפתוח – HTTPS, התחברות, CSRF, CORS.