Ontology + Physical DataGrant Fall-through — M4 Step 4 (#1250)
본 가이드는 Epic #1246 M4 종결 시점의 통합 ABAC engine — evaluate_combined_access — 동작을 다룹니다. M3 Step 1 의 ontology Class grant 평가 위에, 물리 데이터셋의 DataGrant 까지 fall-through 평가해 "ontology 가 비어 있어도 물리 권한이 있으면 통과" 하는 Unity Catalog 패턴 일관성을 확보합니다.
UI 진입점
/ontology 페이지에 Class 목록이 표시되고, 각 Class 에 정의된 data_source 가 fall-through 평가의 입력이 됩니다.

물리 DataGrant 는 /admin/policies 의 "접근 정책" 탭에서 관리합니다 (행 필터·컬럼 마스킹·역할별).

왜 fall-through 가 필요한가
| 시나리오 | M3 Step 1 (ontology-only) | M4 Step 4 (combined) |
|---|---|---|
| ontology grant 명시 allow | allow | allow (즉시) |
| ontology grant 명시 deny | deny | deny (즉시) |
| ontology grant 0건 + 물리 grant 있음 | role-based fall-through (ALLOW_NO_GRANT) — 운영 환경에서 의도와 무관하게 허용 | 물리 DataGrant 매치 검사 후 결정 |
| ontology grant 0건 + 물리 grant 0건 | role-based ALLOW_NO_GRANT (위험) | fail-closed deny |
운영 환경에서 ontology 메타데이터를 점진 도입할 때, ontology grant 가 비어 있다고 무조건 통과시키면 안전 정책이 무력화됩니다. M4 Step 4 는 물리 권한이라는 두 번째 방어선을 추가합니다.
평가 순서
핵심: 명시 정책 우선 — ontology layer 에서 allow / deny 가 명시되면 물리 layer 평가하지 않음.
코드 예시
from gend_api.services.abac import evaluate_combined_access
decision = await evaluate_combined_access(
db, class_row, caller, action="read",
)
if not decision.allow:
raise HTTPException(403, f"Access denied for class {class_row.name}")
# decision.decision_kind ∈ {
# "allow", "deny", "no_grant", "role_fallback",
# "physical_fallback_allow", "physical_fallback_deny",
# }
# decision.evaluated_layers ∈ {("ontology",), ("ontology", "physical")}
OntologyClass.data_source 와 DataGrant 매핑
OntologyClass.data_source (String 512) 는 catalog.schema.table 형식 (예: iceberg.lng.equipment_tag). 물리 fall-through 는 이 좌표를 기준으로 DataGrant 테이블을 검색.
DataGrant 계층 (Unity Catalog 패턴) — schema_name="*" / table_name="*" 와일드카드 사용:
| grantee_type | grantee_name | object_type | catalog | schema | table | privilege | 효과 |
|---|---|---|---|---|---|---|---|
| group | ops | catalog | iceberg | * | * | SELECT | iceberg.*.* read 권한 — iceberg.lng.equipment_tag 도 cover |
| user | alice | table | iceberg | lng | equipment_tag | ALL | 특정 테이블만 전권 |
| role | data_engineer | schema | iceberg | lng | * | INSERT | iceberg.lng.* write 권한 |
grantee_name 매칭: caller.user_id → caller.groups → caller.roles 순. 하나라도 매치되면 subject 인정.
action → physical privilege 매핑
| ontology action | 매치되는 DataGrant.privilege |
|---|---|
read | SELECT, USE, ALL |
write | INSERT, ALL |
delete | ALL |
메트릭
gend_ontology_grant_evaluations_total{decision} Counter 라벨에 두 시리즈 추가 (M3 Step 2 의 allow/deny/no_grant/role_fallback 위):
physical_fallback_allow— ontology no_grant 였지만 물리 grant 로 allowphysical_fallback_deny— ontology no_grant + 물리에서도 priv 부족/grantee mismatchdeny라벨에는 fail-closed deny (양쪽 layer 모두 매핑 0건) 도 합산됨
Grafana 에서 fall-through 사용 비율을 모니터링:
rate(gend_ontology_grant_evaluations_total{decision=~"physical_fallback_.*"}[5m])
/
rate(gend_ontology_grant_evaluations_total[5m])
이 비율이 높으면 ontology grant 가 충분히 정의되지 않은 상태 — ontology layer 마이그레이션 진척률 지표로 활용.
Fail-closed 정책
다음 케이스는 모두 403 Forbidden + deny 메트릭:
- ontology grant 0건 + Class.data_source 설정됨 + 물리 DataGrant 0건
- ontology grant 0건 + Class.data_source 형식 깨짐 (parts ≠ 3)
- ontology grant 0건 + 물리 평가 중 예외 발생 (logger.warning + deny)
마이그레이션 중 안전을 위해 — Class.data_source 가 None 일 때만 ontology layer 의 no_grant (= role-based ALLOW_NO_GRANT) 가 유지됩니다.
라우터 통합
apps/api/src/gend_api/routers/ontology.py 의 _enforce_class_abac 헬퍼가 M4 Step 4 부터 evaluate_class_access 대신 evaluate_combined_access 호출. Class CRUD / instances / properties 모든 endpoint 가 자동으로 fall-through 평가 받음.
async def _enforce_class_abac(db, class_row, caller, *, action):
decision = await evaluate_combined_access(
db, class_row, caller, action=action,
)
if not decision.allow:
raise HTTPException(403, ...)
테스트
- 단위:
apps/api/tests/test_ontology_combined_abac.py— 13 cases (decision matrix 전수) - 회귀:
tests/test_ontology_abac.py,tests/test_ontology_router.py— ontology-only 동작 유지 확인 - LNG 시나리오:
tests/test_ontology_lng_scenario.py— admin/viewer 통합 시나리오
후속
- ontology grant + 물리 DataGrant 충돌 시 차이 알림 (drift detector)
Class.data_source자동 검증 CLI (gend onto data-source --validate)- physical fall-through Grafana 패널 (
ontology-fall-through.json)