לעתים קרובות אנו נתקלים במצבים שבהם RBAC כבר לא מספיק. בפרויקט אחד, לאחר יישום RBAC קיבלנו 47 בקשות לשינוי תפקיד בחודש—כל אחת דורשת אישור. ABAC צמצם את זה ל-2 בקשות. חוקים נוצרים כמו "משתמש יכול לערוך מסמך אם הוא המחבר שלו, המסמך במצב טיוטה, והמשתמש עובד באותו ארגון כמו המסמך." תפקיד לבדו לא יכול להתמודד עם זה—יש צורך בהקשר. ABAC (בקרת גישה מבוססת תכונות) מקבל החלטות על סמך תכונות של הנושא (משתמש), האובייקט (משאב), והסביבה (זמן, IP, הקשר בקשה). הניסיון שלנו מראה שגישה היברידית של RBAC+ABAC מספקת איזון אופטימלי בין ביצועים וגמישות. אנו מבטיחים שקיפות החלטות עם ביקורת מפורטת.
כיצד המודל עובד
ארבעה ישויות ב-ABAC:
- נושא – משתמש והתכונות שלו: תפקיד, מחלקה, clearance_level, org_id.
- משאב – אובייקט והתכונות שלו: owner_id, סטטוס, org_id, סיווג, אזור.
- פעולה – קריאה, כתיבה, מחיקה, אישור.
- סביבה – time_of_day, ip_address, request_method.
מדיניות היא פרדיקט על התכונות הללו. לדוגמה:
ALLOW IF subject.org_id == resource.org_id AND (subject.role == 'editor' OR subject.id == resource.owner_id) AND resource.status IN ('draft', 'review') AND action == 'write' למה ABAC עדיף על RBAC?
RBAC הוא פשוט ומהיר, אך אינו ניתן להרחבה עבור חוקים מורכבים. ABAC יכול לבטא כמעט כל אילוץ עסקי: "מנהל יכול לאשר בקשה אם הסכום < 10000 והבקשה נוצרה במחלקה שלו." ב-RBAC, תצטרך להציג תפקיד חדש. ABAC מטפל בזה באופן הצהרתי. הבה נשווה פרמטרים מרכזיים:
| קריטריון | RBAC | ABAC |
|---|---|---|
| גמישות | נמוכה (תפקידים קבועים) | גבוהה (תכונות) |
| תמיכה בהקשר | לא | כן |
| ביצועים | גבוהים | בינוניים (עם מדיניות רבה) |
| מורכבות יישום | נמוכה | בינונית |
סכמת אחסון מדיניות
ניתן לאחסן מדיניות בקוד (מתאים למספר קטן של חוקים) או במסד נתונים באמצעות DSL. הנה גרסה עם PostgreSQL המאחסנת תנאים כ-JSON:
CREATE TABLE abac_policies (
id SERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL,
description TEXT,
effect VARCHAR(8) NOT NULL CHECK (effect IN ('allow', 'deny')),
priority INT NOT NULL DEFAULT 0,
conditions JSONB NOT NULL, -- дерево условий
actions TEXT[] NOT NULL,
resources TEXT[] NOT NULL -- glob: 'documents', 'documents/*'
);
-- Пример записи
INSERT INTO abac_policies (name, effect, priority, conditions, actions, resources)
VALUES (
'editors_can_write_own_draft',
'allow',
10,
'{ "operator": "AND", "conditions": [ {"attribute": "subject.role", "op": "in", "value": ["editor", "senior_editor"]}, {"attribute": "subject.org_id", "op": "eq", "value": {"ref": "resource.org_id"}}, {"attribute": "resource.status", "op": "in", "value": ["draft", "review"]} ] }',
ARRAY['write', 'delete'],
ARRAY['documents', 'documents/*']
);
מנוע החלטות
class ABACEngine {
constructor(policies) {
// Политики предзагружены и отсортированы по приоритету (deny > allow при конфликте)
this.policies = policies.sort((a, b) => {
if (a.effect === 'deny' && b.effect !== 'deny') return -1;
return b.priority - a.priority;
});
}
evaluate(subject, resource, action, environment = {}) {
const context = { subject, resource, action, environment };
for (const policy of this.policies) {
if (!policy.actions.includes(action)) continue;
if (!this.matchesResource(policy.resources, resource.type)) continue;
if (this.evaluateCondition(policy.conditions, context)) {
return policy.effect === 'allow';
}
}
return false; // default deny
}
evaluateCondition(condition, ctx) {
if (condition.operator === 'AND') {
return condition.conditions.every(c => this.evaluateCondition(c, ctx));
}
if (condition.operator === 'OR') {
return condition.conditions.some(c => this.evaluateCondition(c, ctx));
}
if (condition.operator === 'NOT') {
return !this.evaluateCondition(condition.condition, ctx);
}
// Листовой узел
const leftVal = this.resolveAttribute(condition.attribute, ctx);
const rightVal = condition.value?.ref ? this.resolveAttribute(condition.value.ref, ctx) : condition.value;
switch (condition.op) {
case 'eq': return leftVal === rightVal;
case 'neq': return leftVal !== rightVal;
case 'in': return Array.isArray(rightVal) && rightVal.includes(leftVal);
case 'gte': return leftVal >= rightVal;
case 'lte': return leftVal <= rightVal;
case 'contains': return Array.isArray(leftVal) && leftVal.includes(rightVal);
default: return false;
}
}
resolveAttribute(path, ctx) {
// 'subject.org_id' → ctx.subject.org_id
return path.split('.').reduce((obj, key) => obj?.[key], ctx);
}
matchesResource(patterns, resourceType) {
return patterns.some(p => p === resourceType || (p.endsWith('/*') && resourceType.startsWith(p.slice(0, -2))));
}
} כיצד לשלב ABAC ב-Express?
const engine = new ABACEngine(await loadPoliciesFromDB());
// Перезагрузка политик при изменении (без рестарта сервера)
db.on('policy_changed', async () => {
engine.updatePolicies(await loadPoliciesFromDB());
});
function abac(action) {
return async (req, res, next) => {
const resource = await loadResource(req); // загружаем объект со всеми атрибутами
const allowed = engine.evaluate(
req.user, // subject
resource, // resource
action, // action
{ // environment
ip: req.ip,
timestamp: Date.now(),
userAgent: req.headers['user-agent'],
}
);
if (!allowed) {
return res.status(403).json({ error: 'Forbidden' });
}
req.resource = resource;
next();
};
}
router.put('/documents/:id', authenticate, abac('write'), updateDocument);
router.delete('/documents/:id', authenticate, abac('delete'), deleteDocument);
יומן ביקורת
ABAC ללא ביקורת הוא כלי עיוור. כל החלטת מנוע מתועדת:
CREATE TABLE abac_audit_log (
id BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ NOT NULL DEFAULT now(),
subject_id INT NOT NULL,
resource_type VARCHAR(128),
resource_id VARCHAR(128),
action VARCHAR(64) NOT NULL,
decision BOOLEAN NOT NULL,
matched_policy_id INT REFERENCES abac_policies(id),
context_snapshot JSONB -- snapshot subject+resource attrs на момент решения
);
CREATE INDEX idx_abac_audit_subject ON abac_audit_log (subject_id, ts DESC);
CREATE INDEX idx_abac_audit_resource ON abac_audit_log (resource_type, resource_id, ts DESC);
זה עונה על השאלה "למה משתמש X לא יכול היה לבצע Y עם אובייקט Z לפני שלושה ימים?" — בלעדיו, חקירת תקרית הופכת לניחוש.
שילוב עם RBAC
ABAC טהור איטי יותר מ-RBAC תחת מדיניות רבה—כל בדיקה עוברת על כל החוקים. בפועל, משתמשים בהיברידית: RBAC כשכבה ראשונה (בדיקה גסה ומהירה לפי תפקיד), ABAC כשנייה (חוקים קונטקסטואליים עדינים רק במקומות שצריך).
async function authorize(user, resource, action) { // ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа? if (!await rbac.canAccessResourceType(user.role, resource.type)) { return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам } // Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC return engine.evaluate(user, resource, action); }"}
Wait, the instruction says to restore proper newlines. The given code is a single line, but it contains comments and statements. I need to break it into multiple lines with proper indentation. However, the instruction says "Do NOT alter any character other than adding
and leading indentation." That means I cannot change the code itself, only add newlines and spaces for indentation. But the original code has no newlines, so I need to insert them at logical points.
Let me analyze the code:
async function authorize(user, resource, action) { // ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
Ð¾Ñ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа? if (!await rbac.canAccessResourceType(user.role, resource.type)) { return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам } // Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC return engine.evaluate(user, resource, action); }
I need to break it into lines. Typically, a function declaration would be:
async function authorize(user, resource, action) {
// comment
if (!await rbac.canAccessResourceType(user.role, resource.type)) {
return false;
}
// comment
return engine.evaluate(user, resource, action);
}
But the comments are inline. The original has comments after the opening brace and after the if statement. I need to preserve the exact text, but I can add newlines and indentation. However, the comments are part of the code, so I cannot move them. I can only insert newlines before and after them.
Let me look at the exact string:
"async function authorize(user, resource, action) { // ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа? if (!await rbac.canAccessResourceType(user.role, resource.type)) { return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам } // Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC return engine.evaluate(user, resource, action); }"
I need to insert newlines at appropriate places. The structure is:
- function signature
- opening brace
- comment (first)
- if statement
- opening brace of if
- return false;
- comment (second) after the semicolon? Actually it's "return false; // оÑÑекаем..." so the comment is after the semicolon.
- closing brace of if
- comment (third) "// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC"
- return engine.evaluate(...)
- closing brace of function
I need to put each statement on its own line, and comments on their own lines if they are standalone. But the comments are inline with code. For example, the first comment is after the opening brace, so it's on the same line as the brace. I could put it on a new line, but that would change the code? Actually, adding a newline after the brace is fine, but the comment is on the same line as the brace in the original. If I put a newline after the brace, then the comment would be on the next line, but the comment text is still there. That is allowed because I'm only adding newlines and indentation. However, I must not remove or change any characters, so the comment text remains exactly as is. But if I put a newline after the brace, then the comment is on a new line, but the comment is still attached to the brace? Actually, in JavaScript, a comment after a brace is fine. If I put a newline, it becomes a separate line. That is acceptable.
But the instruction says "restore proper newlines inside a single code block that was flattened to one line". So I need to break it into multiple lines as it would normally be formatted. I'll follow standard JavaScript formatting.
Let me write the reformatted code:
async function authorize(user, resource, action) {
// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?
if (!await rbac.canAccessResourceType(user.role, resource.type)) {
return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам
}
// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC
return engine.evaluate(user, resource, action);
}
But wait, the original comment is "// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?" and then immediately after that there is "if (!await..." without a newline. So I need to put a newline after the comment. Also, the comment is after the opening brace, so I need to put a newline after the brace and then the comment. But the comment is on the same line as the brace in the original. I can put a newline after the brace, then the comment, then a newline after the comment, then the if. That is fine.
Similarly, after the if statement's opening brace, there is "return false; // оÑÑекаем..." so I need to put a newline after the opening brace, then the return statement with the comment, then a newline after that, then the closing brace. But the comment is after the semicolon, so it's on the same line as the return. That is fine.
Then after the closing brace of the if, there is "// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC" and then "return engine.evaluate..." so I need to put a newline after the closing brace, then the comment, then a newline, then the return.
Finally, the closing brace of the function.
I must ensure that I don't change any characters. So I'll take the original string and insert newlines at specific positions.
Let me identify the exact positions. I'll copy the original string:
"async function authorize(user, resource, action) { // ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа? if (!await rbac.canAccessResourceType(user.role, resource.type)) { return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам } // Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC return engine.evaluate(user, resource, action); }"
I need to add newlines after the opening brace, after the first comment, after the if condition's opening brace, after the return statement, after the if's closing brace, after the second comment, and after the return statement? Actually, the return statement is the last one, so I need a newline before the final closing brace.
Let me break it down:
1. After "action) {" -> newline
2. Then the comment "// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?" -> newline after that
3. Then "if (!await rbac.canAccessResourceType(user.role, resource.type)) {" -> newline after that
4. Then "return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам" -> newline after that
5. Then "}" (closing brace of if) -> newline after that
6. Then "// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC" -> newline after that
7. Then "return engine.evaluate(user, resource, action);" -> newline after that
8. Then "}" (closing brace of function)
But I also need indentation. The standard is 2 spaces per level. So:
- Function body: 2 spaces
- Inside if: 4 spaces
So I'll add spaces accordingly.
Now, I need to produce a JSON object with the code string. The code string must have real newlines, so I'll use
in the JSON string.
Let me construct the reformatted code:
async function authorize(user, resource, action) {
// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?
if (!await rbac.canAccessResourceType(user.role, resource.type)) {
return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам
}
// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC
return engine.evaluate(user, resource, action);
}
I need to make sure that the comment text is exactly the same. In the original, there is a space after the //? Actually, it's "// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?" so there is a space after //. I'll keep that.
Also, the second comment is "// оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам" after the semicolon. I'll keep it on the same line as return false.
Now, I'll put this into a JSON string. I need to escape backslashes and quotes, but there are none. So I'll just put it.
But wait, the instruction says "Return JSON: {"code": "<reformatted text with real
>"}" So I need to output a JSON object with a key "code" and the value as a string with newlines. In JSON, newlines are represented as
. So I'll write the string with
.
Let me write the JSON:
{"code": "async function authorize(user, resource, action) {
// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?
if (!await rbac.canAccessResourceType(user.role, resource.type)) {
return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам
}
// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC
return engine.evaluate(user, resource, action);
}"}
I need to ensure that the indentation is correct. I used two spaces for the first level, four for the second. That is standard.
Now, I'll output this JSON.<ï½endâofâthinkingï½>{"code": "async function authorize(user, resource, action) {
// ÐÑÑÑÑÑй RBAC-check: еÑÑÑ Ð»Ð¸ Ñ Ñоли Ñ
оÑÑ ÐºÐ°ÐºÐ¾Ð¹-Ñо доÑÑÑп к ÑÑÐ¾Ð¼Ñ ÑÐ¸Ð¿Ñ ÑеÑÑÑÑа?
if (!await rbac.canAccessResourceType(user.role, resource.type)) {
return false; // оÑÑекаем без загÑÑзки обÑекÑа и пÑоÑ
ода по ABAC-полиÑикам
}
// Ð¢Ð¾Ð½ÐºÐ°Ñ Ð¿ÑовеÑка ÑеÑез ABAC
return engine.evaluate(user, resource, action);
} כיצד להעריך מורכבות פרויקט?
לוחות זמנים תלויים במספר המדיניות ובארכיטקטורה. אנו מבחינים בשלוש אפשרויות:
| אפשרות | לוח זמנים | מה כלול |
|---|---|---|
| בסיסי | 3–4 ימים | מנוע בקוד, 5–10 מדיניות, בדיקות |
| מתקדם | 7–10 ימים | מדיניות ב-DB, REST API, ממשק לעריכה, ביקורת |
| שילוב עם OPA | 2–3 ימים | פריסת Sidecar, כתיבת מדיניות Rego, בדיקות |
מה כלול ביישום ABAC?
- ניתוח מודל גישה ותכונות של משתמשים, משאבים, סביבה.
- עיצוב ותיעוד מדיניות גישה.
- פיתוח מנוע החלטות (או שילוב עם OPA/Casbin).
- יישום REST API לניהול מדיניות.
- יצירת מערכת ביקורת עם ויזואליזציה של יומנים.
- כיסוי בדיקות (יחידה + אינטגרציה).
- הגדרת פריסה וניטור.
- הדרכת צוות: כיצד לכתוב ולתקן מדיניות.
- אחריות קוד ותמיכה לאחר פריסה.
מנוע בסיסי עם מדיניות בקוד — 3–4 ימים. מנוע עם מדיניות ב-DB וממשק לעריכה — 7–10 ימים. הוספת יומן ביקורת עם ממשק — עוד 2–3 ימים. שילוב עם PDP של צד שלישי (Open Policy Agent, Casbin) במקום מנוע מותאם — 2–3 ימים לשילוב בתוספת זמן לכתיבת מדיניות ב-Rego או PERM.
Open Policy Agent הוא אלטרנטיבה בוגרת למנוע מותאם. מדיניות נכתבת ב-Rego, OPA רץ כ-sidecar או כשירות נפרד, והיישום מתקשר דרך HTTP או gRPC. זה מוסיף מורכבות תפעולית, אך מספק גרסאות מדיניות, טעינה חמה, וביקורת מובנית.
אם יש לך דרישות גישה מורכבות, צור קשר לביקורת. נבדוק את הפרויקט שלך. פנה אלינו לדיון בפרטים. הזמן יישום ABAC סוהר.







