על המגבלות של REST – בשירות מערכות ווב בנות-זמננו

כבר כתבתי על REST בבלוג: ב 2011 (בהרבה התלהבות) וגם ב 2016 (במתינות) – מה ששיקף תהליך אישי של התלהבות רבה מ REST, והתפכחות לאורך הזמן.

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

מעולה, לחלוטין הייתי שמח לראות יותר דיפ דייב בנושאים שסקרת פה

הובילו אותי לפוסט הנוכחי, ולהציג היבטים נוספים של תקשורת ו REST – והאופן שאני מציג אותם כיום.

על אלו מגבלות אנחנו מדברים? הרי REST הוא כמעט-מושלם, לא?

למי שלא זוכר / לא היה בתעשייה בשנות ה-2000 אזכיר בקצרה את ההיסטוריה:
בתחילת שנות ה-2000, כאשר התברר לתעשיית התוכנה שמערכות תוכנה הולכות ונעשות מורכבות יותר, ויותר – ולכן רבות מהן הופכות למערכות מבוזרות (יותר משרת אחד) – ומכאן צמח רעיון ה Service-Oriented-Architecture (בקיצור: SOA) לארגון של מערכות מבוזרות שכאלו. מייקרוסופט הציעה את פרוטוקול ה SOAP (לשעבר XML-RPC) כבסיס לתקשורת בסוג הארכיטקטורה הזו, מה שהתקבל במהרה ע"י יבמ, ושאר חברותיה.

החברות שהובילו אז את התעשייה (IBM, HPE, Microsoft, וכו') קידמו את רעיונות ה-SOA לכדי מציאות, ועבדו על Suite של פרוטוקולים בשם *-WS שיפתרו את כל הבעיות התקשורת והאינטגרציה של מערכות Enterprise (מה שהיה מעניין באותה תקופה).

סט הפרוטוקולים המרכיבים את *-WS

בחבילת הפרוטוקולים של *-WS היה כמעט הכל, לקחו בחשבון כמעט כל צורך של Enterprise Software, מלבד אחד חשוב מאוד: פשטות.

לא כולם אהבו את SOAP ו *-WS, אחד מהם היה רוי פילדינג אחד ממגדירי התקן של HTTP. בעיניו – HTTP היה "הפרוטוקול של האינטרנט" ועל אף ש HTTP נועד להגשת "דפי ווב", הוא חשב שפרוטוקול אפליקטיבי צריך להשתלב בצורה הרמונית (אולי "טבעית") עם HTTP וה Web Standards הקיימים, ולא להתעלם ולהחליף אותם. זו הייתה מן גישה מוקדמת של "קיומיות וובית" – שהייתה חדשה ומרעננת באותה התקופה. היום קוראים לזה "Web-native".

פילדינג פרסם באותה תקופה עבודת דוקטורט, שבה פרק מס' 5 (המפורסם!) הציג סגנון ארכיטקטוני בשם Representational State Transfer (בקיצור: REST) למערכות ווב, ארכיטקטורה שמשתלבת באופן מאוד טבעי עם ה World Wide Web ו HTTP.

הסגנןם הארכיטקטוני של REST הוא בעל התאמה מצוינת למערכות פורומים, או אתרי Wiki – אך למערכות ווב מורכבות של ימנו, היא לא באמת רלוונטית. אני בטוח שלכמה קוראים המילים הללו כואבות, ויש עדיין כאלו שמנסים ליישם ארכיטקטורת REST במערכות תוכנה מודרניות. אני אישית השתכנעתי לגמרי בחוסר-הרלוונטיות של REST כסגנון ארכיטקטוני לרוב מערכות הווב המורכבות והמודרניות.

תוצר לוואי של "ארכיטקטורת ה REST", כפי שהוגדרה בעבודת הדוקטורט של פילדינג, היה צורת התקשורת בין חלקי המערכת, מה שנקרא כיום RESTful APIs. כלומר: צורת תקשורת של העברת הודעות JSON (במקור: XML) על גבי HTTP תוך השתלבות הרמונית באופיו והתנהגותו של HTTP והווב. ל HTTP יש מנגנונים כגון Caches, ו resource naming (להלן ה URL) הנכונים גם לתקשורת אפליקטיבית – שאם משתמשים ב HTTP, לא צריך לפתח מחדש. העולם מלא בכלים (למשל: דפדפן, Firewalls, Postman, swagger) שעובדים כבר עם HTTP, והמבנים של HTTP (כלומר: URL, Status Codes, Methods, Headers, Cookies) – מוכרים היטב* בתעשייה, ומקלים על אימוץ הטכנולוגיה.


* האמירה ש"כולם מכירים HTTP" היא סופר-מקובלת בתעשייה, אבל פה אני צריך להשתעל קצת – ולהתוודות שהיא לא לגמרי נכונה. גם כיום, בעידן הידע, ה Geektime, ושנים לאחר ש RESTful APIs הפכו לסטנדרט הלא-מעורער של התעשייה – אני נתקל בחוסרים גדולים של מהנדסי תוכנה בהבנה בסיסית של פרוטוקול ה HTTP. נכון יותר לומר ש HTTP מתועד היטב ויש עליו אינסוף חומר ו References – ולכן הוא נגיש יותר מפרוטוקולים propritary אחרים.

חזרה לתחילת שנות ה-2000: חברות האינטרנט שאז היו עוד צעירות: אמזון, יאהו, גוגל ועוד – אימצו בהתלהבות את REST (או ליתר דיוק: את ה RESTFul APIs) ושינו את מהלך ההיסטוריה. SOA, עם כל התכנון ההנדסי העמוק שנעשה מסביבו, ולמרות הגיבוי של חברות הענק – נקבר בעמודי ההיסטוריה, ו REST הפך לדרך התקשורת הנפוצה והמקובלת במערכות ווב בעשור וחצי האחרונים.

להיכן ממשיכים מכאן?

REST איננו תקן (אלא פרק בעבודת דוקטורט) והפרשנות ל RESTFul APIs אף יותר מזה – היא פרשנות, שכל אחד יכול לקחת למקום שונה. עם הזמן צצה הביקורת על "גנבי-הדעת" שפשוט מעבירים XML על גבי HTTP וקוראים למה שעשו "RESTful API" – מבלי באמת לכבד את פרוטוקול ה HTTP ו/או להשתלב בעקרונות הווב המקובלים (resources, openess, linking). מודל הביקורת שתפס יותר מכל להערכת "יישום RESTful APIs" הוא Richardson's Maturity Model (בקיצור RMM):

הפסים שאתם רואים בשכבה הנמוכה הם בוץ, רפש – שדובקים בכל מי שמסתפק ב "XML/JSON over HTTP" ועוד קורא לזה REST.
גם אני, כשהייתי עוד REST Evangelist – כעסתי על כל מי שנוהג כך. מה, אין כבוד?

ע"פ מודל ה RMM, לשלוח XML או JSON על גבי HTTP הוא נקודות האפס הבסיסית ביותר (והמוערכת הכי פחות). משם אפשר להתקדם לעיצוב "נכון" של URLs, שימוש "נכון" ב HTTP Verbs, ועד הגביע הקדוש של Hypermedia transformations. השתלבות המערכת עם "טבע ה HTTP והווב".

גם בחיים האמיתיים, אפשר לחתור להרמוניה מלאה עם הטבע: להפסיק להשתמש בתוצרי התעשייה, לא לזהם – ולחיות רק ממה שהטבע הפראי מספק. בעוד שיש הרבה אידאל בגישה הזו – לא בטוח שזו צורת החיים האופטימלית לכולם. אולי אני רוצה סכין שיוצרה במפעל, שיקל על מלאכת ההישרדות? אולי בית וחומרי בידוד? כמה רחוק "נכון" ללכת עם רעיון ההשתלבות בטבע?

שאלה דומה היא שאלת ההשתלבות ב HTTP/Web ומידת ההצמדות לרעיון ה REST: יישום של RESTful APIs ברמה 3 לפי ה RMM אולי תעניק לכם חיים בעולם הבא בקרב "קהילת נאמני ה REST" – אך כנראה גם תתברר כעבודה הנדסית בינונית עד איומה, ברוב המקרים. למשל: רמה 3 טוענת שכל קריאה לשרת צריכה לבוא מתוך "גלישה טבעית בווב". באתר ווב אני מתחיל מאתר הבית – ובסדרה של קישורים מגיע לדף בו אני מעוניין. באופן דומה, הטענה היא ששרתים שמדברים זה מזה – צריכים להתקדם למטרה בצעדים. לא לשאול: "כמה כסף יש בחשבון של לקוח X?" אלא תמיד יש להתחיל בשאלה "מי אתה? אלו לקוחות יש לך? איזה מידע יש לכל לקוח? האא… ורק אז לשאול: אז כמה כסף יש בחשבון של לקוח X?".

אם יש לנו שרתים "קרובים" (אותה מערכת) שמתקשרים הרבה, ההבדל בין 4 קריאות (3 "נימוסים" + 1 תכל'ס) על כל בקשה מול קריאה בודדת (רק תכל'ס) – היא משמעותית. רק במקום עבודה אחד בחיים ראיתי יישום של Hypermedia transformations – ואולי עשינו HATEOAS, אבל סיבכנו מאוד את המערכת. ממש ברמת העוול / הטיפשות.

מכאן עולה השאלה המתבקשת: "כמה RESTFulness" כדאי לעשות? ואיך יודעים לאמוד מתי ההצמדות ל RESTfulness היא תורמת, ומתי היא כבר מזיקה?

אז מהן המגבלות העיקריות של RESTFul APIs?

אני יכול להמשיך לדבר/לכתוב על הנושא הזה על בלי דיי. עסקתי בו – מאות שעות לאורך שנים, ודנתי ודשתי בו עם אנשים שונים וגישות שונות. בכל זאת, אשתדל לקצר.

Hypermedia Transformations / HATEOAS – ברוב המקרים, מבחינה הנדסית – זה פשוט טירוף לשמו. יש להימנע – וחבל להמשיך ולעסוק בעניין.

HTTP Verbs הם שפה מגוונת ועשירה למערכות פורומים / Wikis – אך דלה ומוגבלת עבור מערכות ווב מודרניות ומורכבות. הנה ההגדרות מתוך וויקיפדיה:

והנה כמה מגבלות יום-יומיות ,שכנראה שכולנו נתקלים בהן:

  • קריאת GET איננה כוללת Body. כאשר אנחנו רוצים לעשות קריאה שהיא safe (לא משנה את state המערכת), אך רוצים להעביר פרטים מגוונים של הבקשה – אנחנו חסומים לשימוש ב URL לבדו. זו נשמעת מגבלה קטנה, כי על URL אפשר גם לשים 6,000 ויותר תווים בדפדפנים של ימנו – אבל זה לא כ"כ מעשי:
    • Query String הוא נחמד, וקל להשתמש בו בהפשטה של מילון (<Map<String, String) – אל זו עדיין צורה מגבילה להעברת מידע. אין היררכיה, וקשה לתאר מבנים מורכבים יותר. בעיה.
    • על זה נוסיף את הצורך ב URL encoding, כי יש תווים שאסור להציב על ה URL. למשל: רווח (להלן ה %20 שאתם רואים ב URLs). מהר מאוד ה URL הופך ללא קריא, ולכן פחות שימושי.
    • אפשר לקודד JSON (מבנה גמיש והיררכי) על ה URL – אבל זה מקרה קיצוני יותר מהאייטם הקודם. זה כבר לא פשוט, לא שקוף, ולא Web native. פספסנו לגמרי את הרעיון של שימוש ב HTTP בגלל "קריאות ופשטות". כבר כבר יותר RPC.
    • אותה בעיה – קיימת גם עבור פעולות Delete (לכאורה Body הוא optional – אבל כלים רבים יחסמו אותו).
    • הפתרון הנפוץ הוא לבצע פעולות POST עבור קריאות שהיו אמורות להיות GET. ב POST אפשר להעביר body – אבל בעצם השימוש ב POST התרחקנו מההקפדה על הסמנטיקה של HTTP – שזה כל העניין. לא ניתן להבחין כבר בין POST שהוא safe לכזה שלא, ללא מידע נוסף. ראיתי מגוון גישות להתמודד עם החריגה הזו (POST as GET) – כל גישה, וה tradeoffs שלה.
  • חוסר בהירות בין POST ל PUT
    • פעולת PUT ב HTTP משמשת להחלפה של משאב שנמצא ב URL בתוכן הנשלח ב Body, וזו פעולה Idempotent – ניתן לבצע את אותה הפעולה מספר פעמים, מבלי שהתוצאה תהיה שונה מביצוע יחיד של הפעולה.
    • פעולת POST ב HTTP מעדכנת / שולחת מידע למשאב שנמצא ב URL – שזו הגדרה כללית הרבה יותר. הפעולה בהגדרה איננה Idempotent (כלומר: חלק מהפעמים היא עשויה להיות – אך אסור להסתמך על זה).
    • מכאן ועד תרגום של ה Verbs של PUT ו POST ל Create/Update/פעולה אחרת – יש מגוון של פרשנויות, לפעמים פרשנויות סותרות המתבססות על דקויות נכונות מתוך התקן של HTTP. במערכת Wiki, קביעת גבולות ברורים בין PUT ל POST היא דבר קל – במערכת מוצר מורכבת ומסועפת, תקן ה HTTP משאיר אותנו "חשופים" לדילמות, פרשנויות, והתלבטויות.
    • אם אתם לא מכירים את הדיונים הרבים, הנה מאמר תמציתי אחד שמסביר מדוע POST ו PUT הם לא פשוט Create ו Update.
  • בלי להכביר במילים, חוסר בהירות דומה קיימת גם בין PUT ו PATCH. הנה מאמר של המחבר הנ"ל לגבי הדילמה הזו. יש גם מאמר על PUT vs. PATCH vs. JSON-PATCH, ועוד אחד, ועוד אחד, ועוד אחד – כל אחד עם טענה מעט שונה.
הנה פרשנות אחת (ומקובלת) מתי להשתמש ב PUT ומתי ב POST ליצירת אובייקט. יש עוד כמה פרשנויות סבירות והגיוניות.
  • אולי המגבלה הכי קשה באימוץ "REST על מלא-מלא" בהיבט ה Verbs הוא הדלות ואי-העדכניות של ה Verbs המקוריים של HTTP לצרכים ועושר-הפעולות של מערכות תוכנה מודרניות. REST גרס שלא צריך להמציא "שפה חדש" – ואפשר להשתמש ב HTTP, אבל בשפה הזו יש רק 9 פעלים (עוד כ 20 נרשמו כהצעות) – אך אנו זקוקים ל 300 פעלים בכדי לקיים שיחה אינטליגנטית בדומיין העסקי שלנו? דומיין שהוא הרבה יותר מ CRUD?
    • אולי עבור מערכת וויקי, 9 פעלים מספיקים: עמוד יכול להיווצר / להיכתב מחדש (PUT), אפשר לכתוב עליו הערות (POST), ניתן לעשות בו תיקונים קטנים (PATCH) ואפשר למחוק אותו (DELETE) – הנה הסתדרנו!
    • במערכת של חברת ביטוח (למשל), בה על פוליסת ביטוח ניתן לבצע מגוון של פעולות:
      • לחתום על הפוליסה
      • להתחיל את הפוליסה
      • להקפיא / להפשיר את הפוליסה.
      • להוסיף מוטבים.
      • להוריד עותק של המסמך
      • לשלוח את המסמך לצד שלישי
      • לשלוח אישורים על המסמך לצד שלישי
      • לעדכן פרטים של בעל הפוליסה
      • לעדכן פרטים לגבי תוכן הפוליסה
      • לשנות כיסויים בפוליסה
      • לבדוק את הכיסויים בפוליסה
      • לבדוק סימולציה של מקרה – האם מכוסה ע"י הפוליסה
      • לבצע ביקורת על הפוליסה (Audit)
      • ועוד פעולות רבות…
      • הפוליסה מתוארת בצורה RESTFul למהדרין, כ URL כמו customers/{customer_id}/policies/{policy_id}/ אבל אין לי סיכוי לתאר את מגוון הפעולות הנ"ל בעזרת POST/PUT/PATCH בלבד. כל הרעיון ש "נראה את ה verb בלוגים השונים – ונבין מה התרחש" – מאבד ערך אם עבור כל הפעולות הנ"ל אני משתמש ב POST verb (פתרון מקובל אחד). יתרה מכך: אני צריך להשלים את הסמנטיקה שמבדילה בין הפעולות השונות איפשהו (על ה URL? על ה body? כ Header? כ Query Param) – אין מקום מוגדר וברור לעשות זאת. מגוון אפשרויות, עם tradeoffs וחריגות שונות מתקן ה HTTP והכוונות מאחוריו.
  • גם ברמת ה URI, אנו נתקל במגבלות: תיאור של URL היררכי לכל אובייקט במערכת – הוא לא דבר חד-משמעי ופשוט כפי שהיה במערכות פשוטות פעם (שוב: וויקי/מערכת פורומים). אקצר מאוד כדי לא להאריך את הפוסט אבל בעיקר:
    • לא תמיד יש היררכיה בין אובייקטים. לפעמים יש אובייקטים שהם שכנים "Siblings" – ואין דרך טובה לתאר את הקשר בהיררכיה (על ה Path).
    • לפעמים פרטי הזהות של אובייקט הם מורכבים / ארוכים מדי בכדי להיכנס ל URL.
    • ב REST/HTTP אין סמנטיקה לתאר Bulk operation – פעולה על מגוון אובייקטים. ישנן workarounds אך הם שונים ומגוונים, ולכל אחד – המגבלות שלו.
  • ב REST/HTTP אין דרך מוסכמת לתאר גרסאות ועתיות של APIs. באופן אישי אני משוכנע שהשתתפתי בעשרות שעות של דיונים בנושאים הללו, בארגונים שונים ועם אנשים שונים – כי אין תשובה פשוטה של REST שניתן לגשת ולהציג אותה. הנה סדרה של גישות ודיונים בנושא, למי שרוצה לצלול… הנה גם תקציר על 4 גישות מקובלות.
מישהו ריכז כאן דוגמאות וגישות ל versioning ב REST (והיד עוד נטויה). רק להדגים שהנושא אינו "סגור בטון" ומקובל על כולם באותו האופן.
  • עוד "שפה" שהייתה טובה / עשירה בזמנה – אך היום היא מוגבלת מדי היא ה HTTP Status codes. קודים כגון OK 200 או 401 (הקוד נקרא "Unauthorized" אך הכוונה היא בעצם ל Unauthenticated) – טובים ומתאימים לשכבת התשתית, אך מזמן כבר דלים מכדי לתאר את שכבת האפליקציה. להזכיר: HTTP הוגדר עבור ניהול דפי וויב ("documents" או "resources") ולא לתיאור של אפליקציות עסקיות מורכבות. למשל:
    • האם 404 מתאר endpoint שלא קיים, אובייקט שלא נמצא, או פריט מידע לפעולה שלא נמצא? איך מבדילים בין המקרים אם חזר רק הקוד "404"? דיון. לא פעם מפתח אחר מתכוון לדבר אחד, ומפתח אחר – מבין דבר אחר.
    • עוד דוגמה קלאסית היא המשמעות של 400 (הקרוי Bad Request).
      • ע"פ התקן – השרת לא יכול או לא יטפל בבקשה מכיוון שלא הוגשה בקשה נכונה מצד הלקוח (פורמט שגוי, שליחה ליעד הלא נכון)
      • השימוש הנפוץ הוא הרחבה של הקונספט "משהו לא טוב בבקשה" (ולא רק בפורמט/יעד) למשל: נשלח id שלא קיים של אובייקט. אבל…
        • אם נשלחו בבקשה כמה Ids – אז איזה מהם?
        • או אולי, אי אפשר לבצע את הפעולה מכיוון שעבר התאריך.
        • או אולי, לא ניתן לעשות פעולה בסכום נכון, כי החשבון מוקפא, או כי אלו פרטים אחרים אינם תואמים?
      • בקיצור: שום קוד סביר לא בצד הלקוח לא יכול להסתמך על קוד HTTP 400 להחלטה משמעותית של שינוי ב Flow ללא פרטים נוספים. נדרשים קודים מדויקים למגוון גדול של תקלות אפשריות, אז מה החשיבות להחזיר גם קוד HTTP 400? ראיתי שנים אנשים שהקפידו ותיקנו שוב ושוב קוד שהחזיר HTTP 500 להחזיר HTTP 400 – אך מעולם לא נתקלתי בערך שצץ מהפעולה הזו… פשוט עבודה לחינם.
  • עוד נושאים שאפשר לציין ש REST/HTTP לא נותנים להם מענה:
    • תקשורת אסינכרונית
    • Notifications – התקשורת של HTTP היא בקשה/תשובה. אם השרת המרוחק רוצה להודיע לי משהו ביוזמתו – כיצד הוא יעשה זאת?

אלו צרכים בסיסיים, שכנראה רוב מערכות הווב של ימנו זקוקות להם.
חשוב לציין ש HTTP/2 הוא כבר פרוטוקול בינארי, עם יכולות notifications, ואסינכרוניות (Pipeline/streams) – אך עדיין לא נתקלתי ב RESTful APIs שמשתלב ביכולות הללו בצורה הרמונית וטבעית.

סיכום

חזרנו והתבוננו ב REST בראייה מפוכחת, לא רק "REST זה טוב, SOA זה רע" – פזמון הפאנק של אמצע שנות ה-2000.

אני לא ממליץ לאף אחד לחזור ל *-WS או SOAP חלילה, הם כבר עתיקים ולא רלוונטיים. ייתכן ו RESTFul APIs זו הגדרה מספיק טובה לסט מצומצם של Public APIs של חברה (והרי ב Public API יש יתרון גדול בשימוש בתמות מוכרות), אך דיי ברור שלצורכי התקשורת הפנימיים של מערכת מורכבת / מיקרו-שירותים, RESTFul APIs היא גישה מוגבלת מדי ודלה בסמנטיקה – שחייבת הרחבה.

REST מעולם לא היה תקן, אף אחד לא מבאר אותו ומשפר אותו, ולא מתאים אותו לרוח התקופה. הוא הוצג בתחילת שנות ה 2000 – ועולם התוכנה השתנה מאז, ומאוד. אין תלונות.

לא ענינו על התשובה לשאלה: "כמה RESTFulness כדאי לעשות? ואיך יודעים לאמוד מתי ההצמדות ל RESTfulness היא תורמת, ומתי היא כבר מזיקה?" – אל אני מניח שכל ארגון תוכנה בימנו ש"מתבסס על REST" צריך לשאול את עצמו את השאלה הזו – ולהמציא תשובות.

יש ניסיונות כנים ומשמעותיים לנסות ו"לתקן" את REST (בצד התקשורת; צד הארכיטקטורה הוא מאוד ספציפי ומתאים רק למיעוט שבמיעוט שבמערכות) למשל JSON API או להיצמד ל Guidelines של מישהו שעשה עבודה משמעותית, למשל ה Microsoft RESTFul API Guidelines. פעם מישהו הציע לי לאמץ בארגון את JSON API ושללתי את זה על הסף ("כל ארגון יגדיר את מה שמתאים לו"). היום הייתי מתייחס לאופציה הזו ברצינות גדולה יותר. לא כ "תורה מסיני שתפתור הכל", אלא כדי לא להתחיל את תהליך ההגדרה הארגוני מאפס.

לא נכון לכעוס או להתאכזב מ RESTFul APIs כרעיון כללי כ"כ, לא מוגדר היטב, ופשטני. הפשטות הזו – פתרה בעיה גדולה לארגונים בשלב הצמיחה (גוגל, יאהו, ועשרות-אלפי סטאראט-אפים נוספים) ושירתה אותם היטב. הכלליות – התאימה ל"כולם" (כל אחד מצא שם משהו שדיבר אליו, בלי משהו גדול להתנגד אליו) ויצרה "רעיון" שמשך אחריו את כלל התעשייה – גם אם לא היה מדובר בתקן מדויק. רעיונות ה Web-Native חלחלו עמוק, והיום מהנדסים רבים כל-כך חותרים להשתלב עם הווב, במקום "להחליף את הקיים".

תנועות כגון Forget HTTP + הרצאה או HTTP Hell-no אולי מביעות הרבה תסכול מה"אכזבה" של REST בפועל – אך מנסות להציע גם חלופות ותיקון.

טכניקות כגון Polling, Long-Polling ו Webhooks הן דוגמאות פשוטות להרחבות טכניות פשוטות – המאפשרות עוד הרבה מרחב תמרון במקומות ש HTTP/REST מוגבל בהם ("client קורא ל server", וזהו). יש לי הסברים מפורטים על הטכניקות הללו – אניח לרגע שכל הקוראים מבינים אותן. כן גם משמרות את האופי של HTTP/REST כמעט ולחלוטין – זו רק הרחבה.

הפתיחות לאמץ אלמנטים של RPC בצורת העבודה של הארגון (להתגמש ב Resource modeling, להוסיף קודים של הארגון לכל תשובה, ו/או מבנה סטנדרטי של בקשה/תשובה לכלל המערכת) עשויים לשרת אתכם היטב, גם אם "קהילת נאמני ה REST" – ידיחו אתכם מחברות בארגון / חיי נצח / ההבטחה ל 70 מהנדסות תוכנה (טובות!) בעולם הבא.

(אם מישהו ישאל, JSON-RPC הוא פשטני ונאיבי מדי לטעמי, וגם אותו תאלצו מהר מאוד להרחיב ולהגדיר בעצמכם. RCP בינארי כמו Thrift או gRPC הוא אטקרטיבי לשיפור ביצועים / אמינות התקשורת – אך מהלך כבד מדי אם כל מה שאתם מחפשים הוא סמנטיקה וכללי עבודה טובים לבעיות מידול ה Endpoints במערכת שאינה very high scale, הפשטות הרי חשובה).

בקיצור: אני חושב שנכון להודות על מה שיש (RESTFul APIS, HTTP, או מה השאתם משתמשים) – אבל לא להניח שזה מספיק כדי ליצור תשתית תקשורת מספיקה לצורכי המערכת והארגון – בעיקר ככל שהמערכת מתפתחת והארגון גדל. הניחו שיהיה עליכם להגדיר שפה וכללים משלכם – שישרתו אתכם, וזה בסדר.

אם אתם עובדים ברמה של HTTP בצורה ניכרת (או ב RESTFul APIs או ב RPC כלשהו, למשל home-made) – חשוב ללמוד את התקן הפשוט והחשוב הזה, ולהבין אותו. אני רוצה להאמין שזה מאמץ קטן ומשתלם. יש את הפוסטים שלי (HTTP Basics + מבנה ה URL) אלו ואלו, יש את הרפרנס הנחמד הזה של for-get-HTTP – HTTP References שמצאתי במהלך כתיבת הפוסט – ונראה לי שאשתמש בו עוד בעתיד.

מחשבות, הערות, תוספות – יתקבלו בברכה!

REST ו RESTful APIs – גרסת 2016

איי שם בשנת 2011 כתבתי פוסט על REST ו RESTful APIs. פוסט שנצפה דיי הרבה פעמים, וגם עורר שאלות ותגובות. לפני כשבוע שאל אותי עוד מישהו על אחת הדוגמאות שנתתי. דוגמה כיצד לא-לכתוב REST Endpoint.

האמת? לא ידעתי להסביר בצורה חזקה מדוע זו דוגמה שגויה.
יכולתי כמובן לחרטט, אבל הלכתי קראתי את הפוסטים – והחלטתי שאני רוצה לחדש אותם.

האם רעיונות ה REST השתנו מאז בצורה משמעותית?בכל זאת – חמש שנים…
הפוסט הזה הוא שונה – אך בעיקר בגלל התפיסה שלי על הנושא – שהשתנתה.

הניסיונות למסד את רעיונות ה REST שהזכרתי בפוסט המקורי – פרוטוקולי GData ו OData (של גוגל ומייקרוסופט, בהתאמה) – לא ממש תפסו. לניסיון הנוכחי, שזוכה לתשומת לב בימים אלו, JSON API – אני מעריך שיהיה גורל דומה: השפעה מוגבלת באזורים מסוימים בתעשייה. רעיון יפה שיהיו כאלו שישתמשו בו בהצלחה – אך הוא לא יסחוף את כלל התעשייה כמו REST.

רעיונות ה REST באמת גרמו לסחף אדיר בתעשיה, ושינו בתוך שנים ספורות את האופן בו אנו מגדירים APIs בין מערכות / תתי-מערכות.

מה כ"כ חזק ברעיונות ה REST שגרם לשינוי האדיר הזה?
מדוע פרוטוקולים "נבונים" יותר ו"מעודכנים" יותר, כמו GData, OData, או JSON API – לא זוכים לכזו תהודה?
מה סוד הקסם של REST?

– אני אקח סיכון קטן, ואומר שסוד הקסם של REST שהוא פשוט ופתוח לאלתור.
בכלל, אנשים רבים לא מבינים אותו ממש, והם קוראים לאלתור שלהם "REST". בפרוטוקולים הדוקים יותר (כמו OData) – התרגיל הזה עובד פחות טוב.

כלומר: אפשר לומר שמרבית התעשייה עובדת עם "פסדו-REST", ורק חלקים קטנים יותר עובדים עם "REST תקני" או אפילו "REST תקני חלקית".
אם נשווה את מספר המשתמשים ב "REST תקני (חלקית)" – סדרי הגודל של המשתמשים דומים כנראה למספרי השמתמשים בפרוטוקולים חלופיים, קרי JSON API, OData, GData, או Thrift (שפותח במקור ע"י פייסבוק).

בעוד הפוסט המקורי מ-2011 נועד להוות "קריאת השכמה" ולהראות כיצד ליישם "REST תקני"… בפוסט הזה אני אנקוט גישה מעט שונה. בכל זאת, מתבגרים… 🙂

מה הרעיון ב REST?

הרעיון, ביישום המקובל שלו, הוא שבמקום ש"נמציא" פרוטוקול תקשורת חדש לווב, נשתמש בפרוטוקול של הווב – הרי הוא פרוטוקול ה HTTP. פרוטוקול אמין, מוכר, ומוכח ב Scales אדירים.

בגישה זו, יש מספר לא מבוטל של יתרונות:

  • ל HTTP יש המון ספריות, לכל שפת תכנות בערך, והרבה אנשים מכירים אותו לעומק. (אם אתם לא מכירים – זה זמן טוב לקורא את הפוסט בנושא!)
  • אם נפעל בהתאם ל HTTP, נוכל להינות בהשקעה קטנה מצידנו מ Caching שרכיבי הרשת השונים כבר מספקים (Proxies, gateways, CDNs, וכו').
  • רכיבי אבטחת רשת מקובלים (IDS או WAF) מכירים HTTP. הנה לנו פרוטוקול שכבר יש לו תמיכה בלא-מעט כלי אבטחה.
  • HTTP הוא פשוט ויעיל. מתחת למכסה המנוע הוא פשוט כמה headers שנשלחים לפני ההודעה על גבי חיבור ה TCP.
  • HTTP הוא stateless (ברובו הגדול), וה Scalability שלו היא מוכחת! (ה world wide web, דאא)
  • HTTP קל לניטור והבנה (יש הרבה כלים + הוא מבוסס clear text).

ע"פ ההגדרות הפורמאליות, REST אינו בהכרח קשור ל HTTP. הוא מבסס עקרונות (או Constraints) של ה API של המערכת, שדומים מאוד לאלו של רשת האינטרנט:

  • תקשורת שרת-לקוח
  • ה API הוא Stateless
  • ניתן לבצע Cache לבקשות
  • ממשק אחיד, לכלל המערכת:
    • מבוסס מיפוי של משאבים (כלל מנחה: שמם מורכב משמות-עצם)
    • על כל המשאבים ניתן לבצע סט מוגדר של פעולות: קריאה, כתיבה, עדכון, וכו'. (דומה מאוד ל HTTP GET, HTTP POST, וכו').
    • ה API מתאר את עצמו – ולקוח ה API משתמש בתיאור זה בצורה "גנרית" על מנת להשתמש ב API.

כיצד הסתירה הזו מתיישבת? נסביר בהמשך.

היסטוריה: כיצד נולד ה REST?

רעיונות ה REST הופיעו בפעם הראשונה בעבודת הדוקטורט של Roy T. Fielding, חבר בצוות שהגדיר את פרוטוקול ה HTTP וכן co-author של ה Apache Web Server.

עבודת הדוקטורט של פידלינג עסקה באפיון של רעיון שנקרא "סגנונות ארכיטקטוניים", מן דפוסים מרכזיים וחוזרים בארכיטקטורות של תוכנות שונות. בפרק מספר 5 בעבודה הוא תיאר סגנון ארכיטקטוני בשם (Representational State Transfer (REST – אימוץ העקרונות מאחורי ה world wide web, על מנת לתקשר בין שרתים.

העבודה של פידלינג כנראה נקראה ע"י מספר לא מבוטל של אנשים – הוא היה בחור מוכר בתעשייה. בכל זאת, ההשפעה שלה לא ניכרה בצורה ברורה – והיא שכבה לה איי שם במדפי האינטרנט (או ספריית האוניברסיטה) במשך עוד כמה שנים.

כמה שנים לאחר מכן, ב 2003 עד 2005 בערך – העולם נכנס לסערה של Service Oriented Architecture (בקיצור: SOA) והרעיון של Web Services (רשימת תקנים בשם *-WS).
אפשר להשוות את ההשפעה של SOA לאופנה הנוכחית של Microservices. לי, כמפתח צעיר, הרעיונות נשמעו מלהיבים וגדולים – ובחברה שעבדתי בה באותה התקופה החליטו "לא לפגר מאחור – ולאמץ SOA עד הסוף". כמובן שלי ולחברי לא היה מושג בדיוק כיצד להוציא מ SOA את המיטב – ופשוט מימשנו SOA ו Web Services.

Web-Services היו הפשטה גבוהה ועשירה/מסובכת מעל הפרימיטיביים של הווב. היה שם XMLs גדולים ומורכבים, שנעטפו בתוך XML נוסף (ה envelope) שהיה חלק מפרוטוקול התקשורת – SOAP, ותוארו ע"י פורמט שנקרא WSDL והופץ על גבי פרוטוקול בשם UDDI ואז היה ניתן להשתמש בכלי בשם DISCO על מנת למצוא אותם. וזה היה רק הבסיס.

הוגדר שם עולם חדש ושונה מאוד מהווב שאנו מכירים. הכל בשכבות הפשטה רבות, כאשר כל concerns – מבודד משאר ה concerns האחרים. ריכוז של הרבה רעיונות בהנדסת תוכנה.

קהילה הולכת וגוברת של אנשי תוכנה החלו לזעוק: "הגזמתם!", "זו הנדסת יתר", "זה לא יעיל מבחינת ביצועים", "ולא – לא נחכה עוד שנתיים שהאינטרנט יהיה כ"כ מהיא שלא יהיה אכפת"…
הקולות הללו חיפשו אלטרנטיבה להתאחד מאחוריה. משהו פשוט, מהיר לכתיבה, ויעיל מבחינת הביצועים.

ב 2004 ה W3C (ארגון התקינה של האינטרנט) הוציא מסמך שדן בארכיטקטורות אינטרנט. לקראת סוף המסמך, הופיעה פסקה קצרה המרחיבה את ההגדרה של Web Services: היא גם ציינה את REST כבסיס התיאורטי לפעולת ה Web Services, וגם ציין שהם יכולים להיות בלתי-תלויים בפרוטוקולים כמו WSDL או SOAP.

יריית הפתיחה נורתה, ושני מחנות החלו להיווצר:

  • אלו התומכים ב Web Services ע"פ סט תקני ה *-WS.
  • אלו התמוכים ב RESTful Web Services, כלומר: עלינו רק לתאום לכללי ה REST – וזהו Web Service בר-תוקף, בהכשרת ה W3C!
על עצם הצורך בהגדרה של Web Services, או של SOA – אף אחד לא ערער. זה היה באזז חזק שלא ניתן להתכחש לו.
היום כמעט כבר אין ממש זכר ל *-WS, ומעט מאוד מ SOA. הקונצנזוס הגדול התעשייה הוא סביב REST – כאשר עדיין יש הרבה חוסר הבנה מסביב לעקרונות הללו.

קצת עזרה מידידנו ריצארדסון

את חוסר ההבנה ב REST, ניסה קצת להבהיר / לסדר בחור בשם לאונרד ריצרדסון.

ריצרדסון הוא המחבר של שני ספרים מוכרים (הוצאת O'Reilly):

  • RESTful Web Services – 2007
  • RESTful Web APIs – 2013

בהרצאה בכנס QCON-2008, ריצרדסון הציג מודל בגרות של אימוץ רעיונות ה REST:

מקור: http://www.slideshare.net/josdirksen/rest-from-get-to-hateoas

רמה 0 – הביצה של POX (קיצור של Plain Old XML. פורמט ה XML היה בזמנו יותר פופולרי מ JSON)

רמה זו מתארת שימוש ב HTTP בכדי להעביר קבצי JSON או XML, נניח תמיד במתודת HTTP POST. זה יישום פשוט, Freestyle (שזה סוד הקסם שלו) – אך זה לא REST.

זה כמובן לא מפריע לקרוא לו REST – ויש אנשים שבאמת מתייחסים לכל פורמט סטנדרטי (נניח JSON) שעובר ל HTTP – כ REST. לא נבוא איתם בחשבון 🙂

רמה 1 – מיפוי משאבים

ברמה זו אנו מקפידים על תיאור של ה URIs כך שיתארו משאבים. עבור הווב של משתמשי הקצה משאבים הם מסמכים: html, js, או css. עבור API – משאבים הם דומים יותר לאובייקטים, או חלקים בתוך האובייקטים.

בעוד שיכולנו לתאר API של מערכת לניהול כנסים כ:

  • POST /create_session
  • POST /set-session-title
מיפוי של משאבים יתייחס לקריאות הללו בצורה בה מתייחסים למשאבים:
  • POST /sessions/create
  • POST /sessions//title
מכיוון שה title שייך ל session, אנו מרכיבים את ה URI בצורה היררכית – כך ש session קודם (ולכן "מכיל") את ה title. כלומר: כל "/" מתאר בהכרח רמה היררכית של מבנה המשאבים.

מיפוי המשאבים מציב כל מיני דילמות, שאין להן תשובה חד משמעית:
  • כאשר אין קשר היררכיי ברור בין ה resources  – מה הסדר הנכון?
    • למשל: הזמנת-נסיעה ונהג – מי מכיל את מי? (בהנחה שכל אחד מהם הוא אובייקט שחי בפני עצמו. הזמנת-נסיעה נוצרת בלי קשר להשמה לנהג).
  • רזולוציה: האם למפות כל תכונה של אובייקט (למשל: title) לresource שיש לו URI משלו, או פשוט להסתפק ב sessions/update/ עבור כל התכונות יחדיו (בהתאם לפרמטרים שנשלחים בבקשה)?
  • כיצד לתאר גרסאות שונות של ה API? כחלק מה URI או כפרמטר?
  • ועוד…
רמה 2 – שימוש ב HTTP Verbs (ו Status Codes)
שימוש ב Method (נקראים גם Verbs) של פרוטוקול HTTP בכדי לתאר את הסמנטיקה של הפעולה:
  • GET – על מנת לבקש את ה state של המשאב אליו מפנה ה URI.
  • POST – על מנת לשלוח content ("נתונים"/"הודעה") למשאב לצורך עיבוד, עיבוד שייתכן וישנה את ה state של המשאב. לעתים הודעה זו תיצור תת-משאב (ואז נהוג להחזיר Status Code של 201 – "נוצר").
  • PUT – על מנת להחליף (rebind – "רישום מחדש") של התוכן של המשאב.
  • PATCH – על מנת לבצע עדכון חלקי של ה state של המשאב.
  • DELETE – על מנת למחוק את המשאב.
ניתן כמובן להשתמש גם במתודות האחרות של פרוטוקול ה HTTP,קרי HEAD, OPTIONS, TRACE – אולם הגדרת ההתנהגות שלהן עבור REST API היא פחות ברורה ויותר פתוחה לפרשנות.

כלל חשוב נוסף הוא שאין חובה לתמוך בכל 4 הפעולות הבסיסיות על כל משאב. ייתכן משאב שעליו ניתן לבצע רק GET ומשאב אחר שעליו אפשר לבצע רק POST. מצד שני הגדרת ה URI מחייבת שכל צומת מתאר משאב שניתן לגשת אליו. לדוגמה, אם הגדרתי משאב כ:

אזי גם ה URIs הבאים צריכים להיות ברי-קיום:
יתרון גדול בשימוש ב HTTP Verbs היא "קבלת Caching" בעלות מזערית. רכיבי רשת רבים: שרתי ווב, CDNs, רכיבי פרוקסי ו Gateway – מודעים ומכבדים את ה Verbs הללו. אם אנו עושים הרבה GET (ובעיקר: מוסיפים גם headers נכונים) – התשובה תהיה cached ב"רשת". כאשר נבצע POST לאותו משאב – ה caches יתנקו.
יתרון נוסף הוא שרכיבי אבטחת רשת IDS, WAF, ועוד – מודעים לבקשות הללו ול HTTP error codes – יודעים על פיהן להסיק ולזהות התנהגות חריגה / מסוכנת ברשת שלנו.
מה שכן דיי מעצבן הוא שלעתים רכיבים האבטחה מניחים שתשובות עם סטטוס 404 הם שגיאה ברורה – ונדירה מטבעה, אבל יכול להיות APIs שמחזירים 404 בצורה דיי תדירה (נניח: 10% מהפעמים) – ואז צריך או להרגיע (= לכוונן) את רכיב האבטחה או להימנע משימוש בקוד 404 ב API שלנו…
ההתנהגויות המוגדרות של HTTP verbs השונים. מקור: Lynda.com
עוד מידע על התנהגות ה HTTP הצפויה מה verbs השונים, ועל שימוש ב Status Codes – ניתן למצוא בפוסט על פרוטוקול ה HTTP.
כפי שתיארו את זה ריצרדסון ורובי בספר, הכוונה היא שפרוטוקול ה HTTP (או פרוטוקול אחר, רעיונות ה REST לא מתייחסים בהכרח ל HTTP) ישמש באותה המידה משתמשים אנושיים, ומכונות (צרכני API) באותה המידה ו(כמעט) באותו האופן:
"…Reunite the programmable web with the human Web… a World Wide Web that runs on one set of servers, uses one set of protocols, and obeys one set of design principles"
 — Richardson & Ruby, RESTful web services, 2007.
 
 
רמה 3 – Hypermedia Controls
שמות נרדפים: Hypermedia transformations או Hypermedia As the Engine Of Application State (בקיצור: HATEOAS)
רעיון זה טוען שעל פעולה ב REST למשאב צריכה לתאר את פעולות ההמשך האפשריות כ links לפעולות הללו.
כשאנו גולשים באינטרנט, אנו הולכים למשאב מרכזי, למשל: https://www.bankhapoalim.co.il – ומשם אנו משתמשים בקישורים על מנת לבצע את שאר הפעולות: כניסה לחשבון, ביצוע העברה בנקאית, שליחת טופס לנציג הבנקאי האישי שביצענו טעות בהעברה הבנקאית (!!) – וכו'…
באופן דומה, הרעיון ב REST הוא שהתוכנה בצד השני (ה Client) לא "תדע" אלו פעולות זמינות, אלא תלמד אותן מהתשובה. למשל:
התשובה שלנו לכל קריאה, מכילה גם links שהם תיאור של הפעולות האפשריות. self למשל היא הפעולה הנוכחית, כאשר אני גם יכול לבצע product.search – ויש בידי את ה URI המתאים לביצוע התחזוקה.
עוד השלכה חשובה של ה HATEOAS היא שכל פנייה לשרת צריכה לפתוח בקריאת GET (או OPTIONS – לדקדקנים), ורק אחריה קריאת POST או PUT וכו'. יש מערכות שמסתמכות על ההנחה הזו.
משתמש אנושי יכול באמת להבין מה המשמעות של "product.search", אבל מה מכונה מבינה מזה? ישנן שתי אופציות:
  • המכונה מכירה את כל ערכי ה rel האפשריים – ולכן יודעת לפעול על פיהם.
  • המכונה לא מבינה.
הפער הזה נקרא ה Semantic Gap, עניין שאנשי ה Semantic Web (מספרם קצת הצטמצם בשנים האחרונות, לא?) – מנסים לפתור.
ישנם פרוטוקולים כמו XMDP או ALPS לתיאור סמנטיקה של פעולות API. מן שפות של meta descriptions.

מי שממשיך ומתקדם מעל רמה 3 – ראוי להיכנס להיכל התהילה של REST, כמי שיישם את מודל ה REST בצורתו הטהורה ביותר!

ספר על REST? – יש עשרות רבות…

מודל ה REST בפועל

האם זה הגיוני ליישם את שלב 3 במודל של ריצ'רדסון, Hypermedia Controls?
אם אתם מפתחים מערכת ווב טיפוסית – ברור שלא!
אם הגעתם ליישום של שלב 3 במערכת שכזו, כנראה שנכשלתם: נכשלתם ליישם מערכת פשוטה, שניתן לפתח במהירות.

פידלינג טען בתוקף, שכל יישום של פחות מרמה 3 – איננו ראוי להיקרא REST, הנה הטקסט. הוא כנראה צודק. אנו משתמשים בתעשייה ב REST בעיקר באופן שונה ממה שהוא תכנן אותו. בצורה הרבה יותר מינימליסטית ופשוטה.
דווקא ריצרדסון שהגדיר את מודל ה maturity ציין בראיונות מאוחרים יותר שהוא ניסה לספק שלבים כיצד לאמץ REST בהדרגה, ומבחינתו עצירה ברמות 1 או 2 – הן גם טובות.

לדעתי האישית, אין טעם ברמה 1 ללא רמה 2: מיפוי משאבים ללא שימוש ב Verbs הוא אנמי וחסר. הייתי ממליץ על רמה 2 או 0, עם הסתייגויות.

ההסתייגויות הן שכללים רבים של "REST טהור" עשויים להיות פשוט לא יעילים ליישום במערכת שלכם. הם יכולים לגזול זמן מיותר – ולספק מעט תועלת.

הנה כמה כללים שאנחנו נוהגים להתעלם מהם במערכת הקיימת שלנו (בידיעה):

  • הפער הגדול ביותר: אנו משתמשים בפעולות POST גם לצורך קריאות שלא משנות את ה state של האובייקט. פשוט כמו GET שמכיל payload גדול יותר, אפילו אם לא מתבצע processing מורכב מאחורי הקלעים.
    • זה אומר ששברנו כמה כללים של ה HTTP: חלק מקריאות ה POST הן עכשיו cacheable ו idempotent.
    • מצד שני: שרשור של פרמטרים ה URL זה פחות קל, ופחות קריא. הכי מעצבן היה לגלות שוב ושוב חסימות לאורך ה URL ברכיבי הרשת השונים: פעם ב WAF, פעם nginx – ופעם ב Load Balancer. אמנם אין הגבלה בתקן ה HTTP עצמו, אבל כנראה שבעקבות הדפדפנים שנהגו להגביל את אורך ה URL – נראה שגם רכיבי הרשת אמצו, כברירת מחדל, את המסורת…
  • לא כל חלק של URI הוא בר-תוקף. ייתכן וישנו URI בשם {companies/{id}/roles/{role_id/ מבלי שיש ל companies/{id}/roles/ כל תוקף. למה לנו לפתח API שאף אחד לא הולך לקרוא לו?
  • אנחנו משלבים שמות של פעולות, ולא רק משאבים ב URI. אנחנו משלבים אותן בסוף, כמעט-תמיד. למשל:
    areas/by_point ו areas/by_rectangle.
  • אם פעולה יצרה משאב חדש, אנו לא מחזירים header של location עם ה URI למשאב שנוצר. הרבה יותר פשוט להחזיר id של האובייקט החדש בתוך ה JSON של התשובה.
  • ויש כנראה עוד…
קצת פרספקטיבה: כללי ה REST הם לא עבודה שצמחה מתוך עשייה של API, אלו לא כללים שהתפתחו וגדלו עם הזמן.
כללי ה REST הופיעו בדוקטורט (של בחור מאוד חכם, שמבין HTTP יותר טוב מכולנו) בשנת 2000 – ומאז לא השתנו. רק הפרשנויות אליהם השתנו מעט עם הזמן.

כמו שאני נוהג להטיף, גם כאן אני מציע: השתמשו בכל כלי בצורה שהכי מועילה לכם, ולא ע"פ כלל שרשום איפשהו.

"לשבור את הכללים" ללא כל שיטה, זה גם לא דבר נכון. סדר הוא חשוב, במיוחד ככל שהמערכת גדלה ומסתבכת. לעתים נכון לשמור על הסדר הכללי, גם כאשר הוא לא נכון מאזורים נקודתיים במערכת.

אנו כן מקפידים ב REST לשמור על הכללים הבסיסיים:

  • למדל בצורה סמנטית (קרי – ברורה מפתח) ובחשיבה על resources במבנה היררכי את URIs שאנו משתמשים בהם.
  • להקפיד על שימוש ב Verbs של HTTP ועל Status Codes בסמנטיקה שהוגדרה להם. אנו לא משתמשים ב Verbs ו Status Codes שאינם חלק מתקן ה HTTP (על אף שתקן ה HTTP עצמו – מותיר זאת).
  • להיות "Good Citizens", ולהתאים את התנהגות ה APIs שלנו לכללי ה HTTP במידת האפשר.
הנה במצגת הזו, תוכלו למצוא בין היתר דוגמאות לשימוש "לא תואם ל REST" ב APIs של חברות מוכרות.
הייתי נמנע מרוב ה"שגיאות" שמוצגות, אך כנראה גם הייתי סולח ל.. עד מאמץ כמה מהן.
המצגת היא מ 2012, וחלק מה APIs שמוזכרים השתנו מאז, או אפילו לא קיימים יותר.

שיהיה בהצלחה!

קישורים רלוונטיים

הבלוג של מרטין פאוולר, על Richardson Maturity Model ו REST:
http://martinfowler.com/articles/richardsonMaturityModel.html

מצגת נחמדה שמתארת כמה מהבעיות שעלולות לצוץ ביישום REST (הרשאות, גרסאות שונות של API, טיפול בשגיאות, וכו'):
http://www.slideshare.net/MasoudKalali/masoudkalalirest-new-format

HTTPS חלק ג': שיקולים ליישום באפליקציה

בפוסטים הקודמים עסקנו בסקירה כללית על HTTPS ובחינה עמוקה יחסית של מנגנון אימות הזהות. בחלק זה, אחרון בסדרה, ארכז כמה נושאים שלא כיסיתי בפוסטים הקודמים – אך עדיין נראים לי חשובים ורלוונטיים.

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

Default Port שונה

בניגוד ל HTTP בו פורט (port) ברירת המחדל הוא 80, ב HTTPS, פורט ברירת המחדל הוא 443. כלומר: אם ב URL של HTTPS לא צויין port – אזי מדובר בפורט 443.

Overhead

כפי שציינתי בפוסט הראשון בסדרה, ל TLS ,בסה"כ, אין תקורה משמעותית:

  • כ 6% יותר נתונים עוברים ברשת (Headers נוספים וחתימה דיגיטלית מוסיפים, TLS דוחס את ה HTTP headers – מה שקצת חוסך).
  • שרת מודרני, משקיע כ 2.5ms זמן CPU לצורך TLS handshake של מפתח א-סימטרי ארוך (2K) – לא זמן משמעותי. בעבר היה מקובל להוציא את משימת ה TLS handshake לחומרה חיצונית (Load Balancer או CDN) שעשתה עבודה זו בעלות נמוכה יותר – אך נראה שמגמה זו פוחתת.
  • עבודת ה CPU בצד הלקוח היא לא משמעותית, אולי מלבד מכשירי מובייל (?!)
הנקודה היחידה בה יש הבדל ביצועים משמעותי בין HTTP ל HTTPS הוא יצירת ה connection ההתחלתי.

בפוסט הקודם הסברתי על מנגנון שנקרא Session Ticket שפוטר מהצורך שלנו לבצע TLS handshake לכל connection, כך שבסך הכל אנו יכולים לצפות ל 2 roundtrips נוספים לכל origin איתו אנו יוצרים קשר.

האמנם?

מקור: הבלוג של איליה גריגוריק. שווה לעקוב.

פעמים רבות תתקלו ב TLS שהעלות שלו גדולה מ"התאוריה".

הנה, בתרשים למעלה, רואים גישה לאותו המשאב ב HTTP (שורה עליונה) מול HTTPS (שורה תחתונה), בהינתן RTT של כ 380ms.
ע"פ התאוריה, ניתן לצפון לעלות נוספת של 760ms (פעמיים 380ms) בביצוע TLS handshake, אבל הנה בדוגמה למעלה, ההפרש בפועל הוא כמעט 2000ms, כמעט פי 3 (!) ממה שציפינו.

בפוסט מעניין ויפה איליה גריגוריק מראה כיצד הוא מנתח ומתקן את המצב המתואר לעיל. הוא מזהה, ומתקן, "באפר" (congestion window) שקונפג בשרת בצורה שלא מטיבה עם ה TLS Handshake ואז הוא מוסיף עוד אופטימיזציה של TLS בכדי לצמצם את התקורה ל roundtrip בודד. סה"כ 2000ms הופכים ל 380ms – שיפור מרשים מאוד!
לא צריך להכיר את nginx (השרת המדובר) בכדי לקרוא את הפוסט.

קריאות HTTPS שלוקחות זמן ארוך מהצפוי – הוא לא דבר נדיר, ולא קל לנתח למה זה קרה. במיוחד אם השרת המדובר הוא לא בבעלותכם. פתרון פשוט יחסית הוא להעביר את המשאב לשרת שלכם (במידה וזה אפשרי) – שם כבר טיפלתם בביצועי ה HTTPS. אפשרות אחרת היא לספוג את המחיר ולהשקיע את המאמצים בלגרום למשאב זה לא להיות בנתיב הקריטי של רנדור הדף.

עוד גורם שיכול לעכב קריאות HTTPS הוא הפעלה של פרוטוקול בשם Online Certificate Status Protocol (בקיצור: OCSP). זהו פרוטוקול המגביר את אמינות וידוא הזהות של HTTPS – במחיר ביצועים. ע"פ הפרוטוקול, כאשר הדפדפן מקבל Certificate הוא פונה ל CA שהנפיק אותו ומבקש לוודא שה Certificate עדיין תקף. סיבה ש Certificate לא יהיה תקף הוא אם המפתח הפרטי שהיה בשימוש ליצירת ה Certificate נגנב איכשהו, או שהתגלה שבעל ה Certificate הוא מתחזה / פועל בצורה לא כשרה. בחלק ב' של הפוסט שעסק ב Certificates לא כיסיתי את נושא ביטול (revocation) של Certificates – מפאת חוסר מקום / עניין נמוך לקורא.
אם המשתמשים שלכם מפעילים OCSP – אתם יכולים לבדוק את ה CA שלכם, עד כמה הוא מהיר בתגובות שלו לבקשת OCSP באזורים הגאוגרפים הרלוונטיים עבורכם. במידת הצורך, ניתן להחליף CA באחד זמין / מהיר יותר.

שווה לציין ששירותי (CDN (Content Delivery Networks, יכולים לקצר את זמני ה TLS handshake. ל CDNs יש תחנות הקרובות גאוגרפית למשתמש הקצה (ולכן יש להן latency נמוך יותר). משתמש הקצה מבצע TLS handshake מול התחנה הקרובה, של ה CDN, בה כל round-trip הוא זול יותר. מרגע שנוצר ה connection התחנה משתמשת כפרוקסי (מאובטח) לשרת שלכם (על גבי connection מאובטח שנפתח מבעוד מועד) או סתם מעבירה לכם TLS Session Ticket שאתו תוכלו לעבוד. ייתכן ובכלל התוכן זמין על cache של ה CDN ואין טעם להגיע בכלל לשרת היעד.
שירות זה נקרא TLS Early Termination, כאשר הכוונה במילה Termination היא "קיצור המסלול" ולא "סיום התקשורת".
שירות זה, עולה כסף – כמובן, ולא תמיד הוא זמין בכל אתר (או Point of Presence, בקיצור PoP) בה ה CDN פעיל.

משתמש הנמצא באירופה יכול לחסוך כחצי מזמן ה TLS handshake אם הוא מבצע אותו מול תחנה בחוף המזרחי, שרק לאחר מכן מעבירה את התעבורה לחוף המערבי (שם נמצא שרת היעד).

Connection Termination

מתי שהוא, סיימנו את העבודה המאובטחת מול השרת, ואנו רוצים לסיים את תקשורת ה HTTPS.
דרך מחשבה אחת אומרת כך: "HTTPS חי על גבי TCP, לכן פשוט אפשר לסיים את connection ה TCP בעזרת הודעת TCP FIN – ולסיים עניין".
אולם, הודעת TCP FIN יכולה בעצם להישלח ע"י כל אחד, גם תוקף פוטנציאלי המנסה לשבש את התקשורת. לצורך כך נקבע שתקשורת TLS תסתיים ע"י הודעה ברמת ה TLS (הדלקת flag בשם close_notify) ורק לאחר מכן TCP FIN.

לא לבלבל נושא זה עם Early Termination.

הימנעות מ Mixed Content

אם אתם זוכרים את הפוסט הקודם, נרצה להימנע באתר שלנו מ mixed content: תוכן HTTP בתוך דף HTTPS.
כיצד עושים זאת?
הרבה פעמים ראיתי שפשוט כותבים קוד דומה לזה:

var url = location.url.startsWith('https')) ? HTTPS_URL : HTTP_URL 

זה עובד, אבל זה מעט מסורבל. ניתן פשוט לכתוב url יחסי יחיד שלא מגדיר פרוטוקול – והוא ישתמש, בהגדרה, בפרוטוקול של הדף, יהיה זה HTTP או HTTPS. למשל:

var url = "//myserver/resources/style.css";

זה נראה קצת מוזר, אבל זה תקני לחלוטין. ניתן למצוא עוד פרטים על URL יחסי בפוסט שלי על URLs.
מעניין לציין ששירותים שונים (למשל Google Libraries API) מגישים לנוחיותכם את אותם המשאבים גם על גבי HTTP וגם על גבי HTTPS – וממליצים פשוט להשתמש בסכמה שתארתי.

כדאי לציין שחלק משירותי האירוח יציעו לינק HTTPS כברירת מחדל (כדי להבטיח שקיבלתם מהם אכן את הקובץ שביקשתם, ולא ממתחזה). HTTPS בתוך HTTP לא יוצר בעיית "Mixed Content" – אך הוא פוגע בביצועים של הדף (כפי שצוין למעלה בפוסט).

HTTPS כפרוטוקול "VPN"

למרות ש HTTPS הוא לא פרוטוקול VPN, יש לו יתרון שהוא "עובר מסך" אצל firewalls ורכיבי רשת שונים בקלות יחסית:
"אם הצלחת ליצור קשר על גבי TLS עם השרת שלי – אני סומך עליך. אפילו שאין לי מושג (או יכולת לדעת) מה אתם מדברים ביניכם" אומר ה Firewall או ה Transparent Proxy לעצמו.

לאחר שה TLS Connection נוצר הוא מוצפן – ולכן הוא שקול לפרוטוקול בינרי, low level, בין הצדדים. לדוגמה: ניתן להפוך את כיוון התקשורת, להעביר מידע בינרי, להעביר הודעות בחלקים לא מסודרים וכו'. סוג של "TCP על גבי HTTPS" (ה HTTPS משמש בעיקר ל handshake, אח"כ ניתן לרדת חזרה לרמת ה TLS היותר בסיסית).

תכונה זו של HTTPS בשימוש ע"י רכיבים של Content Delivery Networks, תקשורת בין Cloud למערכת ארגונית, ופרוטוקולים כגון SPDY (שהופך להיות HTTP 2.0) ו Web Sockets.

קצרים

  • האם זה נכון שהדפדפן לא עושה Caching לתוכן HTTPS?
    לא. לכו בדפדפן ל about:cache לראות בעצמכם. אפשר בעזרת HTTP Headers מתאימים להגביל את ה Cache ואת אופן השמירה שלו ע"י הדפדפן / ה CDN.
  • האם רכישת Certificate הוא דבר יקר? אלפי דולרים בשנה?
    תלוי בסוג ובמקור ה Certificate, אבל זה יכול להיות גם עשרות דולרים בשנה.
  • האם זה נכון ש HTTPS מונע Virtual Hosting (לארח כמה אתרים עם Hostnames שונים על אותו שרת פיסי)?
    לא. עקרונית יש בעייה, אבל פותרים אותה בעזרת הרחבה ל TLS בשם Server Name Indication (בקיצור SNI). אולי יש פתרונות נוספים.
  • האם HTTPS מבטיח פרטיות למשתמש הקצה?
    נו, אתם אמורים לענות על זה לבד בשלב זה: הוא מגן מפני sniffing ברשת, או בפני התחזות (phishing) – אבל אין לו קשר לאיזה מידע השרת שומר עליכם, כיצד הוא מגן על מידע זה או מה הוא עושה אותו (אולי הוא מפרסם אותו לכולם בגוגל על גבי HTTP …?)

סיכום

זה לא היה בדיוק פוסט סטנדרטי, אלא רשימת השלמות על נושא ה HTTPS מפוסטים קודמים.
מן הסתם לא כיסינו את כל מה שיש לומר על HTTPS, אבל אני חושב שכיסינו את רוב הנושאים הנפוצים – וסיפקנו מספיק מידע לבנות תמונה לא רעה על מהות הפרוטוקול והיתרונות / חסרונות ביישום שלו.

שיהיה בהצלחה!

—-

לינקים רלוונטים:

HTTPS – חלק ב': אימות זהות

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

תהליך ה Handshake

תקשורת HTTP מתבצעת על גבי TCP connection.
TCP Connection הוא של הסכמה הדדית בין 2 הצדדים כיצד לעבוד, כך שלא יהיה צריך להסכים עם כל הודעה מחדש. הסכמה על TCP connection נעשית בתהליך תלת-שלבי שנקרא "לחיצת ידיים משולשת" (three-way handshake). אני מניח שכל זה מוכר.

פרוטוקול TLS משתלב על גבי לחיצת הידיים של TCP ומוסיף את הבטי האימות וההצפנה. לצורף כך, TLS דורש עוד כארבע לחיצות ידיים בדרך. יש כמה אופטימיזציות שונות ללחיצת היד של TLS (אינני מכיר את הפרטים של רובן), והן יתרחשו אם התסריט מאפשר זאת ושני הצדדים מודעים לקיצור הדרך. בתרשים למעלה הצגתי פישוט דיי בסיסי בו ה Ack האחרון של "לחיצת-הידיים המשולשת" משמש גם להעברה של ההודעה הראשונה של ה TLS handshake, הרי הוא ה Client Hello.

הנה תקציר תהליך ה TLS handshake:

הודעה 1: Client Hello

ה Client מצהיר שהוא מוכן להתחיל בתהליך ה TLS handshake ושולח כמה פרטים: גרסאות ה TLS בהן הוא תומך, רשימה של הצפנות (סימטריות) בהן הוא תומך ועוד כמה פרטים טכניים.
כל התקשורת בשלב זה נעשית ב clear text, כלומר: ללא הצפנה.
הודעה 2: Server Hello + Certificate
השרת בוחר בגרסת TLS וההצפנה בהן הוא מעוניין לעבוד (בעצם: המקסימום שהוא תומך) ומצרף את המפתח הציבורי שלו עם Certificate – מספר שדות ("קובץ קצר") שמאמתים את זהותו שלו. מעין "צילום של תעודת הזהות שלו". הקובץ חתום ע"י חתימה דיגיטלית של גורם צד-שלישי מוכר. פרטים נוספים על Certificates – בהמשך הפוסט.
בשלב זה יכול השרת להחליט שהוא רוצה לאמת בעזרת HTTPS Authentication, את זהות המשתמש – ולבקש מה Client חזרה את ה Certificate שלו.
הודעה 3: העברת מפתח סימטרי
ה Client מאמת את ה Certificate של השרת (איך הוא עושה זאת – בהמשך) ומייצר מפתח סימטרי אקראי לצורך ה session הנוכחי עם השרת. אני מניח שמשימת יצירת המפתח הסימטרי ניתנה ל Client בכדי לשפר את ה scalability של השרת ("1000 מעבדים ממוצעים חזקים ממעבד-מפלצת של שרת אחד").
את המפתח הסימטרי, ה Client מצפין בעזרת המפתח הציבורי של השרת. רק בעל המפתח הפרטי המתאים – יוכל לפתוח את ההצפנה (בזמן סביר).
הודעה 4: שימוש במפתח הסימטרי לצורך אימות וסיום תהליך ה TLS handshake
בשלב זה השרת משתמש במפתח הפרטי שלו כדי לפענח את ההודעה הקודמת, לחלץ את המפתח הסימטרי – ולעבור להשתמש בו.
ההודעה הראשונה שנשלחת ל Client היא הודעת Finish, מוצפנת במפתח הסימטרי שה Client סיפק, הודעה שעוזרת לודא שתהליך העברת המפתח הסימטרי והשימוש בו ע"פ ההצפנה (chiper) הנכונה – הצליח.

נקודה משמעותית לשים לב אליה היא שאם ב HTTP יצירת connection הייתה תקורה של roundtrip אחד בין הדפדפן לשרת, ב HTTPS – התקורה ליצירת connection היא שלושה roundtrips. בתקשורת בין ישראל לארה"ב – מדובר על בערך שנייה (1000ms לפני שהעברנו ביט אחד של מידע בעל-ערך למשתמש).

Session Tickets
אם אתם זוכרים, הדפדפן פותח עד 6 או 8 connections במקביל מול כל שרת בכדי להאיץ את ההורדה.
כדי לא להגיע למצב בו משלמים את התקורה של TLS handshake מספר רב של פעמים, נוספה יכולת בשם Session Ticket (תקן: RFC 5077) המאפשרת ל connection שני, שלישי וכך הלאה, מול אותו השרת, לעשות שימוש חוזר ב: מפתח הסימטרי, אימות, הסכמה-על-הצפנות וכו'.
עם הודעת ה Finish, השרת שולח Session Ticket (מוצפן) ל Client. כל connection חדש יכול לצרף את ה ticket הזה בכדי "להצטרף" ל HTTPS session שמחזיק השרת – ולחסוך לעצמו תהליך handshake נוסף.

בניגוד ל HTTP שבברירת המחדל הוא stateless (מתנתק אחרי כל response), פרוטוקול HTTPS הוא תמיד statefull (במסגרת ה session).

לסיכום: התקורה הנוספת היא בהחלט משמעותית, אבל היא רלוונטית רק לטעינת הדף הראשונית. שרת מודרני (תקן ה Session Ticket הוא מ 2008) ידע לצמצם גם את התקורה הראשונית.

HTTPS Authentication

כפי שציינו, פרוטוקול TLS מספק הצפנה + חתימה דיגיטלית – יכולות שאני לא מוצא הרבה מה להרחיב עליהן. בואו נדבר מעט על Authentication.

TLS תמיד כולל אימות של זהות השרת, והשרת יכול לבקש לאמת גם את זהות משתמש הקצה. מנגנון אימות הזהות ( Authentication) למשתמש הקצה הוא תחליפי לשיטות אחרות כגון SAML 2.0 או OpenID.

מנגנון אימות הזהות של TLS מבוסס על שרשרת של אמון, באופן דומה למה שמתרחש בעולם היום-יומי:
כאשר אנו טסים לחו"ל, לא בודקים את זהותנו באופן פרטני – אלא סומכים על מסמך שהנפיקה המדינה שהוא הדרכון שלנו. את הדרכון לא הנפיק חוקר האבטחה הראשי של משרד הפנים באופן אישי – יש לו אמון במשרדים השונים של משרד הפנים הפועלים ברחבי המדינה.

כלומר: משטרת הגבולות (של מדינה זרה, אפילו), סומכת על משרד הפנים, שסומך על משרד כלשהו שבדק את זהותנו בצורה יסודית מספיק והנפיק לנו מסמך קשה-מספיק לזיוף.

TLS משתמש בסטנדרט שנקרא X.509 לתיאור Certificates – "תעודות" המאמתות את זהותו של אדם / שרת.
ישנם Server Certificates לשרתים וישנם Client Certificates לזיהוי משתמשים. בסה"כ אלו מנגנונים דומים.

X.509 מסתמך על היררכיה של Certification Authorities (בקיצור CA) – גופים (לרוב עסקיים) שמורשים להנפיק Certificates. ה CA המוכר ביותר הוא כנראה זה של חברת VeriSign, שחטיבת אימות הזהות שלה נמכרה לפני כשלוש שנים ל Symantec, אך היא עדיין פועלת תחת השם VeriSign.

VeriSign מאמתת זהות של ארגונים ומנפיקה Certificates לשרתים (ליתר דיוק: ל DNS Entries – השרת הוא רק ה"בחור שנמצא שם כרגע"). היא עושה זאת באופן ישיר וגם הרבה בעזרת ספקי משנה שהיא מסמיכה.

הנה הדרך בה תוכלו לבדוק את ה Certificate של אתר אליו אתם גולשים (דוגמה = כרום):

צעדים 1-4 מביאים אותי למסך בו אני רואה את שרשרת (או היררכית) האמון של בנק הפועלים: VeriSign מאמתת זהות של תת-שירות או מחלקה של VeriSign שמאמתת את בנק הפועלים.

VeriSign בהכרח לא תיתן לחברת סטארטאפ מאוקראינה אימות של Bank Haopalim ל DNS שכתובתו http://www.bankhaopalim.com (זו נראית כמו הכנה להתקפת Phishing! [א]) – צריך להיות קשר ישיר בין השם שנחתם לעסק, ולכתובת ה DNS שאותה מבקשים לחתום.

בפינה הימנית למטה צרפתי עוד דוגמה של Certificate של אתר בשם buy2usa (השייך לחברה buy2) שאומת ע"י CA אחר – במקרה הזה Go Daddy.

ייתכן ותשאלו את עצמכם: מדוע רק לחלק מהאתרים יש רקע ירוק יפה בשם (מסומן בריבוע ירוק בתמונה למעלה)? האם זהו style ב CSS?

ובכן… לא. אתרים שחשובה להם יותר "הפגנת" הזהות שלהם (בנקים זו דוגמה טובה) עוברים תהליך יותר מקיף שנקרא Extended Validation. תהליך אימות הזהות מול ה CA הוא מקיף (ויקר) יותר, אם כי ההצפנה היא אותה ההצפנה.
כלומר: אתם יכולים להיות בטוחים במידה גבוהה יותר של בטחון שמי שבקצה הוא מי שהוא טוען שהוא (כל עוד ההצפנה לא נפרצה).

הנה האופן בו דפדפנים שונים מציגים Extended Validation Certificates:

לכל העניין של חוויית המשתמש יש חשיבות גדולה ב Security – הרי בסוף, אנשים הם חוליה בשרשרת האבטחה. חוליה חלשה – בד"כ.

ספציפית לגבי Extended Validation Certificates (בקיצור: EVC), חוקרים ממייקרוסופט ואוניברסיטת סטנפורד ביצעו מחקר משותף ב 2007 שהראה שמשתמש ממוצע פשוט לא שם לב להדגשה של ה EVC ולא מתייחס אליה כמידע נוסף. מצד שני, אנשים שעברו הדרכה ולמדו לחפש אחרי ההדגשה של ה EVC הצליחו לשים לב אליה ולבצע החלטות טובות יותר בהימנעות מהתקפות phishing.

אז מהם בדיוק ה Certificates ואיך הם מבטיחים וידוא אמין של זהות?

  • Certificate הוא צירוף של Public Key (א-סימטרי), זיהוי הישות, וכמה שדות מידע אחרים.
  • Certificate נחתם בחתימה דיגיטלית, לפחות בעזרת המפתח הפרטי העצמי.
  • X.509 Certificate הוא תקן שמכיל כמה שדות סטנדרטיים ומאפשר הרחבה לשדות נוספים – ללא הגבלות מסוימות על אורך / מבנה המידע.
  • Certificates נקראים לעתים בקיצור certs, בעיקר בפורומים מקצועיים של אבטחה.

הנה דוגמה למבנה של Certificate:

מקור: וויקיפדיה

הערה: Certificates שמועברים ב HTTPS מקודדים לרוב בפורמט בינרי, ASN.1, כך שלא תראו אותם בפורמט של clear text, כמו זה למעלה.

  1. Issuer זו הסמכות שאימתה את זהות השרת/משתמש, ה CA.
  2. כל Certificate מגיע עם תאריך תפוגה – ולא יהיה תקין אם תאריך זה חלף.
  3. אלו פרטי הישות שאותה ה Certificate מזהה. הרשומה החשובה ביותר היא ה CN (קיצור של Common Name, ע"פ פרומט X.501, כזה המוכר לכם אולי מ LDAP) שצריכה להתאים ל DNS Entry, במידה ומדובר בשרת.
  4. זהו המפתח הציבורי של הישות המזוהה.
  5. זוהי חתימה דיגיטלית שנוצרה על בסיס MD5 (פונקציית גיבוב) של כל ההודעה – ונחתמה ע"י המפתח הפרטי של ה CA (או חותם אחר: חתימה עצמית או ארגון). כדי לפתוח אותה המשתמש זקוק למפתח הציבורי של ה CA.

הנה הצורה בה ה Certificate מוצג בממשק המשתמש של "חלונות":

הערה: certificate זה לא מתואם עם הדוגמה מוויקיפדיה – הוא חדש הרבה יותר

מהיכן מגיעים Server Certificates למחשב?

ציינו שעל מנת לאמת את החתימה הדיגיטלית של ה Certificate, עלינו לפתוח אותו בעזרת המפתח הציבורי של ה CA. מניין לנו המפתח הציבורי של ה CA? כיצד אנו מקבלים אותו בצורה בטוחה ולא "זיוף שלו"?

ובכן, כל הדפדפנים המודרניים מגיעים בהתקנה עם רשימה של Certificates של ה CAs המרכזיים.

במערכת ההפעלה חלונות יש Certificate Store, שזהו אכסון מוצפן של Certificates שמגיעים עם מערכת ההפעלה. כאשר אנו מתקינים או מעדכנים דפדפן – הוא מוסיף ומעדכן Certificates ל Certificate Store. זוהי החלטה של כל דפדפן – על אלו CAs לסמוך.

ניתן לצפות ב Certificate Store בעזרת הפעלה של certmgr.msc מה command line בחלונות.

Root CAs בד"כ חותמים דיגיטלית על עצמם – כלומר בעזרת ה public key שב Certificate ניתן לאמת את החתימה הדיגיטלית.

CAs משניים (Intermediate) חתומים ע"י Root CAs. זו היא בעצם שרשרת האמון עליה דיברנו בתחילת הפוסט.

נחזור לדוגמה שהצגנו מוקדם יותר:

במקרה זה Go Daddy Class 2 הוא ה Root CA שחתם על ה Certificate של עצמו וחתם על Go Daddy Secure – שזהו CA משני (אפילו ששייך לאותה החברה. אולי מדובר בסניף אחר או חטיבה אחרת, למשל).
Go Daddy Secure הוא זה שחתם לנו על buy2 ואימת את זהותו.

בכדי שהדפדפן יאשר את זהותו של buy2, לפחות Certificate אחד בשרשרת צריך להיות trusted (קרי נמצא ב Certs store שלנו), מאומת ותקין.
בהמשך הפוסט נראה מה קורה כאשר Certificate הוא לא תקין.

שרשרת האמון ב certificates. מקור: איליה גריגוריק

מעניין לציין של JVM של ג'אווה יש Certificate Store נפרד מזה של מערכת ההפעלה(המורכב מ2 חלקים הנראים truststore ו keystore). ייתכן ויש בו Certificates שונים מאשר בדפדפן. כלומר: אנו יכולים לאמת ישות דרך הדפדפן ולא בעזרת קוד ג'אווה – או להיפך.
אם אתם "חוטפים" שגיאות של javax.net.ssl.SSLException: untrusted server cert chain בעוד אין לבם בעיה להתחבר לאתר עם הדפדפן – יש סיכוי גבוה של JVM אין את ה certificates שיש לדפדפן.

נאמר לי שעורקל נתקלה בבעיה טכנית בגינה היא לא עדכנה certificates בגרסאות הג'אווה זמן מה – אולם לא ראיתי זאת בעצמי.

מהיכן מגיע Client Certificate למחשב?

Client Certificate הוא כזה שמזהה את המשתמש הספציפי, ומשמש לצורך Authentication למערכות שתמוכות בשיטה זו. מאיפה אם כן הוא מגיע למחשב שלכם? האם איי פעם נפגשתם עם סוכן של Go Daddy או VeriSign ונדרשתם להוכיח את זהותכם? – אני מניח שלא.

יש כמה דרכים לייצר Client Certificate:

דרך פשוטה אחת היא בעזרת תוכנה שעושה זאת, ואז אתם חותמים על ה Certificate בעצמכם. למשל הפעלה ב command line של:

openssl genrsa -des3 -out server.key 1024

תייצר מפתח פרטי בשם server.key ואז הפעלה של

openssl req -new -key server.key -out my_cert.csr


תייצר קובץ certificate על בסיס המפתח שנוצר קודם לכן.
את קובץ ה certificate יש לטעון לשרת בתהליך רישום כלשהו – כדי שיידע להכיר אתכם ולסמוך עליכם. מעתה בחיבור לשרת הזה, לא תצטרכו להקליד שם וססמה – פרוטוקול ה TLS יטפל בעניין.

דרך אחרת היא שהשרת מייצר certificate עבורכם וחותם עליו. אתם מורידים קובץ ועושים לו import לתוך ה Certificate Store.

דרך אחרונה מקובלת היא שארגון ה IT שלכם יוצר לכם certificate ומשתיל אותו בהתקנה / באיזה סקריפט שרץ בהפעלת המחשב.

כמובן שדרך זיהוי זו תקפה בעיקר לזיהוי (Authentication) פנים-ארגוני ולא למערכות אינטרנט, בהן כל תקשורת עוברת דרך תווך לא מאובטח / שקשה לדעת מה מקורו.

התנהגות הדפדפן וחיווי למשתמש

לא תמיד הכל הולך כמתוכנן ולפעמים אתר עובד עם HTTPS שנראה תקין… מלבד פרט אחד קטן. הדפדפנים מנסים לשקף למשתמש את מידת הסיכון ע"י חיווי מתאים בממשק המשתמש – חיווי שהם מנסים שיהיה אינטואיטיבי וברור גם למשתמש שלא מכיר את פרוטוקול TLS (קרי: כמעט כל המשתמשים).

עד כמה חיווים אלו ברורים? – תגידו אתם.

אני חושב כדאי להכיר אותם בתור משתמשים "מתקדמים" וגם כדי לענות ללקוח שנתקל בחיווי ופונה אליכם בשאלה "מה לא בסדר?"

לפני שהדפדפן שולח את client certificate לשרת – הוא מבקש את פרטי המשתמש בעזרת dialog (שעשוי להראות מעט שונה בין דפדפנים שונים):

חלון אישור לשליחת Client Certificate בכרום

הסיבה לבקשה המפורשת היא שה certificate יכול להכיל כמה פרטים אישיים (שם/שם משפחה, כתובת דואר אלקטרוני) שאיננו רוצים ששרת שהגענו אליו בטעות, ואיננו רוצים באמת להתחבר אליו – יקבל אותם.

ב IE ניתן בעזרת קונפיגורציה לבטל את ה dialog אם יש לנו Client Certificate יחיד מותקן, אולם ככל הידוע לי זהו הדפדפן היחיד שמאפשר זאת.

Mixed Content

ייתכן מצב בו דף HTTPS כולל רכיבים של HTTP. זו איננה בהכרח בעיה, אך זה אומר שהאתר שאנו מתייחסים אליו ברמת בטיחות של HTTPS – הוא לא כזה. התוכן שמגיע ב HTTP יכול להגיע משרת לא מאומת / יכול להיות שעבר שינויים בדרך.

אציין שיש חריגים בכלל של Mixed Content: תגיות img, video, audio ו object שלא כולל data attributes – לא נכללים בבדיקה. מאוד קשה לבצע התקפה עם תמונה – ויש מעט אתרים שמארחים תמונות על גבי HTTPS.

כלומר: אם אנו בונים אתר HTTPS אנו צריכים לוודא שכל ה scripts, css ועוד מגיעים גם ממקורות המאובטחים ע"י HTTPS. זה לא תמיד טריוויאלי.

אם זה לא המצב, הדפדפן יספק משוב למשתמש המזהיר אותו (בדמות משולש צהוב על המנעול, בדפדפן כרום – למשל) שיש איזו פשרה באבטחה.

ההזהרה, כפי שנראית בעת כתיבת הפוסט בכרום

IE, ולפני כחודשיים בערך החלו גם כרום ו FF לחסום כהתנהגות ברירת-מחדל את תוכן ה HTTP כאשר יש mixed content. כלומר: הדף נטען ללא רכיבי ה HTTP – מה שיכול לגרום לו לרוץ בצורה לא טובה / לא לרוץ בכלל.

למשתמש יש אפשרות אפשרות לבחור שהוא רוצה בכל זאת לטעון את תוכן ה HTTP – מכיוון שהוא מבין את הסיכון ולוקח אותו עליו.

הנה הדרך בה מוצג תוכן חסום והדרך לאפשר אותו בכרום:

והנה ב FF:

לפחות בכרום, אם אני מאפשר תוכן – הדפדפן יזכור את בחירתי ולא יחסום תוכן בעתיד – רק יראה הזהרה.
אם אתם רוצים לבדוק התנהגות של דפדפן אחר, הנה לינק (תקף בעת כתיבת הפוסט) עם תוכן מעורב.

Certificate לא תקין:

כאשר ה Certificate לא תקין, הדפדפן מציג זאת בצורה דיי בולטת:

בעצם, זה נראה דיי מפחיד – וזה יכול לקרות גם לאתר הבטוח שלכם. מספיק שללקוח שלכם אין Certificates מעודכנים של ה CA (כל מיני חברים שרצים על Win XP ו IE7) – וזה מה שהם יקבלו, גם אם האתר שלכם תקין לחלוטין. נדמה לי שפעם הדפדפן היה מגיב בצורה פחות אגרסיבית ל Certificate שהוא פג תוקף – אך זה לא המצב עכשיו.

הנה אתר בו תוכלו למצוא לינקים לדוגמה עם תקלות שונות ב Certificate שלהם: http://testssl.disig.sk/index.en.html

הנה שני אתרים שיכולים לעזור ולנתח מדוע יש תקלת Certificate:

סיכום

פו…. זו הייתה כתיבה ארוכה!
סקרנו את האופציה לבצע Authentication בעזרת HTTPS/TLS על בסיס Certificates וראינו בגדול כיצד המנגנון עובד.
נשארו עוד כמה נושאים שאני רוצה לדון בהם – על ההשלכות היותר קונקרטיות למפתח – אשמור חלק זה לפוסט המשך קצר.

שיהיה בהצלחה!

—-

[א] נשמע כמו "לדוג", אבל הכוונה היא להתחזות למקור אמין בכדי לגנוב פרטים או את זהות המשתמשים.

HTTPS – חלק א': סקירה

HTTPS הוא HTTP עם SSL (קיצור של Secure Sockets Layer). ה "S" ב HTTP משמעה "Secure".

עד לפני מספר שנים, HTTPS היה פרוטוקול שהיה נפוץ בעיקר במערכות Enterprise, ו/או אתרים משעממים אחרים.
דברים השתנו, ויותר ויותר אתרים משתמשים ב HTTPS בתור פרוטוקול ברירת המחדל שלהן: גוגל, טוויטר, פייסבוק ועוד. ע"פ סקר SSL Pulse (לינק עם נתונים גרפיים), בשנה האחרונה אחוז האתרים שעובדים ב HTTPS עלה מ 15%, ל 52%! ייתכן והסקר מעט מוטה – אך בהחלט יש כאן מגמה.

ישנן כמה סיבות שבגללן השינוי מתרחש:

  1. HTTPS הוא "תואם" ל HTTP ואינו דורש (בתיאוריה) שינויי קוד בכדי לעבור אליו.
  2. החומרה נעשית חזקה יותר ויותר, והתקורה של עבודה ב HTTPS היא כבר לא-משמעותית*.
  3. מודעות גדלה לפרטיות ואבטחה ברשת.
* מלבד מקרה חשוב אחד – עליו נדבר בהמשך.
  • האם המעבר ל HTTPS הוא "טוב ליהודים"?
  • מה HTTPS בעצם עושה? היכן הוא מגן והיכן – לא?
  • מה המשמעות, בשבילי המפתח, ובכלל – לעבוד עם HTTPS?

על שאלות אלו, ואחרות – אנסה לענות בפוסט זה.

Context: היכן פרוטוקול HTTPS "מתרחש"?

הבהרה / היסטוריה: פרוטוקול ה SSL (קיצור של Secure Sockets Layer) הוגדר ע"י חברת Netscape עבור הדפדפן Netscape Navigator – שאיננו קיים עוד. הוא היה בגרסאות 1.0 עד 3.0, ואז הוא הועבר לגוף תקינה סטנדרטי ושמו שונה ל TLS (קיצור של Transport Layer Security). בשנת 1999 שוחרר TLS גרסה 1.0, הגרסה העדכנית של TLS היא 1.2 (שוחררה בשנת 2008).
למרות שהשם "TLS" קיים כבר יותר מעשור, השם SSL דבק ועדיין מוכר יותר / משתמשים בו לעתים קרובות בהחלפה ל TLS. ישנו עוד ציוד רשת (בעיקר שרתים) שעדיין תומכים ב SSL או גרסאות ישנות של TLS – מה שמחייב את הדפדפנים לעבוד בגרסה ישנה יותר (ופחות מאובטחת) של הפרוטוקול.

בסך הכל, TLS הוא פרוטוקול שרץ מעל TCP ומתחת ל HTTP (או פרוטוקולים אחרים ברמת ה"אפליקציה"), כך שבעקרון הוא לא משנה את אופן העבודה של HTTP:

כדאי לציין שיש עוד פתרונות הצפנה ל HTTP כגון VPN או IPSec שהם ברמת פרוטוקול ה IP – אך הם מחוץ ל scope של דיון זה.

אלו שירותים פרוטוקול HTTPS מספק?

"הצפנה!" – נכון. אבל הצפנה של מה?

כדי להבטיח תקשורת פשוטה ומאובטחת, אנו זקוקים לקצת יותר מ"הצפנה!".
פרוטוקול TLS מספק 3 שירותים עיקריים:

  • הצפנה של המידע העובר בין הלקוח לשרת – כדי שצד שלישי לא יוכל להאזין לתקשורת.
  • Authentication: זיהוי השרת ו (אולי גם) הלקוח – כדי שנדע, למשל, ש"הבנק" שלנו הוא באמת הבנק שלנו, ולא אתר מתחזה [ב].
  • וידוא שלמות ההודעה, בעזרת "חתימה דיגיטלית" – כדי להתמודד עם התקפות מסוג Man in the Middle.
פרוטוקול TLS בפעולה בדפדפן כרום:
ורוד – אני רואה שיש אימות שאני אכן מחובר לבנק הפועלים. לפני כל הכנסה של כרטיס אשראי – כדאי מאוד לוודא שהשרת מאומת ושמו נשמע הגיוני.
תכלת – הממ…. 112-ביט הוא מפתח מעט חלש בימנו + חיבור 1.0 TLS הוא מעט לא מעודכן וחשוף להתקפות כגון BEAST (בהמשך)

על מפתחות סימטריים וא-סימטריים (הצפנה)

טוב, אני בהחלט לא מומחה להצפנה, אבל בגלל שנראה שזהו רקע נדרש – אנסה לתאר בכמה משפטים את הנושא.
בעולם ההצפנה מחלקים את המפתח הסודי, בעזרתו מצפינים / מפענחים את ההצפנה, ל 2 סוגים עיקריים: סימטריים וא-סימטריים.

מפתחות סימטריים הם פשוטים יותר ונוחים לעבודה. בימינו הם לרוב באורך של 128 עד 256 ביט – כאשר מפתח ארוך יותר הוא בעקרון חזק יותר וקשה יותר לפיצוח ב "brute force" (כלומר: ניסיון כל הצירופים האפשריים, לעתים עם כמה קיצורים). יכולת הפיצוח תלויה כמובן גם בסביבה, באלגוריתם ההצפנה ובמידת האקראיות של המפתח.

מה הבעיה? כל מי שמחזיק במפתח יכול לפתוח את השדר שהוצפן בעזרתו. יש קושי להעביר מפתחות בצורה בטוחה בין שרת ללקוח. בנק יכול לדרוש שתגיעו לסניף לקבל מפתח, אבל מה עם אתר לקנייה חד-פעמית מסין?

לצורך כך הומצאו המפתחות הא-סימטריים. האלגוריתם הנחשב בתחום זה הוא אלגוריתם RSA (שאחד ממפתחיו הוא ישראלי) – אלגוריתם שמאפשר לסיטואציה המדהימה הבאה להתרחש:

דני ואבי מנסים לתקשר בצעקות מ 2 צדי חדר גדול. במרכז החדר יש עשרות מומחי הצפנה, זוכי פרס טורינג רבים, ששומעים בבירור כל מה שדני ואבי אומרים. דני ואבי לא דיברו זה עם זה, או החליפו מידע לפני שנכנסו לחדר.
בהתבסס על אלגוריתם ה RSA, דני צועק משהו לאבי, אבי צועק משהו לדני וחוזר חלילה. הם מצליחים לתקשר בצורה בטוחה מבלי שאף אחד ממומחי האבטחה במרכז החדר יכולים לפענח את מה שהם אומרים.

כיצד זה קורה? זה קצת מסובך, אבל בגדול העיקרון מבוסס על הגרלה של 2 מספרים ראשוניים אקראיים גדולים מאוד והכפלה שלהם. המספר שנוצר מהמכפלה הוא ענק – ומאוד קשה לחשב ממנו את המספרים הראשוניים מהם הוא מורכב (במיוחד כשארכיטקטורת המחשב היא, 64 ביט, היא קטנה משמעותית מאורך המספרים). מתוך המכפלה מחלצים 2 מפתחות / מספרים:
מפתח ציבורי – מפתח שניתן לחלק לכל / לצעוק בתוך החדר.
מפתח פרטי – מפתח סודי שיש לשמור.

כדי להצפין ולפתוח מסרים ב RSA – זקוקים לשילוב של 2 המפתחות. ניתן לשלב אותם בשני האופנים הבאים:

  • כאשר רוצים להצפין מסר לבעל המפתח, מצפינים אותו בעזרת המפתח הציבורי – וניתן לפתוח אותו רק בעזרת המפתח הפרטי (שהוא סודי, בידי בעל המפתח).
  • כאשר בעל המפתח רוצה לאמת בפני אחרים שהוא שולח ההודעה ושהיא לא שונתה, מה שנקרא "חתימה דיגיטלית", הוא מצפין את ההודעה בעזרת המפתח הפרטי – שרק בעזרת המפתח הציבורי ניתן לפתוח. יש גם עוד checksum (או MAC) של ההודעה שמאמת שלא נעשו בה שינויים בדרך.

סה"כ מפתחות א-סימטריים היום הם באורך של 512 עד 2048 ביט – ודיי יקר למעבד לפענח אותם. דרך הפעולה של TLS הוא לבצע את התקשורת הראשונית בעזרת מפתח א-סימטרי (יקר להצפנה/פענוח). בערוץ המאובטח שנוצר – מעבירים מפתח סימטרי קצר יותר (קל להצפנה/פענוח) ושצריך פחות עבודת חישוב בו נשתמש מכאן והלאה. חתימה דיגיטלית יכולה לסייע לאמת את זהות המתקשרים (שרת / לקוח).

האם פרוטוקול ה SSL/TLS הוא מוגן לחלוטין?

לא.

למשל:
ממשלת ארה"ב (כמו ממשלות אחרות) מגבילה בחוק את גודל המפתחות בהן ניתן להשתמש להצפנה. מפתח גדול יותר = קשה יותר לפענוח. הסברה אומרת שממשלת ארה"ב מתאימה את החוק כך שיאפשר לאזרחים וחברות להתגונן מפני אזרחים וחברות אחרים, אך לא להתגונן בפני מחשבי העל של ה NSA או ה FBI.

כרגע החוק בארה"ב (למיטב ידיעתי) מגביל את גודל המפתחות ל 256 ביט להצפנה סימטרית ול 2048 ביט להצפנה אי-סימטרית (במקרה שלנו: RSA).

האם ארגון גדול בעל חוות מחשוב ענקיות (אמזון, גוגל, מייקרוסופט, ועוד כמה אחרים) – יכולים לפענח בזמן סביר את חוזק ההצפנה שמוגבל בחוק? – קרוב מאוד לוודאי שכן. בפועל, חוות המחשוב הענקיות הללו עסוקות מאוד בפעילות עסקית – כך שיכולת זאת נשארת בעיקר תאורטית.

האם פרוטוקול RSA עליו מסתמכת חלק גדול מהאבטחה של HTTPS יכול ל"הפרץ" בצורה גנרית? כנראה שכן – אבל זה עדיין לא קרה.

יש גם באגים בפרוטוקול ה SSL עצמו.
התקפות מפורסמות אחרונות המבוססות על באגים אלו קרויות BEAST ו CRIME – וחלק גדול של משתמשי האינטרנט עדיין חשוף אליהן [ג]. חשוף, מכיוון ששרתי אינטרנט רבים הם לא מעודכנים ועדיין עובדים עם גרסאות ישנות של TLS או אפילו SSL (כלומר: גרסה ישנה הרבה יותר)
.
ע"פ שמועות, מומחי ה NSA "דחפו" באגים מתוחכמים לתוך התקן של TLS – כדי שהם יוכלו לנצל אותם בהמשך. יש פה הנחה, לא בהכרח נכונה, שהבאגים כ"כ מורכבים ופינתיים – שאף אחד לא ימצא אותם. ה NSA, בין היתר גם שילם ל RSA כדי שיחלישו את ההגנות שלהם.

לצורך הפוסט נניח שפרוטוקול TLS הוא בטוח לחלוטין, בכל זאת – מדובר בפרוטוקול טוב ומאובטח למדי. חשפנו כאן את הצד הפחות מחמיא של TLS, אבל אם היינו נטפלים לפרוטוקולים נפוצים אחרים – הם כנראה היו יוצאים הרבה פחות טוב.

מה בהמשך?

הפוסט מתארך ולכן אני מחלק אותו ל2 חלקים.
בחלק א' סקרנו את הפונקציונליות הכללית של HTTPS (קרי TLS/SSL) וקישרנו אותו ל TCP/IP Stack שכולנו מכירים.
בחלק ב' אנסה להתמקד ב HTTPS Authentication וה Certificates – צד חשוב מאוד בפרוטוקול ה HTTPS, ובהשפעה של HTTPS על מערכת / המתכנתים / Operations.
שיהיה בהצלחה!

לינקים:
מדוע השתנה שם הפרוטוקול מ SSL ל TLS?

[א] כדאי לזכור שחברות אלו שיתפו פעולה בעבר עם ה NSA בחשיפת מידע של משתמשיהן. כעת הן מנסות להפגין את שינוי הכיוון שחל במדיניותן כלפי הרשויות / לטובת המשתמשים.
[ב] שרתי DNS נחשבים כלא כ"כ מאובטחים, ולכן התקפה לא מורכבת כ"כ יכולה להפנות אותנו לאתר שונה ממה שהתכוונו אליו.

[ג] לא צריך מאוד לדאוג – כי אלו התקפות מורכבות למדי ליישום. עדיין הייתי רוצה שכל שירות דואר אלקטרוני או אתר שמתעסק בכסף שלי, יעבוד עם TLS גרסה 1.2.