2016-10-24

התקפת מניעת-שירות, וכיצד האינטרנט בחוף המזרחי - הושבת?

התקפת מניעת שירות, או בקיצור DoS (קיצור של Denial of Service), היא התקפה פשוטה יחסית לביצוע אך אפקטיבית למדי. מכיוון שרבים ממנגנוני ההגנה הקיימים לא רלוונטיים עבורה - היא הפכה לפופולארית למדי.

DDoS (כלומר: Distributed Denial of Service) היא כבר התקפה שיש מאחוריה סיפור מרגש: אלפי מחשבים (אולי אפילו מיליונים) - תוקפים שירות גדול וחזק: ענק אינטרנט, בנק, תשתית ממשלתי, וכו'. האם להקת הציפורים, שכל אחת ממנה חסרת חשיבות - יצליחו ביחד להפיל את הענק?
המטאפורה של רבים מול יחיד - היא חומר מצוין לסיפורים "ויראליים", וכך התקפות ה DDoS זוכות לחשיפה רחבה - גם בקרב אנשים לא טכניים שאין להם מושג בסיסי במערכות מחשבים.


סצנה מתוך סרט "הציפורים" של היצ'קוק. מקור: http://www.filminamerica.com/Movies/TheBirds

כיצד נראית התקפת DoS?


להלן דוגמה פשוטה מאוד:

אני מתחבר לאתר ״האכל חתול״ (שעוקב אחר חתולים עזובים ונקודות בהן ניתן למצוא אותם ולהאכיל אותם) ולוחץ על כפתור ה refresh ללא הפסקה. יודעים מה? אני משתמש ב Refresh Monkey, תוסף סטנדרטי לכרום - בכדי שהמחשב יעשה זאת עבורי.

Refresh Monkey. הטאב יבצע refresh חמש פעמים בשנייה - ואני יכול גם לפתוח מאה טאבים כאלו.

בפעולה פשוטה זו הגדלתי, לבדי, את סה"כ העומס על האתר: נניח שפי 4 - כי זה אתר נישתי עם מעט traffic.
גם אם המערכת לא קורסת, היא לא מוכנה לטפל בכמות כזו של בקשות - והיא תאלץ לדחות חלק מהבקשות.
חלק מהבקשות שיידחו הן שלי, אבל חלק נוסף של משתמשים לגיטימיים - וכך יצרתי נזק: מנעתי ממשתמשים לגיטימיים שירות - Denial of Service.

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


--

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

הדף הראשי של האתר - הוא דף שמתוכנן להיות מהיר. רוב ה traffic מגיע אליו. העבודה שנעשית בו היא שליפה של חתולים באזור נתון. בחולון, העיר בה אני גר יש כמות ממוצעת של חתולים שמוזנים במערכת, אולם אם אדווח על המיקום שלי במרכז ת"א - צפיפות החתולים המתועדים גדולה פי שלושה!
בכדי לשפר את ההתקפה שלי - עלי לחשוב כיצד אני גורם לשרת לעבוד יותר קשה. אם אוכל לגרום לדף הראשי באתר לעבוד קשה יותר, אולי כך אפגע ביותר משתמשים, נניח 15% מהמשתמשים.


מה שבעצם אני רוצה למצוא, הוא את הבקשה הקלה ביותר עבורי לייצר, שתגרום למערכת המותקפת - להכי הרבה עבודה.


מצאתי באתר דף שנקרא: ״פריסה ארצית של חתולים ויחס האכלות״. בינגו! זה כנראה דף שלמערכת הרבה יותר קשה לרנדר (כי הרינדור מבוסס על חיתוכים וסיכומים בבסיס הנתונים). אם הנתונים אינם cached בצורה יעילה (מה שיכול ממש לקלקל!) - אני מסודר.
כמו כן, סביר יותר שדף שולי במערכת זכה לפחות שיפורי ביצועים - פשוט כי התנועה הדלילה אליו פחות משפיעה על סה"כ ביצועי המערכת.
עבורי  - זו הזדמנות מצויינת! אני אלמד את בוני האתר כמה לא יעיל הקוד מאחורי הדף הזה (!Muahaha). אני משגר עשרות בקשות בשנייה לרינדור הדף, ומייד מנתק את ה connection (ללא RST - מבלי להודיע). לשרת ייקח כמה שניות להבין שנעלמתי - ובזמן הזה הוא עדיין יעבוד להכין את הדף ולנסות לשלוח אותו אלי.

התקפה כזו עשויה להיות הצלחה גדולה: אמנם ״פריסה ארצית של חתולים ויחס האכלות״ - הוא דף שאינו חשוב במערכת, אבל אותם CPU ו Database שמשרתים אותו - משרתים גם את שאר המערכת. ברגע שתפסתי אותם, הצלחתי למנוע שירות מ90% מהמשתמשים הלגיטימיים שמנסים לגשת לאתר הבית, ולהאט משמעותית את זמני התגובה ל 10% המשתמשים שכן הצליחו לקבל שירות. הצלחה גדולה!


מקור: Scudlayer



על DDoS ו SDoS


לאחר שהבנו כיצד התקפת DoS עובדת - מה בעצם המשמעות של DDoS, מונח שהוא כנראה אפילו יותר מוכר?

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

יתרה מכך, מנגנוני ההגנה המקובלים בפני התקפת DoS היא לזהות כמות לא סבירה של traffic ממשתמש יחיד - ואז לחסום אותו ברמה היעילה ביותר (למשל: לדחות בקשות ל tcp connection, הנדרש בכדי לבצע בקשת HTTP, על בסיס כתובת IP של השולח).

אם אני מבצע התקפה מ 10000 מחשבים, כאשר כל אחד מהם מייצר כמות traffic סבירה (נניח: 3 בקשות בדקה) - יהיה הרבה יותר קשה למערכת להתגונן ולבדל traffic עויין מ traffic לגיטימי. אם המערכת תחסום כל משתמש שמבצע יותר מ-2 בקשות בדקה - ניצחתי: מנעתי שירות מהרבה משתמשים לגיטימיים! (ואז אין לי בעיה לשנות מעט את ההתקפה, ולבצע רק 2 בקשות בדקה).




על מנת לייצר התקפת DDoS יעילה, עלי לאסוף אלפי מחשבים, אולי עשרות אלפים, ואולי יותר - שאולי לשלוט בהם. כל מחשב כזה נקרא Zombie, וצבא של זומבים נקרא גם BotNet. האיסוף מתבצע בד"כ ע"י נוזקה שתתוקן במחשב ותהיה במצב רדום - עד אשר ארצה לבצע את ההתקפה. הנוזקה לא צריכה "להשתלט על המחשב": היא רק צריכה להתקין agent שמאזין ל port מסוים ומאזין לפקודות משרת ההפעלה שלי. בעת ההתקפה, בעל המחשב כנראה לא ירגיש בכלל שהוא שותף להתקפה. לא יפריע לו - ולא יהיה לו טריגר לנסות ולהסיר את ה agent שלי.
את הנוזקה אני יכול להפיץ באתרי הורדות קבצים במשך חודשים או שנים - עד לרגע ההפעלה. מי שרוצה לקצר תהליכים ויש ברשותו משאבים - יכול גם לרכוש רשתות BOTNET בשוק העברייני, ממישהו שטרח והקים אותן.

בהתקפה שהתרחשה בימים האחרונים על Dyn - ספקית שירותי DNS ("ספר הטלפונים של האינטרנט") גדולה, השתמשו ברשת Mirai - רשת Botnets מוכרת, שכבר שימשה לכמה התקפות בעבר, אם כי לא בסדר גודל כזה (מקור).

ההתקפה על Dyn. מקור: TechCrunch

מה שהיה חדש יחסית בהתקפה הוא השימוש ב "מכשירים בסיסיים" או "דברים" (IoT - Internet Of Things) - לביצוע ההתקפה. כלומר: הזומבים ב Mirai Botnet הם טוסטרים, שואבי אבק, מצלמות אבטחה - רכיבים שונים בעלי גישה לאינטרנט. הנוזקה "Mirai" סורקת את האינטרנט וברגע שמזהה מכשיר שכזה מנסה להתחבר אליו כ Admin בעזרת רשימה של users/passwords נפוצים שיש ברשותה. אלו הם לרוב ה defaults שמסופקים מהיצרן או שם וססמה קבועים - שלא ניתן בכלל להחליף.

"למה שאשנה את הססמה של הטוסטר החכם שלי?" - תוהה לעצמו המשתמש הביתי המתוחכם (שבכלל מודע לאפשרות). "מה יעשו - ישרפו לי את הצנים בבוקר? חחחח".

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


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

SDoS הם ראשי תיבות של Self Denial of Serive (מושג מקביל: Unintended Denial of Service) בהם ארגון מייצר לעצמו traffic שהוא לא מסוגל לעמוד בו - ואז נאלץ לדחות (= לא לספק שירות), לכמות גדולה של משתמשים לגיטימיים.

דוגמה קלאסית: קמפיין שיווקי שלא תואם (או אולי תואם, ותוצאותיו לא נצפו ע"י ה R&D/Operations) - שמגדיל את התעבורה פי כמה מונים - אך המערכת לא יכולה לעמוד בכמות ה traffic שנוצר.

פאדיחות.


בהתקפת DoS/DDoS זה מה שאתם רוצים שבעלי האתר יראו: פחות traffic מטופל, וזה שמטופל - הוא איטי ומציק.
מקור התמונה: New Relic


סגנונות תקיפה נפוצים


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


בכל זאת, ניתן לאפיין ברמה הטכנולוגית כמה סגנונות נפוצים לביצוע התקפות DoS/DDoS:


HTTP Flood

התקפות HTTP Flood הן ההתקפות הפשוטות והישירות - כמו הדוגמה שנתתי למעלה.
את ההתקפה אפשר למקד בכמות בקשות גבוהה, או בתפיסת רוחב הפס ("Bandwidth attack") של האתר. את סוג ההתקפה הזו עושים ע"י הורדה של כמויות גדולות ככל האפשר של נתונים מהאתר (לזומבים, כמו בחשבון ביתי, יש לרוב יש יותר נפח הורדה - ופחות נפח העלאה).

כדי לייצר התקפה מוצלחת חשוב למצוא דפים / אלמנטים באתר שהם איטיים במיוחד, דורשים חישוב רב - והכי חשוב: אינם cached! חשוב למצוא כמה דפים כאלו. במהלך ההתקפה האתר יכול להתגונן ע"י השבתה מכוונת של דף שאנו תוקפים - ואז נרצה לעבור מהר מאוד לדף איכותי אחר.

האוייב הגדול ביותר שלנו הוא CDN (כלומר: cache יעיל במיוחד). CDN בנוי להגשת תוכן בכמויות גדולות בצורה אופטימלית - ואותו יהיה קשה מאוד להעמיס. תשתית ה CDN מנוהלת ע"י ספק שמשרת עשרות אלפי אתרים - והקיבולת של ה CDN היא אדירה.

טריק פשוט ומקובל הוא לייצר אלמנט אקראי (למשל: ב query string) ב URL - שיגרום ל CDN לחשוב שאין לו את המשאב - ולהעביר את הבקשה ישירות לאתר.
למשל:

https://feed_a_cat.co.il/comprehensive-cats-report?random=198982

הרכיב "random=198982" ב URL כמעט ואף פעם לא יגרום לשרת לדחות את הבקשה (מי מתגונן בצורה כ"כ הדוקה?) - ובד"כ יגרום ל CDN לחשוב שמדובר במשאב ייחודי שלא נמצא אצלו ב Cache.



התקפות רזות: SYN Flood ו UDP-Based

כפי שאמרנו קודם לכן: אנו רוצים לייצר את הבקשה הקלה ביותר עבורי לייצרשתגרום למערכת המותקפת - להכי הרבה עבודה.

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

הרעיון של התקפות אלו הוא להימנע מיצירת ה TCP connection, שהיצירה שלו אורכת זמן (גוזלת מהמתקיף משאבים) - ואז יש יעד ברור לתשובה.
להזכיר: TCP Connection הוא הבסיס לביצוע HTTP Request. לא יהיו לנו בקשות HTTP בסגנון ההתקפה הזה.

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

אולי אפילו טוב יותר: הוא לא יענה במשך זמן ממושך (או בכלל) - ואז המערכת אותה תקפנו לא תוכל בכל הזמן הזה לשחרר את המשאבים שהקצתה לצורך ה connection. רוב המערכות יחכו כמה שניות ואז יוותרו על המאמץ לייצר connection, מערכת שלא עושה זאת (אין לה timeout) - היא פשוט טרף קל!

מקור התמונה: Cisco
מבחינת יחס עלויות - אנחנו שלחנו הודעה יחידה של פרוטוקול IP (זול מאוד), אך המחשב המרוחק השקיע הרבה יותר משאבים בעקבותיה.

וריאציות אחרות של ההתקפה היא לשלוח הודעות UDP לפורטים שונים של המערכת - שסרקנו במעוד מועד, וראינו שהם מגיבים בהודעת destination unreachable. רוב הסיכויים שהשרת המרוחק משקיע יותר משאבים מאיתנו, בסדר גודל או שניים.

אפשר לבצע Ping / Echo ולתת לשרת לענות. אפשר לנסות Pings עם payload גדול - שעשויים להקשות על השרת המרוחק, ואפשר (זה ממש מרושע) לנסות לשקר שכתובת ה IP שלנו היא כתובת של שרת אחר ב cluster של המערכת המותקפת - ואז לתת לשרתים השונים של המערכת לבצע ping בינם לבין עצמם...

בכל המקרים הללו, אנחנו תמיד נשלח הודעות ונשקר לגבי כתובת ה IP שלנו (כלומר: של הזומבי) - כך שיהיה קשה יותר לאתר את מקור ההתקפה ולהתגונן בפניה. זה ייתרון שניתן לנו בכך שאנו לא מייצרים TCP connection.



התקפות Amplification (הגברה)

אם יש בשליטתי 10,000 זומבים, ואני יכול לחבר לכל אחד מהם "מגבר" שיאפשר לו לייצר פי 10 traffic - הרי שכללתי במידה רבה את כלי ההתקפה שלי!

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

בעקבות צפיפות גבוהה של התקפות DDoS שנעזרו בשרתי DNS הופעל לחץ כבד על ספקי ה DNS לשכלל את ההגנות שלהם, ולא לענות לכמות גדולה של Queries מורכבים שמגיעים לפתע למערכת.

מגברים "טבעיים" אחרים שניתן למצוא ברשת האינטרנט כוללים:
  • שרתים שעונים לפרוטוקול SNMP (פרוטוקול ניהול רשת) - היחס בין גודל הבקשה לגודל התשובה יכול להגיע לעד פי 600, יותר! לרוע המזל, אין הרבה שירותי SNMP שפתוחים לאינטרנט הרחב...
  • שירותי NTP (כלומר: Network Time Protocol, הפרוטוקול בעזרתו שרתים מסתנכרנים על השעה הנכונה) - הם יעד פופולארי. פקודת monlist של הפרוטוקול (המחזירה רשימה של 600 השרתים האחרונים שביקשו שירות) היא בעלת יחס החזרה של פי 200.
  • מערכות Peer to Peer - שניתן למצוא פרצה ולבלבל את כל ה Peer שאנו מתקשרים איתם באותו הרגע ולחשוב שהשרת המותקף הוא בעצם אני. הם עלולים לנסות שוב ושוב עד שיקבלו את התשובה שהם מצפים לה...
  • וכו'

התקפות הגברה נמדדות לרוב בכמות ה Bandwidth שהן מייצרות. השיאים נשברים כל הזמן, והשיא הידוע לי הוא 602 Gbps - כלומר: traffic של 602 ג'יגהביט בשנייה (או 75 ג'יגהבייט בשנייה). שיא שבוודאי יישבר.





סיכום


מה הופך התקפות DDoS לכ"כ פופולאריות?
  • הן פשוטות יחסית ליישום. הרבה יותר פשוטות מהתקפות אחרות. 
  • הן מתאימות כמעט לכל מטרה: הן לא תלויות ב stack טכנולוגי מסוים, או בפגיעויות מסוימות של המערכת.
  • קשה לאתר את התוקף - ובמיוחד בהתקפות שאינן מבוססות על TCP. היכולת להתחמק - היא אינטרס עליון של התוקף.

התקפות DDoS הן חלק מהתרבות הפופולארית - וזוכות לחשיפה תקשורתית מוגברת מצד העיתונות. הן פופולריות כנגד אתרים גדולים, נגד שירותים ממשלתיים, וגם נגד Online Games (שם המשתמשים מתקשים *מאוד* להתנתק מהמשחק האהוב עליהם - אפילו לשעות ספורות). פעמים רבות ישנה התקפה קטנה "לדוגמה", שלאחריה יש דרישת כופר - שתשלום עליה ימנע את ההתקפה "הגדולה" (ואז ניתן לעבור ליעד הבא).

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


כיצד מתגוננים?
  • מעבירים את התעבורה לאתר דרך שירות שיודע לזהות ולסנן traffic של התקפת DDoS (כמו CloudFlare או Akamai) . שירות כזה עולה כסף, אך הוא מסוגל לאתר ולמנוע מכם את רוב ה traffic של התקפת DDoS. 
  • חוסמים לגישה מהאינטרנט כל port או endpoint שאינו הכרחי.
  • בונים את המערכת כך שיהיה ניתן להשבית (disable) בקלות מנגנונים יקרים במשאבים של המערכת. בעת התקפה, עדיף לאבד יכולת או שתיים של המערכת - מאשר את כל המערכת.


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




2016-10-04

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/<session_id>/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 מחייבת שכל צומת מתאר משאב שניתן לגשת אליו. לדוגמה, אם הגדרתי משאב כ:

http://example.com/orders/2009/10/776654

אזי גם ה URIs הבאים צריכים להיות ברי-קיום:

http://example.com/orders/2009/10
http://example.com/orders/2009
http://example.com/orders
http://example.com

יתרון גדול בשימוש ב 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