הפוסט הזה, בניגוד לשאר הפוסטים, הוא יותר על ארכיטקטורה או יותר נכון – על גישה בתוכנה שנקראת Least Privilege או ״הרשאה מינימלית״. הגישה הזו היא גישה חשובה וכדאי שגם מתכנתים ילמדו אותה. במיוחד בעולם של ה-LLM שמייצר קוד, החשיבה וההבנה של גישות כאלו הן קריטיות כאשר אנו בעצם מנהלים את ה-LLM ומבקשים ממנו לכתוב קוד. ה-LLM מייצר קוד לפי סבירות סטטיסטית ולא בטוח שלפי הגישה הזו, בטח אם לא מנחים אותו. אם תכירו, תוכלו לוודא שבקוד שהוא יוצר עבורכם העקרון הזה נשמר.
בפוסט הזה אני מספר על הגישה הזו באופן מאד מעשי עם קוד בג׳אווהסקריפט ובפייתון ובתסריטים מעשיים שרלוונטיים לכל כותבי הקוד בבקאנד.
בגדול, העקרון הזה הוא פשוט למדי – לא נותנים הרשאה למי שלא צריך. בדיוק כמו שלא כל עובד צריך לקבל את המפתח לכספת בחדר של המנכ״ל, כך גם בתוכנה. אנחנו נותנים את ההרשאה המינימלית שצריך עבור ביצוע הפעולה. זה בכל הרבדים, ברובד התוכנה, ברובד מסד הנתונים ובכל מקום. הגישה הזו בעצם מונעת התקפה של Privilege Escalation. כתבתי על ההתקפה הזו כאן. בפוסט שלי הסתכלתי על הצד של התוקף. פה אני מראה את הצד של המתכנת שבעצם צריך למנוע את הפרצה הזו.
הדרך הטובה ביותר להמחיש את הגישה היא באמצעות הקוד הבא. אני משתמש פה בדוגמאות בג׳אווהסקריפט אבל הדוגמאות האלו יהיו מובנות בהחלט גם לדוברי שפות אחרות. פשוט תתייחסו לזה כליטקוד 🙂
הבה ונבחן את הקוד הזה שמציג פרופיל משתמש. כאשר משתמש נכנס ל /users/234 למשל, נעשה תהליך של אימות זהותו – כדי שלא יהיה חלילה מצב שמדובר פה באדם לא מזוהה שמגיע מהרחוב אלא משתמש רשום במערכת. האימות נעשה באמצעות authenticate. במידה והאישור תקין, אז יש קריאה להביא את הפרטים של המשתמש, במקרה הזה 234 ומציגים אותם.
app.get('/users/:id', authenticate, async (req, res) => {
const user = await getUserById(req.params.id);
res.json(user);
});
זה נראה טוב וזה גם קוד שיוצר על ידי LLM. אבל זה בעייתי. למה? כי נתתי פה הרשאה רחבה מדי. ההרשאה היא לכל משתמש. אבל רגע, אני לא רוצה ��כל משתמש יוכל לראות את כל המשתמשים! זה יכול להוביל לדליפת מידע (או ל Privilege Escalation אופקי, אם אנו משתמשים בביטוי מקצועי). איזו דליפת מידע? משתמש שנכנס לפרופיל של משתמש אחר! הדברים האלו קרו בעבר.
הוספת התנאי הבא, תמנע את ההרשאה הרחבה לכל משתמש ותצמצם את ההרשאה. משתמש יכול לראות רק את עצמו או אדמין, שיכול לראות את כולם.
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).send('Access Denied');
}
גישת Least Privilege היא לא רק בקוד
אתן דוגמה נוספת, שמאד נפוצה בעולם מסדי הנתונים. כידוע, אם אני משתמש למשל ב-MySQL, אני יוצר קישור למסד הנתונים עם משתמש מסוים במסד הנתונים. לא מעט פעמים, למשתמש הזה יש את כל ההרשאות על מסד הנתונים. בתחילת העבודה, יוצרים אותו בערך ככה:
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'localhost' IDENTIFIED BY 'StrongPassword123!';
אפשר לעשות key rotation כמובן אבל בסופו של דבר, יש לנו app_user שיכול לעשות הכל ומשתמשים בו בכל רחבי האפליקציה באופן אחיד. אם נבחן את הקוד לעומק, נראה שגם משתמש שיש לו הרשאות קריאה בלבד, עושה שימוש באותו חיבור למסד הנתונים ואותו משתמש במסד הנתונים שגם משתמש עם הרשאות גבוהות יותר עושה.
const mysql = require('mysql2/promise');
async function runQuery() {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'app_user',
password: 'StrongPassword123!',
database: 'app_db'
});
try {
const [rows] = await connection.execute('SELECT id, name FROM invoices LIMIT 10');
console.log('Invoice List:', rows);
} catch (err) {
console.error('Query failed:', err.message);
} finally {
await connection.end();
}
}
runQuery();
זה בעייתי. כי אם למשל תתגלה פרצה של SQL Injection זה אומר שמשתמש לצפייה בלבד יכול לבצע נזקים. אבל… מי אמר שחייבים להשתמש באותו משתמש לכל הפעולות? אפשר ליצור משתמש אחר, בשם readonly_user שהוא זה שי��שה את פעולות הקריאה.
CREATE USER 'readonly_user'@'localhost' IDENTIFIED BY 'OtherStrongPassword';
-- Grant only SELECT permission on the invoices table
GRANT SELECT ON app_db.invoices TO 'readonly_user'@'localhost';
זה השלב שחלק מכם יגלגלו עיניים וזה בסדר גמור. גם אני, כשאני כותב קוד, אני רק רוצה שהכל יעבוד ויעבוד מהר ולהתחיל להתעסק עם שמות משתמש למסדי נתונים או הרשאות בדיוק כשאני רוצה לעבוד זה… מבאס. מצד שני, חשוב לזכור שאבטחה זה גם חלק מהפיצ׳ר.
כשיש הרשאות שלא צריך, זה מגדיל את מה שנקרא ״משטח התקיפה״. זה כמו להדליק אור איפה שלא צריך – רק פתח לבעיות. וגם, בעולם של היום, שבו כל LLM מזדמן מייצר קוד, דווקא היתרון של מתכנתים הוא בחשיבה ובהבנה של הצרות שיכולות להגרם ומניעה שלהם מראש. לכתוב קוד כל אחד יכול. לכתוב קוד טוב ומאובטח? זה לא בטוח. אם אתם פה וקוראים את הפוסט הזה ובטח גם פוסטים אחרים – זה היתרון היחסי שלכם.
Least Privilege היא גם עקרון מנחה בתשתיות
זה לא רק עניין של קוד או מסד נתונים אלא גם בעיה של תשתיות וגם בענן. אני אראה דוגמה של אמזון שאותה אני מכיר היטב. לא מעט פעמים אנו יוצרים באקט S3 לאחסון קבצים. משהו בסגנון הזה (קוד פייתון CDK אבל קל להבנה)
app_role.add_to_policy(iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=["s3:*"],
resources=["*"]
))
מה יש פה? מתן הרשאה לכל הבאקט ולכל הפעולות. אבל רגעעעעע!!! אם בדיוק כמו בדוגמאות של מסד הנתונים, זה מישהו שרק צריך לראות את הקבצים? אז אני אגביל את הפעולות באמצעות role יותר מדויק. גם אני אגדיר את ה-prefix בבאקט שאפשר לגשת אליו – רק ל-invoices וגם אגדיר יותר במדויק את הפעולות שאפשר לעשות. רק קריאה.
app_role.add_to_policy(iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=["s3:GetObject"],
resources=[f"{bucket.bucket_arn}/invoices/*"]
))
לפעמים זה עניין של הגדרת ישויות במערכת. למשל, אם יש לי למבדה ב-AWS (מקבילה ל-Cloud Functions בגוגל או Azure Functions במיקרוסופט) ואני רוצה שהיא תכתוב לוגים ל-cloudwatch. אני יכול לתת לה הרשאה כזו:
app_lambda.role.add_to_policy(iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=["logs:*"],
resources=["*"]
))
זה כמובן מרחיב מדי, גם לכל קבוצת הלוגים וגם מתן הרשאות לעשות מה שאנחנו רוצים. גם למחוק ��וגים – לא דבר שאנחנו רוצים! בקוד הזה אני גם מגביל את קבוצת הלוגים שאפשר לעשות בה פעוחות וגם מגביל את הפעולות עצמן.
log_group_arn = f"arn:aws:logs:{self.region}:{self.account}:log-group:/aws/lambda/{app_lambda.function_name}:*"
app_lambda.role.add_to_policy(iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=[
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
resources=[log_group_arn]
))
סיכום
אפשר לתת המון דוגמאות קוד מעשיות נוספות. אבל אני לא בטוח שצריך. אני די שונא פילוסופיה של קוד, אבל במקרה הזה גישת Least Privilege היא מה שאנחנו קוראים לו state of mind. משהו בתודעה שכל הזמן צריך להיות לנו בראש כאשר אנחנו כותבים קוד. בטח ובטח אם אנחנו כותבים את הקוד הזה עם LLM שאומן על קוד שפשוט עובד ולאו דווקא קוד מאובטח. כשאני כותב עם קופיילוט שלי קוד CDK וכותב לו app_lambda.role.add_to_policy, הוא ישלים לי דווקא את כל הדוגמאות הלא מאובטחות כי הן מרבית הטקסט שהוא אומן עליו. זוכרים את Stackoverflow? עליו רוב ה-LLMים אומנו. תמיד היה שם קוד לא מאובטח או בעייתי מבחינת ביצועים ולמטה היו כל מיני הערות של ״הי! זה לא מאובטח!״.
גם לא מעט פעמים ה-LLM נותן בהתחלה קוד מאובטח שלא עובד – כי לא שמנו משאב נכון ולא שמנו הרשאה מתאימה. קורה. אם אנחנו נותנים ל-Agent לעבוד ולעשות איטרציות, יש סיכוי יותר מגבוה שהוא פשוט ישים הרשאה רחבה יותר כחלק ממאמצי התיקון וישאיר אותה שם. זה התפקיד שלנו כמתכנתים להסתכל על מה הוא עושה וליישם את הגישה. לא רק ב-CDK אלא גם בקוד, בחיבור למסד נתונים ובתשתיות כמו דוקר.






