본문으로 건너뛰기

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).
Engineservices/ontology/abac.pyevaluate_class_access / filter_classes.
condition_expr DSLinstance.<field> <op> <literal> — restricted AST (Python eval() 금지).
Fail-closedgrant 미정의 → 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 in
  • and, or (short-circuit)
  • 리터럴: 숫자, 문자열, 리스트, 튜플, set, None
  • instance.&lt;field&gt; (단일 속성 접근만 — 중첩 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

EndpointAction동작
GET /classesread결과 row 에 filter_classes 적용 (deny 된 Class 는 응답에서 제외)
GET /classes/{id}read403 if deny
PATCH /classes/{id}write403 if deny
DELETE /classes/{id}delete403 if deny
GET /classes/{id}/propertiesread403 if deny
PUT /classes/{id}/with-propertieswrite403 if deny
POST /instances/{class_id}write403 if deny
GET /instances/{class_id}read403 if deny
GET /instances/{class_id}/{key}read403 if deny — instance.<field> 조건 가능
GET /instances/{class_id}/{key}/neighborsread403 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 가 도입되는 순간부터:

  1. 해당 Class+action 에 대한 명시 deny 가 우선
  2. caller 매치 grant 가 있고 condition_expr 모두 False → deny
  3. 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.pycondition_expr DSL (안전·unsafe), evaluate_class_access (no_grant/allow/deny/scope mismatch), filter_classes, 라우터 통합 (3 케이스) — 22
apps/api/tests/test_ontology_router.pyM1+M2 회귀 유지 (grant 0건 환경 backwards compat)

관련

  • Epic: #1232 Ontology M3 GA
  • 메모리: [[project_context_security]] fail-closed 정책
  • 후속: M3 Step 2 (메트릭 gend_ontology_grant_evaluations_total{decision})