20.10.16

elasticsearch - מבוא חלק 3 - חיפוש


איך מבצעים חיפוש ב ElasticSearch - מבוא בסיסי


עד עכשיו הכנסנו מידע ל elasticsearch (בהמשך ייכתב ES בקיצור). הגיע הזמן שנחפש אותו ...

Types

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


לדוגמא: נבנה אינדקס עבור מסעדה ונאנדקס בו תאור של כל המנות תחת type שנקרא dish, נאנדקס מידע על המלצרים תחת type שנקרא waiters ונאנדקס מידע על ספקים תחת type שנקרא supplier.

זה מה שמאפשר לנו אח"כ לחפש מידע מסוג מסויים. נוח!

הכנסת מסמך עם ציון הסוג שלו:

דוגמא:


ובמקרה שרוצים לתת ל ES לקבוע בעצמו את מזהה המסמך:

נחזור לחיפוש

לחיפוש יש שני API-ים שונים.. אחד פשוט ומהיר שמיועד בעיקר לאנשים שרוצים להריץ שאילתא. השני הוא מובנה ומיועד לתוכנית שפונה ל ES.  בשני המקרים צריך לציין איפה לחפש. זאת מציינים בשורת ה URL:

כדי לחפש באינדקס מסויים נרשום בשורת ה URL:
GET /indexName/_search

כדי לחפש בסוג מסויים באינדקס:
GET /indexName/typeName/_search

כדי לחפש בכל האינקסים שמתחילים בתוים מסויימים:
GET /indexPrefix*/_search

ויש עוד אפשרויות שונות.

חיפוש מהיר - query string

בחיפוש מהיר מתארים במחרוזת אחת את השאילתא, מחרוזת אחת כמו שיש למשל ב where של שאילתת SQL. אפשר לרשום את המחרוזת הזאת בשורת ה URL וזה ממש נוח כאשר אדם עובד מול ES ורוצה לבצע חיפושים:


בדוגמא הזו, ES יחפש במסמכים בעלי סוג בשם tweet בכל האינדקסים את המסמכים בעלי שדה tweet שמכיל את המילה elasticsearch.

הנה דוגמא לביצוע החיפוש המהיר שאינו דרך שורת ה URL וזאת דרך פקודת simple_query_string


אפשר גם לציין כמה קריטריונים לחיפוש. אם שמים מחרוזת כזו בשורת ה URL אז צריך לבצע מה שנקרא percent encoding כדי להפוך אותך למחרוזת רציפה אחת.

דוגמא: התנאי name:john +tweet:mary+ אומר שיש למצוא מסמכים שבשדה name מופיעה המילה john ובשדה tweet מופיעה המילה mary. כדי לשים את הביטוי בשורת ה URL צריך לקודד אותו ל %2Bname%3Ajohn+%2Btweet%3Amary.



והנה שורת החיפוש (דוגמא לאתר שיעשה את זה בקלות - http://www.url-encode-decode.com/)


סימן '+' מציין כי יש למצוא את המילה. סימן '-' מציין כי אסור שתהיה המילה. כל המילים שאין לפניהן '+' או '-' הן מילים אופציונליות. ככל שיותר מילים כאה יימצאו כך הניקוד של המסמך תעלה (כלומר, המיקום שלו ברשימת התשובות תהיה גבוהה יותר).

אפשר גם לחפש בכל השדות ע"י ציון מילות החיפוש בלבד ללא ציון שם שדה לחיפוש.

GET /_search?q=mary

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

דוגמא מורכבת יותר:



חיפוש "מלא" - DSL

שפת החיפוש הקלה שנקראית query-string search או query lite נוחה מאוד לחיפושים אנושיים על ה DB. תוכנית לעומת זו זקוקה ל API מסודר. וזה גם קיים ב ES. בשיטה הזו, משתמשים בפקודה _search ומעבירים בגוף הבקשה תאור של השאילתא דרך json. 

אנקדוטה מעניינת היא שפעולת החיפוש נעשית ע"י HTTP GET. וזה בתורו מעלה שאלה, איך אפשר לתת ל GET מידע נוסף ב body? ואכן, ישנם שרתים, במיוחד שרתי פרוקסי שלא יעבירו את ה body. במקרה כזה, ES מאפשר להשתמש גם ב HTTP POST לבצע את החיפוש.

הסיבה שב ES בחרו להשתמש ב HTTP GET היא שזה יותר מתאים לפעולה, הרי היא לא משנה כלום בנתונים.

דוגמא לשאילתא:

ודוגמא יותר מורכבת:

במקרה הזה יש אופטור bool שמרכיב תתי שאליתות. על כל תת שאילתא מציינים האם היא חייבים להתקיים או לא.

מה מוזר ושונה בחיפוש

מי שרגיל לעבוד ב SQL אז הוא\היא יגלו שהחיפוש ב ES מתנהג אחרת.

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

דוגמא:




חוץ מזה, תמיד יחזרו עד 10 תשובות אפילו אם יש יותר מסמכים רלבנטים. מוזר! הסיבה היא ש ES עובד תמיד עם Paging. כלומר, כאשר מבצעים חיפוש ולא מציינים מספר דף, אז יחזור הדף הראשון. אבל אפשר לבקש דף אחר ע"י פרמטר.

דוגמא:



שדה from הוא ה offset של התשובה מהתשובה הראשונה. שדה size מציין כמה מסמכים להביא.

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

דוגמא:



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




הערה: הסיכום מסתמך בעיקר על הספר Elasticsearch - The Definitive Guide

9.10.16

XSS התקפת

התקפת XSS על אפליקציות ווב

התקפת XSS מנצלת את העובדה שאפלקציית ווב משתמשת ב HTML ו JavaScript לרוץ על הדפדפן. 

בעזרת הטכניקה שתתואר בהמשך, התוקף מצליח להריץ קוד JavaScript שלו על הדפדפן של הנתקף.

ראשית, XSS הוא ראשי תיבות של Cross Site Scripting. במקומות מסויימים ההתקפה גם נקראית JavaScript injection.


איך זה עובד?

זה כל כך פשוט שזה מדהים. 

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

ואיך אפשר "להזריק" קוד? הרי זה נשמע בלתי אפשרי ...  

יותר קל להבין את השיטה דרך דוגמא.

דוגמא:

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

באתר כזה יהיה טופס להכניס הודעה חדשה לפורום. ויהיה בו דף אחר בו אפשר לראות את כל ההודעות.

להלן דף להזנת הודעה חדשה:


ובדף אחר אפשר לראות את כל ההודעות:



עכשיו התוקף ייכנס לאתר ויזין לשדה הנושא או לשדה ההודעה מחרוזת שמריצה קוד JavaScript (יש להקיף אותו בתגית



מה שיקרה עכשיו הוא שכאשר מישהו ייכנס לדף שמראה את ההודעות, אזי הדפדפן שלו עלול להריץ את הקוד:

למה זה קורה ?

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

דוגמא:


<div>subject string 1</div>
<div>message string 1</div>
<div>subject string 2</div>
<div>message string 2</div>

זה יהפוך ל:



<div>General Discussion</div>
<div>Have something you ….</div>
<div><script src=attackersite ></script></div>
<div><script src=”javascript:Function(v)”></script> </div>


מה אפשר לעשות?
אסור לסמוך שהמשתמש הכניס מחרוזת שאינה מכילה קוד זדוני!

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

עד כמה לנקות?

ניקח לדוגמא את הקלט:

<p style="color:blue">
this is
<em onmouseover="this.textContent='ATTACK!'">click here</em>
attacker code
</p>

יש כמה אפשרויות:

מה עושים
מה יישאר
מה יקרה על המסך
אפשר להוריד כל תגית HTML (התגית והתוכן שלה)
this is attacher code
הטקסט יוצג בצבע ברירת המחדל
אפשר להוריד רק תגיות מסוכנות. זה מאפשר להציג שדה שיש לו עיצוב. דבר שמאוד שכיח בשדות טקסט של הודעה או תאור.
<p style="color:blue"> this is <em >click here</em> attacker code </p>
הטקסט יוצג בצבע שרשום ובעיצוב שרשום ללא onmouseover
אפשר לבצע פעולה שנקראית escaping והיא הופכת כל תו "<" או ">" לייצוג שלו ב HTML
&lt;p style=&quot;color:blue&quot;&gt; this is &lt;em onmouseover=&quot;this.textContent='ATTACK!'&quot;&gt;click here&lt;/em&gt; attacker code &lt;/p&gt;
הטקסט יוצג ממש כמחרוזת HTML אך ללא הפעלת הפעולות (שינוי צבע או פעולת עכבר)
אפשר לסמוך על הקלט (אם בטוחים שבסדר משום מה)
המקור
הטקסט יוצג בעיצוב הדרוש וכאשר לוחצים עם העכבר, יבוצע הקוד

יש לבחור את השיטה שדרושה. דרך אגב, באנגולר יש כבר תמיכה בכל אחת מהשיטות שהוזכרו (ng-bind, שרות $santize ושרות $sce)

וזה היה תאור קצר על התקפת XSS ואיך להתגונן בפניה.

5.10.16

elasticsearch for dummies (hebrew) - part 2 - the document

המסמך

בחלק 1 של מבוא ל elasticsearch נאמר כי elasticsearch הוא database שיודע לשמור מסמכים ולחפש בהם בצורה מאוד מהירה.

אז מה זה מסמך?

כאשר שומעים את המילה "מסמך" (document) ישר קופץ לראש מסמך וורד או מסמך PDF שיש בו הרבה טקסט ותמונות. לא מדובר פה בכזה מסמך.

מסמך ב elasticsearch הוא אוסף של שדות. כל שדה יכול להכיל מחרוזת או מספר. שדה יכול להכיל מערך של ערכים. שדה גם יכול להיות שדה "מורכב" כך שהוא בעצמו מכיל אוסף שדות. חשוב לזכור שבמקרה והשדה מחזיק מחרוזת זה לא אומר בהכרח שמדובר בטקסט פשוט. יכול להיות שהמחרוזת הזו היא בעצם תאור של תאריך או תאור של סכום כספי.

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

הצורה השכיחה להציג מסמך כזה היא שימוש בפורמט json. דוגמא:




לאחר שיוצרים אינדקס, אפשר לאנדקס מסמכים, למחוק מסמכים מהאינדקס, לעדכן מסמכים ולחפש מסמכים.

שמירת מסמך ב database

אם רוצים לשמור מידע חדש ב database אז יוצרים עבור המידע "מסמך" ואותו שומרים ב database תחת אינדקס מסויים. הפעולה הזו גם נקראית "לאנדקס את המסמך".

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

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

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

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

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

דוגמא להכנסת מסמך:


בדוגמא הזו מוכנס המסמך לאינדקס ששמו website. Blog מציין את סוג המסמך (type) – על כך בהמשך. 
ES יחזיר בתשובה את המזהה שהוא נתן למסמך.

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


עדכון מסמך

אפשר לעדכן מסמך באינדקס. זה לא עדכון כמו שקורה ב RDB. זאת כי אי אפשר לשנות מסמך שנשמר ב ES. אבל כן אפשר לשמור גרסה חדשה שלו.

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

כדי לעדכן, שומרים עותק מלא של המסמך שוב תוך שימוש במזהה של המסמך המקורי:




בתשובה רואים שמספר הגרסה עלה ב 1:



לאחר העדכון, ES מסמן את הגרסה הישנה של המסמך כגרסה למחיקה ומאנדקס את המסמך החדש. אי אפשר לבקש את הגרסה הישנה והיא תמחק מתי שהוא בעתיד.

קיים גם API לעדכון מסמך בו לא צריך להעביר את כל השדות של המסמך אלא רק את השדות שהשתנו. כאשר ES יקבל את הבקשה הזו הוא ייצור גרסה חדשה של המסמך (ב ES אי אפשר לשנות מסמך קיים) ויעתיק אליו את כל הערכים שלא השתנו. ה API לעדכון חלקי הוא _update:


מחיקת מסמך

כמובן שאפשר גם למחוק מסמך וזאת ע"י בקשת DELETE:


אם המסמך קיים אז מקבלים תשובה עם קוד 200 (OK) אחרת מקבלים תשובה עם קוד 404 (Not Found).
כמו עדכון, הפעולה הזו רק מסמנת המסמך למחיקה והוא יימחק מהדיסק מתשהוא בעתיד.

נעילות

נשאלת השאלה מה קורה כאשר מעדכנים אותו מסמך משני מקורות שונים. מה תופס? איך אפשר לזהות זאת?

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

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



כדי להתמודד עם המצב הזה ב databases הומצא מנגנון הנעילות.

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

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

כל המנגנון הזה משתמש בשדה _version של המסמך כדי לדעת האם הוא השתנה.

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

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



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



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