אבטחת API - Security by Design
אבטחת API היא לא “תוסף”. היא חלק מהארכיטקטורה: מודל איומים, הגנות שכבתיות, וניהול סיכונים. בפרק הזה נלמד איך להגן על שירותי FastAPI (וגם על שירותים בענן) בצורה סטנדרטית ופרקטית.
מטרה: להפוך API מ"עובד" ל"עמיד": פחות פריצות, פחות דליפות, פחות תקלות תפעוליות, והפתעות נעימות בריאיון עבודה.
1) מודל איומים קצר: מה התוקף מנסה לעשות?
- לגנוב נתונים (PII, מסמכים, תמונות, תוצאות מודל)
- להפעיל פעולות ללא הרשאה (מחיקה, שינוי, העלאות)
- לשבור זמינות (DoS, rate abuse, ייצור עלויות)
- לנצל משאבים יקרים (קריאות LLM, חיפוש וקטורי, GPU)
2) Top mistakes בפרויקטים של API
Broken Authorization
המערכת “מאמתת” משתמש, אבל לא בודקת שהוא מורשה למשאב (IDOR). זה הבאג הכי יקר.
דוגמה: GET /users/123 מחזיר נתונים גם אם אתה משתמש 999.
Secrets בקוד
מפתחות JWT, API keys, credentials - לא נשמרים ב-repo. בפרודקשן רק ENV/Secret Manager.
No rate limiting
endpoint יקר בלי הגנה = הפקת עלויות והפלת שירות. במיוחד בעולמות GenAI.
Input לא מוגבל
קבצים ענקיים, JSON עצום, טקסטים אינסופיים - תוקף יכול להפיל שרת גם בלי “פריצה”.
3) שכבות הגנה: Defense in Depth
שכבה אחת לא מספיקה. הפתרון הטוב הוא שילוב:
- App: ולידציה, הרשאות, לוגיקה
- Gateway/Proxy: TLS, rate limiting, request size limits
- DB/Storage: הרשאות מינימליות, הפרדת נתונים
- Observability: לוגים, alerts, audit trail
4) Authentication מול Authorization - איפה אנשים נופלים?
אימות (JWT) אומר “מי אתה”. הרשאות אומרות “מה מותר לך”. רוב הפריצות קורות כשמסתמכים על user_id מה-path או מה-body בלי להשוות לזהות מה-token.
# לא נכון: סומכים על user_id מהלקוח
@app.get("/users/{user_id}")
def get_user(user_id: int):
return db.get_user(user_id)
# נכון: מזהות מה-token + בדיקת הרשאה
@app.get("/me")
def me(user=Depends(get_current_user)):
return db.get_user(user.user_id)
5) Rate limiting ו-Protecting expensive endpoints
מנגנון בסיסי: “כמה בקשות פר משתמש ליחידת זמן”. אפשר ליישם ב-Redis או לשים ברמת gateway.
דוגמה רעיונית עם Redis (קונספט)
# key: rl:{user_id}:{minute_bucket}
# INCR + EXPIRE
# אם עבר threshold - מחזירים 429 Too Many Requests
בפרודקשן נהוג להשתמש ב-Gateway/WAF או ספרייה מוכחת, לא להמציא לבד.
6) Input Validation + Schema hardening
- Pydantic נותן סכימה קשיחה ומונע “שדות מפתיעים”.
- הגבלת אורכים: text, lists, מספרים.
- איסור שדות לא מוכרים: extra = "forbid".
from pydantic import BaseModel, ConfigDict, Field
class NoteIn(BaseModel):
model_config = ConfigDict(extra="forbid")
title: str = Field(min_length=1, max_length=120)
content: str = Field(max_length=10_000)
7) CORS, Cookies ו-CSRF - מה צריך לדעת
CORS הוא מנגנון דפדפן שמגן על המשתמש, לא על השרת. אם אתה משתמש ב-cookies לאימות, אתה חייב לחשוב גם על CSRF. אם אתה משתמש ב-Bearer token ב-header, CSRF פחות רלוונטי, אבל עדיין צריך CORS מדויק.
כלל פשוט
- API ל-SPA מודרני: לרוב Bearer tokens, CORS מגודר ל-origin שלך.
- Cookies: דרוש CSRF token + SameSite + Secure.
8) File uploads: אבטחת העלאות וקבצים
קבצים הם שטח התקפה קלאסי. חשוב להפריד: Object storage לקבצים ו-DB למטא-דאטה.
- הגבלת גודל העלאה (request size limit).
- בדיקת content-type וחתימה (מינימום).
- אחסון פרטי + Signed URLs להורדה.
- שמות קבצים לא מהלקוח - יצירת key פנימי.
9) Secrets management
סודות הם “דלת אחורית” אם דולפים. בפרודקשן: secrets ב-Secret Manager/ENV, הרשאות מינימליות, ורוטציה.
מה לא עושים
- מפתח JWT ב-git
- .env בתוך repo ציבורי
- אותו סוד לכל סביבה
מה כן עושים
- Secret per env (dev/stage/prod)
- Rotation תקופתי
- Audit מי ניגש לסוד
10) Logging, Audit trail, ופרטיות
- לא לוגים של tokens, סיסמאות, או מידע רגיש.
- Audit events: login, שינוי role, מחיקות, העלאות.
- Correlation id לבקשה (כדי לאתר תקלות).
11) Checklist קצר לפרודקשן
- HTTPS בלבד
- JWT/API keys עם TTL ורוטציה
- Authorization לכל משאב (אין IDOR)
- Rate limiting (במיוחד ל-LLM/RAG)
- Request size limits
- Secrets ב-Secret Manager
- Logging זהיר + audit trail
תרגול
שאלות בדיקה (12)
- למה CORS לא “מאבטח את השרת”?
- מה זה IDOR ולמה הוא נפוץ?
- מתי CSRF רלוונטי ומתי פחות?
- מה ההבדל בין 401, 403, 429?
- למה חשוב להגביל request size?
- למה endpoints של GenAI נחשבים “יקרים” מבחינת אבטחה?
- מה הסיכון בהחזקת secrets בקוד?
- איזה מידע לא כדאי להכניס ללוגים?
- מה היתרון ב-Signed URLs לקבצים?
- למה “extra=forbid” יכול להציל אותך?
- מה ההבדל בין rate limiting ב-app לבין gateway?
- תן 2 שכבות הגנה שונות שהן לא אותה שכבה.
משימת בית (Hardening ל-CRUD שלך)
מטרה: להפוך את ה-CRUD שלך לעמיד יותר.
- הוסף extra="forbid" לכל models.
- הוסף הגבלת אורך לתוכן (Field max_length).
- הגן על כל endpoint עם auth, וודא שאין IDOR (notes רק של המשתמש).
- הוסף rate limit ל-login ול-endpoint היקר ביותר.
- הוסף upload לקובץ: קובץ נשמר ב-storage (לתרגול אפשר “fake”), וב-DB נשמר רק URL.
בדיקת פתרון (Checklist)
- אין endpoints שמקבלים user_id מהלקוח כדי לקבוע זהות.
- כל משאב נבדק מול המשתמש המחובר (ownership או role).
- קלט מוגבל וסכימה קשיחה (Pydantic).
- קיימת הגנה על endpoints יקרים (rate limit).
- הפרדת binary data מה-DB (URL/metadata בלבד).
- אין secrets ב-repo.