item × action 권한을 Object-level Authorization으로 확장하기
Agent Tool의 Resource Authorization을 설계한 글에서는 operation 권한과 target 권한을 분리했습니다. Agent가 performance.read를 호출할 수 있어도 임의의 memberId를 조회할 수 있는 것은 아니므로, 기존 item × action permission 뒤에 actor-target 관계를 확인하는 경계가 필요했습니다.
이 문제를 Agent API에만 추가하면 같은 object-level 정책을 UI와 다른 internal API에서 다시 구현하게 됩니다. 기존 IAM 전체를 확인해보니 역할과 permission뿐 아니라 민감 데이터 분류, 탭 가시성, 커스텀 프로퍼티 접근, 조직 범위와 상태 필터가 서로 다른 위치에서 권한 결과에 참여하고 있었습니다.
현재 운영 중인 권한 엔진은 item × action RBAC입니다. 커스텀 프로퍼티의 item key 파생, permission catalog 동기화, 권한 부여 범위 검사와 Agent delegation의 조건 합성도 기존 코드에 있습니다. relation tuple, rewrite evaluator, 통합 context condition과 decision log는 이 동작을 분석해 정리한 전환 설계입니다.
현행 item × action 모델의 범위#
기존 IAM은 사용자가 가진 역할의 permission을 합쳐 요청한 item과 action을 허용할지 판정합니다.
allow = hasPermission(actor, item, action)이 구조에서 역할은 permission 묶음이고, 사용자는 여러 역할의 permission을 union으로 얻습니다. settings.* 기능 권한도 같은 방식으로 처리합니다. 병역, 보훈, 장애, 징계처럼 별도로 통제하는 민감 데이터는 이미 다른 item_key로 분류돼 있습니다.
마스킹은 READ permission으로 반환 필드를 사영하고, 탭 가시성은 하위 section 중 하나라도 READ가 있으면 노출하는 union 규칙입니다. 검토한 현행 정책 대부분은 역할 membership, permission assignment, union rewrite에 대응됐습니다.
반면 특정 memberId나 organizationId는 공통 authorization input에 포함되지 않습니다. 같은 엔진은 사용자가 기능을 사용할 기본 자격은 확인하지만 그 기능으로 어느 객체까지 접근할지는 답하지 못합니다. relation model이 추가로 다루는 범위는 이 object-level decision입니다.
Role과 permission의 relation 표현#
RBAC의 role membership과 permission assignment도 relation graph로 표현됩니다.
role:manager#member@user:42
item:member.performance#reader@role:manager#member첫 번째 tuple은 사용자가 역할의 구성원이라는 사실을, 두 번째 tuple은 그 역할의 구성원이 item을 읽을 수 있다는 사실을 나타냅니다. 기존 permission grid의 의미를 relation과 userset rewrite 안에서 보존하는 방식입니다.
기능 permission과 object relation은 같은 evaluator를 사용하더라도 정책상의 질문은 다릅니다. item과 action은 기능을 사용할 기본 자격을 확인하고, target relation은 그 자격이 특정 객체까지 닿는지 확인합니다.
allow
= RBAC.hasPermission(actor, item, action)
∧ ReBAC.check(actor, targetRef, relation)
∧ ContextAllows(applicableConditions, context)이 식은 세 검사가 모든 경로에서 항상 실행된다는 뜻이 아닙니다. operation이 target을 요구할 때만 relation이 참여하고, 해당 resource에 동적 조건이 있을 때만 context condition을 평가합니다. target이 필수인데 targetRef가 없으면 relation을 true로 두지 않고 거절합니다.
Google Zanzibar는 사용자와 객체의 관계를 tuple로 저장하고 relation과 userset rewrite를 이용해 접근 여부를 계산합니다. 이번 설계에서는 이 데이터 모델과 평가 의미론을 참고하지만 전역 분산 authorization service와 일관성 프로토콜까지 도입하지는 않습니다. 기존 IAM과 domain data가 있는 HRS 안에 in-process evaluator를 두는 방향입니다.
정적 속성의 relation 재료화#
커스텀 프로퍼티 권한은 처음에는 ABAC이 필요한 영역처럼 보였습니다. 접근에 사용하는 item이 isSystem, anchorEntity, nodeType, 상위 탭의 fieldKey에 따라 달라졌기 때문입니다.
현재 코드는 노드의 속성을 읽어 필요한 item key를 계산한 뒤 RBAC에 전달합니다.
CustomPropertyNode의 정적 속성
↓
resolveRequiredItemKey / customFieldGateForTopTab
↓
custom.field.personal 또는 custom.field.career
↓
RBAC permission check하지만 이 값들은 요청마다 바뀌는 actor나 환경의 속성이 아닙니다. 노드를 생성할 때 정해지고 수명주기 동안 유지되는 schema metadata입니다. CustomFieldValue에도 값의 소유자나 생성자를 기준으로 판정할 정보는 없습니다.
전환 설계에서는 이 분류를 요청마다 context condition으로 계산하지 않습니다. 노드를 생성하거나 분류를 변경할 때 resource와 permission category의 relation을 저장합니다. 정적 속성을 write-time relation으로 바꾸는 셈입니다.
Custom section 생성 또는 변경
↓
CustomSectionPermissionSync
↓
resource와 permission category의 relation provisioning
↓
요청 시 relation check기존 CustomSectionPermissionSync가 section lifecycle과 permission catalog를 동기화하므로 이 지점을 relation provisioning 경계로 사용하도록 설계했습니다. 현재 sync가 tuple까지 생성한다는 뜻은 아니며, 전환 시 relation write를 함께 수행하도록 확장하는 설계입니다.
Authzed의 attribute 모델링 가이드도 check 시점에만 알 수 있는 context가 아니라면 relation으로 표현할 것을 권장합니다. 특정 제품을 채택한 것은 아니지만 정적 속성과 동적 조건을 나누는 기준으로 참고했습니다.
요청 시점 조건의 분리#
대상의 현재 상태나 요청 시점이 판정에 필요하면 tuple만으로 결과를 고정하기 어렵습니다. 이런 값만 context condition으로 남깁니다. Zanzibar 계열 구현 중 SpiceDB는 유사한 조건부 relationship을 caveat라고 부릅니다.
커스텀 필드의 값 존재 여부에 따라 CREATE와 UPDATE를 선택하는 로직이 한 예입니다. 이 조건은 node schema가 아니라 현재 value state에 따라 달라집니다. 현재처럼 domain state를 읽어 action을 선택하고 두 permission을 각각 확인하거나, 공통 evaluator에 포함할 필요가 생기면 해당 경로의 context condition으로 등록합니다.
employmentStatus를 이용한 재직·퇴직 제외도 값이 계속 바뀌는 조건입니다. 다만 이 조건이 보안상 접근 금지인지 목록 표시 정책인지 먼저 구분해야 합니다. 표시 필터라면 query predicate로 남고, 접근 제어 정책이라면 공통 context condition으로 옮겨 개별 쿼리에 흩어지지 않게 합니다.
시점 조회나 평가 기간처럼 asOf, period, now가 필요한 정책도 request-time context에 해당합니다. 현재 authorization이 live relation만 사용한다면 미리 모델링하지 않고 실제 시점 기반 권한이 도입될 때 추가합니다.
HRS의 AgentDelegation에는 이런 합성의 운영 선례가 있습니다. delegation의 시간 유효성, 허용 Tool, thread binding, 원래 사용자의 현재 IAM을 각각 확인하고 모두 만족할 때만 요청을 허용합니다. 범용 caveat engine은 아니지만 관계와 요청 문맥을 한 decision에서 AND로 평가하는 패턴은 이미 사용하고 있습니다.
Write invariant와 relation provisioning#
역할이나 permission을 부여할 때 적용하는 다음 규칙은 특정 resource에 대한 접근 판정과 성격이 다릅니다.
requested grants ⊆ actor's current authority이 규칙은 권한을 가진 사용자가 자신보다 넓은 권한을 다른 역할에 부여하는 것을 막는 집합 불변식입니다. 하나의 relation이나 request context만으로는 판정되지 않으며, 변경 전후의 permission 집합을 같은 transaction 경계에서 비교합니다.
부여 범위 ⊆ 보유 범위는 ReBAC이나 context condition으로 옮기지 않고 application layer의 write guard로 유지합니다. authorization evaluator는 접근 가능 여부를 계산하고, application service는 권한 변경 transaction의 불변식을 보장합니다.
relation provisioning에도 별도의 write 경계가 필요합니다. 조직 변경과 tuple 갱신이 서로 다른 시점에 반영되면 authorization graph가 domain state보다 앞서거나 뒤처집니다. in-process evaluator를 사용하면 외부 authorization service와의 분산 일관성 문제는 줄지만, 조직 aggregate의 version 검사와 tuple 갱신 순서는 애플리케이션이 보장해야 합니다.
특히 조직 계층을 갱신하면서 상위 조직 관계와 하위 조직의 유효 범위를 함께 바꾸는 경로는 동시 수정의 영향을 받습니다. 관계를 계산하는 원본 aggregate에 version 기반 충돌 감지가 있어야 stale state로 만든 tuple이 최신 관계를 덮어쓰지 않습니다.
판정 근거와 전환 경계#
relation과 context condition을 도입해도 predicate가 여러 controller와 query에 흩어지면 정책을 추적하기 어렵습니다. employmentStatus처럼 표시 조건과 authorization 조건이 섞일 수 있는 값은 동일한 요청이 경로에 따라 다른 결과를 만들기도 합니다.
전환 설계에서는 authorization boundary가 다음 근거를 하나의 decision으로 남기도록 합니다.
operation / item / action
RBAC decision
targetRef / required relation / relation decision
applied context conditions / predicate result명시되지 않은 relation이나 condition은 추론해서 허용하지 않습니다. 새로운 item, resource type, relation을 추가할 때 policy mapping과 decision logging을 함께 등록해야 합니다.
현재 운영 경로에서 유지되는 것은 item × action RBAC, 정적 item key 파생, permission catalog sync와 application write guard입니다. relation tuple provisioning, rewrite evaluator, 조직 관계 동기화, context condition의 공통 평가와 decision log가 전환 범위에 해당합니다. 이 경계가 구현과 검증을 마치기 전까지 relation 기반 판정을 운영 중인 기능으로 서술하지 않습니다.
Object-level Authorization의 구성 기준#
Object-level Authorization을 추가한다고 기존 RBAC을 폐기하거나 모든 속성을 ABAC으로 옮길 필요는 없었습니다. 현행 permission vocabulary는 기능을 사용할 기본 자격을 판정하고, 대상과 actor의 관계는 relation으로 표현하며, 요청 시점에만 알 수 있는 조건만 context condition에 남깁니다. 권한 부여처럼 집합 전체의 일관성이 필요한 변경은 application transaction에서 별도로 지킵니다.
정적인 분류를 relation으로 재료화하면 요청마다 같은 속성을 다시 해석하지 않아도 됩니다. 현재 상태와 시간처럼 계속 변하는 값을 tuple로 고정하지 않으면 stale authorization data도 줄일 수 있습니다.
권한 모델을 확장할 때의 기준은 RBAC, ReBAC, ABAC 중 하나를 고르는 데 있지 않았습니다. 권한 결과에 필요한 각 사실이 언제 고정되고 어떤 데이터와 transaction 경계에서 검증되는지를 나눈 뒤, 접근 요청에 필요한 결과만 합성하는 것이 이번 전환 설계의 기준입니다.