Ontology Class ABAC — M3 Step 1
본 가이드는 Epic #1232 M3 Step 1 시점에서 활성화된 Class 단위 ABAC enforcement 를 다룹니다. M1 에서 OntologyClassGrant 모델만 정의됐고 라우터가 평가 안 했지만, M3 Step 1 부터 모든 Class CRUD/Instance 조회/Property 조회 endpoint 에 ABAC engine 적용.
개념
| 항목 | 설명 |
|---|---|
OntologyClassGrant | (subject, class, action, condition_expr) 4-tuple. M1 에서 모델 정의 (db/models/ontology.py). |
| Engine | services/ontology/abac.py — evaluate_class_access / filter_classes. |
condition_expr DSL | instance.<field> <op> <literal> — restricted AST (Python eval() 금지). |
| Fail-closed | grant 미정의 → role-based fall-through, 평가 실패 → 503 sanitized, deny → 403 sanitized. |
Grant row 예시
INSERT INTO ontology_class_grants
(layer, class_id, subject_id, action, condition_expr)
VALUES
('L2', '<eq-class-uuid>', 'analytics-team', 'read', NULL), -- group all-allow
('L2', '<eq-class-uuid>', 'jane@genon.ai', 'write', NULL), -- specific user
('L2', '<eq-class-uuid>', 'analytics-team', 'read', 'instance.severity != ''CRITICAL'''), -- conditional
('L2', '<eq-class-uuid>', 'admin', 'delete', NULL); -- role
subject_id는 Keycloak group path / user_id / role name 중 하나action ∈ {read, write, delete, grant}(CHECK constraint)condition_expr가 비어있으면 무조건 grant 매치
DSL — condition_expr
지원 구문
==, !=, <, <=, >, >=, in, not inand, or(short-circuit)- 리터럴: 숫자, 문자열, 리스트, 튜플, set, None
instance.<field>(단일 속성 접근만 — 중첩 X)-(unary minus)
차단 구문
- 함수 호출 (
instance.field(),globals()) - 모듈 import (
__import__) - 중첩 attribute (
instance.a.b) - 임의 변수 이름 (
instance외) eval/exec
예시
# Allow only non-critical instances
"instance.severity in ['INFO', 'WARN']"
# Pressure range
"instance.design_pressure > 0 and instance.design_pressure < 100"
# Region restriction
"instance.region == 'KR' or instance.region == 'US'"
실패 안전망
- 파싱 실패 / unknown name / 차단된 구문 →
False(deny) None < 70같은 TypeError →False(deny)- 모든 평가 실패는
logger.warning으로만 기록 — 호출자 라우터는 정상 403
라우터 enforcement
| Endpoint | Action | 동작 |
|---|---|---|
GET /classes | read | 결과 row 에 filter_classes 적용 (deny 된 Class 는 응답에서 제외) |
GET /classes/{id} | read | 403 if deny |
PATCH /classes/{id} | write | 403 if deny |
DELETE /classes/{id} | delete | 403 if deny |
GET /classes/{id}/properties | read | 403 if deny |
PUT /classes/{id}/with-properties | write | 403 if deny |
POST /instances/{class_id} | write | 403 if deny |
GET /instances/{class_id} | read | 403 if deny |
GET /instances/{class_id}/{key} | read | 403 if deny — instance.<field> 조건 가능 |
GET /instances/{class_id}/{key}/neighbors | read | 403 if deny |
POST /classes (Class 생성) 은 ABAC 평가 대상 아님 — 신규 Class 는 아직 grant 가 없으므로 role-based (analyst+) 만으로 제어.
Backwards compatibility
Grant 가 0건인 기존 환경에서는 ALLOW_NO_GRANT fall-through → role-based (viewer/analyst) 만으로 모든 endpoint 통과. 사용자 액션 변화 0.
Grant 가 도입되는 순간부터:
- 해당 Class+action 에 대한 명시 deny 가 우선
- caller 매치 grant 가 있고
condition_expr모두 False → deny - caller 매치 grant 0건 → deny ("explicit grant required" 모드)
Fall-through 정책 (M3 후속)
현재: ontology Class grant 만 평가. 물리 DataGrant 와 fall-through 는 M3 Step 2~4 에서 통합 예정.
향후 계획:
- ontology grant → physical DataGrant → fail-closed
- 둘 다 deny / 결과 없음 → reject
- [[project_context_security]] fail-closed 정책 준수
회귀 가드
| 파일 | 범위 |
|---|---|
apps/api/tests/test_ontology_abac.py | condition_expr DSL (안전·unsafe), evaluate_class_access (no_grant/allow/deny/scope mismatch), filter_classes, 라우터 통합 (3 케이스) — 22 |
apps/api/tests/test_ontology_router.py | M1+M2 회귀 유지 (grant 0건 환경 backwards compat) |
관련
- Epic: #1232 Ontology M3 GA
- 메모리: [[project_context_security]] fail-closed 정책
- 후속: M3 Step 2 (메트릭
gend_ontology_grant_evaluations_total{decision})