누구나 알아두면 도움이 될 인가 시스템 : Google의 Zanzibar와 ReBAC
안녕하세요. 앞선 글에서는 RBAC의 특징과 한계에 대하여 이야기를 나누어 보았습니다. 특히 동적인 환경에서의 유연성 부족, 세분화된 접근 제어의 어려움, 그리고 복잡한 관계 표현의 한계를 주요 문제로 언급했었는데요. 오늘은 이러한 문제들을 ReBAC(Relationship-Based Access Control)가 어떻게 접근하고 해결하였는지 ReBAC을 기반으로 만들어진 인가 시스템(Authorization System)인 Google의 Zanzibar를 통해 어떻게 실제로 구현되었는지 살펴보려고 합니다. 오늘의 이야기는 Google에서 2019년에 발표한 Zanzibar: Google’s Consistent, Global Authorization System 논문을 기반으로 이야기해보겠습니다.
RBAC의 한계와 ReBAC의 해결책
1. 동적 환경에서의 유연성 문제
RBAC에서는 사용자의 상황이나 문맥이 변경될 때마다 역할을 새로 정의하거나 수정해야 했습니다. 예를 들어, 조직 관리 시스템의 권한을 설정하기 위해 '조직장', '조직원', '급여 관리자' 같은 기본적인 역할을 정의하게 됩니다. 하지만 한 사람이 어떤 조직에서는 조직원이고, 다른 조직에서는 조직장일 수 있습니다. 이를 표현하기 위해서는 '조직A_조직원', '조직A_조직장', '조직B_조직원', '조직B_조직장'와 같이 각 조직별로 별도의 역할을 만들어야 합니다. 수백 개의 조직이 있다면, 그만큼 많은 역할이 필요하게 되죠.
대신 ReBAC 기반의 Zanzibar는 이 문제를 아래와 같은 형식의 'Relation Tuple'이라는 개념으로 해결합니다.
Relation Tuple은 namespace, object_id, relation, user 네가지 key로 정의합니다. 이를 위에서 언급한 사례를 아래와 같이 정리해볼 수 있습니다.
department:조직A#조직장@user123
// object : department:조직A
// relation : 조직장
// user : user123
department:조직B#조직원@user123
// object : department:조직B
// relation : 조직원
// user : user123
위와 같은 문법을 통하여 한 직원이 서로 다른 조직에서 다른 역할을 가질 수 있으며, 각 관계는 해당 조직의 컨텍스트 내에서만 의미를 가집니다. 새로운 조직이 신설될 때마다 별도의 역할을 만들 필요 없이, 단순히 새로운 Relation Tuple을 추가하는 방식으로 간단하게 대응이 가능합니다.
2. 세분화된 접근 제어 문제
기업 문서 관리 시스템 사례
RBAC에서 "조직장은 자신의 조직와 하위 조직의 모든 문서를 볼 수 있다"는 규칙을 구현하려면, 각 부서별로 별도의 역할('급여 관리자', '세일즈 매니저' 등)을 만들고, 각 문서에 대해 이러한 역할들과의 매핑을 개별적으로 관리해야 합니다. 조직 구조가 변경될 때마다 이러한 매핑을 모두 수정해야 하는 큰 관리 부담이 발생합니다.
Zanzibar에서는 이를 관계의 전파를 통해 자연스럽게 표현할 수 있습니다.
namespace document {
relation viewer {
userset_rewrite {
union {
this // 직접 viewer로 지정된 사용자
tuple_to_userset { // 문서가 속한 부서의 director를 찾아 권한 부여
tupleset { relation: "department" } // 문서와 부서의 관계를 찾고
computed_userset { // 해당 부서의 director 권한을 계산
object: "$DEPARTMENT"
relation: "director"
}
}
}
}
}
}
여기서 tuple_to_userset은 객체 간의 관계를 따라가면서 권한을 계산하는 메커니즘입니다.
위 코드는 아래의 플로우와 동일하게 동작합니다.
- 먼저 문서와 부서의 관계를 찾습니다 (document:doc123#department@department:marketing)
- 그다음 찾은 부서의 director 관계를 확인합니다 (department:marketing#director@user456)
- 최종적으로 user456이 doc123의 viewer 권한을 갖게 됩니다
이렇게 정의하면 조직 구조가 변경되더라도 권한 규칙을 수정할 필요가 없습니다. 부서의 계층 구조만 올바르게 유지되면 권한이 자동으로 전파됩니다.
3. 복잡한 관계 표현의 문제
소셜 미디어 프라이버시 설정 사례
RBAC로 "친구의 친구에게만 게시물을 공개"하는 정책을 구현하려면 매우 복잡해집니다. 사용자 A의 친구 목록을 조회하고, 다시 그 친구들의 친구 목록을 조회한 다음, 이들에게 개별적으로 권한을 부여해야 합니다. 더욱이 친구 관계가 변경될 때마다 이 모든 권한을 다시 계산하고 갱신해야 합니다.
Zanzibar에서는 이러한 복잡한 관계도 아래와 같이 간단하게 표현할 수 있습니다.
namespace social {
relation viewer {
userset_rewrite {
computed_userset { // 주어진 관계를 따라 사용자 집합을 계산
object: "$AUTHOR#friend" // 게시물 작성자의 친구 목록을 찾고
relation: "friend" // 그 친구들의 친구 목록을 다시 계산
}
}
}
}
여기서 computed_userset은 지정된 객체와 관계를 기반으로 권한을 가질 수 있는 사용자 집합을 계산합니다. 위 코드는 아래의 플로우와 동일하게 동작합니다.
- 먼저 Alice의 친구 목록을 찾습니다 (user:alice#friend@[bob, carol])
- 그다음 Bob과 Carol의 친구들을 찾습니다 (user:bob#friend@[dave, eve], user:carol#friend@[frank, george])
- 최종적으로 Dave, Eve, Frank, George가 게시물을 볼 수 있게 됩니다
이 정의는 "게시물 작성자의 친구의 친구"라는 복잡한 관계를 간단하게 표현합니다. 친구 관계가 변경되면 권한도 자동으로 업데이트되며, 별도의 권한 재계산 과정이 필요하지 않습니다.
이처럼 Zanzibar는 현실 세계의 복잡한 관계와 권한을 보다 자연스럽게 모델링할 수 있게 해줍니다. RBAC에서 필요했던 복잡한 역할 정의와 권한 매핑 대신, 객체 간의 관계를 직접적으로 표현함으로써 더 직관적이고 관리하기 쉽게 도와줍니다.
ReBAC의 문제와 해결
3년 이상의 운영 이후에 적은 내용인만큼 Google의 대규모 시스템에 실제 구현하고 운영하는 과정에서 여러가지 엔지니어링 문제를 만났음을 함께 언급했습니다.
먼저 논문에서 제시한 Zanzibar의 아키텍쳐를 보겠습니다.
위에서는 acl과 database 시스템만이 아닌 watch server / leopard 등 알 수 없는 시스템들도 보이는데요. 이에 대한 내용과 함께 어떤 문제를 어떻게 풀었는지 이야기해보겠습니다.
1. 데이터 일관성 문제
가장 먼저 언급된 문제는 분산 환경에서의 데이터 일관성 문제였습니다. 특히 "New Enemy" 문제는 심각한 보안 위험을 초래할 수 있었습니다. 아래에 문제가 될 수 있는 두가지 예시를 들어보겠습니다.
예제 A: ACL 업데이트 순서 무시
1. 앨리스가 밥을 폴더의 ACL에서 제거한다.
2. 앨리스가 찰리에게 새로운 문서를 폴더로 이동하도록 요청한다. 이때 문서 ACL은 폴더 ACL을 상속받는다.
3. 밥은 새로운 문서를 볼 수 없어야 하지만, 두 ACL 변경 간의 순서를 무시하면 볼 수 있게 된다.
예제 B: 오래된 ACL을 새로운 콘텐츠에 잘못 적용
1. 앨리스가 밥을 문서의 ACL에서 제거한다.
2. 앨리스가 찰리에게 문서에 새로운 콘텐츠를 추가하도록 요청한다.
3. 밥은 새로운 콘텐츠를 볼 수 없어야 하지만, 밥이 제거되기 전의 오래된 ACL로 평가되면 볼 수 있게 된다.
이러한 문제는 강한 일관성을 항상 보장하도록 강제함으로써 해결할 수 있지만, 글로벌 가용성과 지역적 성능이 중요한 서비스 특성상 더 섬세한 접근이 필요했습니다. Zanzibar는 이를 해결하기 위해 Spanner 데이터베이스와 Zookie 프로토콜을 도입했습니다.
Spanner 데이터베이스의 TrueTime을 활용한 외부 일관성 보장을 통해 모든 ACL 변경에 전역 순서를 부여하고, 이를 전역적으로 일관되게 적용할 수 있게 되었습니다.
Zookie 프로토콜은 콘텐츠 변경 시와 권한 검사 시 각각 다른 방식으로 작동합니다. 콘텐츠가 변경될 때는 최신 스냅샷에서 권한을 검사하고 새로운 zookie를 발급하여 콘텐츠와 함께 저장합니다. 이후 권한 검사가 필요할 때는 저장된 zookie의 타임스탬프를 확인하고 해당 시점 이후의 ACL 변경사항을 반영하여 권한을 평가합니다.
성능 최적화를 위해 Zanzibar는 요청을 두 가지 유형으로 분류합니다. 10초보다 오래된 주키를 가진 Safe 요청은 로컬 복제본으로 처리가 가능하여 빠른 응답 시간을 보장하며, 전체 요청의 대다수를 차지합니다. 반면 10초 미만의 주키를 가진 Recent 요청은 리더 복제본 접근이 필요하여 상대적으로 느린 응답 시간을 보이지만, 전체 요청 중 소수에 불과합니다.
이러한 요청 분류는 8초의 복제 지연을 고려하여 설계되었으며, 대부분의 요청을 로컬에서 처리할 수 있게 합니다. 아래 이미지와 같이 실제 데이터를 분석한 결과, Safe 요청이 압도적 다수를 차지하여 각 로컬 리전의 데이터로 많은 요청을 처리할 수 있게 되었고, 이는 조회 성능의 큰 향상으로 이어졌습니다.
2. 권한 변경의 실시간 전파와 Watch API
권한 모델이 복잡해질수록, 변경사항을 실시간으로 추적하고 반영하는 것이 중요한 과제가 되었습니다. 예를 들어, 검색 시스템은 문서의 ACL이 변경되면 즉시 검색 결과에서 해당 문서를 제외해야 하고, 협업 도구는 사용자의 권한이 박탈되면 즉시 편집 기능을 비활성화해야 합니다.
Zanzibar는 이를 위해 Watch API를 도입했습니다. Watch API는 마치 데이터베이스의 Change Data Capture(CDC)와 유사하게 작동합니다. 클라이언트는 특정 네임스페이스의 권한 변경을 감시 시작 시간을 특정할 수 있는 zookie를 기반으로 구독할 수 있으며, 시스템은 변경이 발생한 순서대로 이벤트를 스트리밍 방식으로 전달합니다.
이벤트는 항상 전역 순서를 따르며, 클라이언트는 마지막으로 처리한 zookie의 타임스탬프를 사용해 누락 없이 변경사항을 추적할 수 있습니다. 이를 통해 권한 변경이 시스템 전반에 신속하고 안정적으로 전파될 수 있게 되었습니다.
3. 깊은 관계 평가와 Leopard Sysyem
"조직의 조직장의 모든 직속 부하 직원들은 이 문서를 볼 수 있다"와 같은 복잡한 관계를 평가하는 것은 상당한 컴퓨팅 자원을 필요로 합니다. 특히 이러한 관계가 여러 단계로 중첩되면 평가 비용이 기하급수적으로 증가합니다.
Leopard Indexing System은 이 문제를 그래프의 도달 가능성 관점에서 접근했습니다. 관계를 미리 계산해두고 실시간 변경사항을 별도 레이어로 관리하는 방식으로, 복잡한 관계도 밀리초 단위로 평가할 수 있게 되었습니다. 예를 들어 A → B → C → D라는 관계가 있다면, "A는 D의 멤버이다"라는 정보를 미리 계산해두고, 중간 관계가 변경될 때만 실시간으로 재평가하는 방식으로 동작합니다. Leopard System은 Watch API를 통하여 튜플 변경의 전체 스트림을 수신하고, 이를 Leopard 튜플 추가, 업데이트 및 삭제의 시간 순서 스트림으로 변환합니다.
4. 인기 객체의 핫스팟 문제
갑자기 유명해진 YouTube 동영상처럼 특정 객체에 대한 권한 검사가 폭증할 때, 해당 객체의 ACL을 저장하는 서버가 병목이 될 수 있습니다. 이는 전체 시스템의 성능에 영향을 미칠 수 있는 심각한 문제입니다.
Zanzibar는 이를 해결하기 위해 동적 캐싱 전략을 도입했습니다. 특정 객체에 대한 요청이 임계값을 넘어서면, 해당 객체의 전체 ACL을 메모리에 캐시하고 모든 서버가 공유하도록 합니다. 또한 동일 시점의 동일한 권한 검사 요청들을 하나로 통합하여 처리함으로써, 데이터베이스 서버의 부하를 크게 줄일 수 있었습니다.
이러한 기술적 해결책과 함께, 성능 격리를 위한 여러 제한을 도입했습니다. 클라이언트별 CPU 사용량을 제한하고, 서버별로 동시 처리할 수 있는 요청 수를 제한했으며, 각 클라이언트에 대해 독립적인 잠금 테이블을 사용하여 서로 영향을 주지 않도록 했습니다.
간결하게 정리하긴 했으나, 핫스팟 섹션(3.2.5 Handling Hot Spots)은 이 파트만 하나의 글로 다뤄야 할만큼 다양한 방법을 통하여 문제를 접근하고 해결했습니다. 이 과정에서 많은 인사이트를 주었는데, 이 섹션은 한번 읽어보시는 것을 추천드립니다.
이번 글에서는 ReBAC의 특징과 이를 실제로 구현한 Google의 Zanzibar 시스템에 대해 살펴보았습니다. ReBAC는 RBAC의 한계를 극복하고 현대적인 서비스에서 요구되는 복잡한 접근 제어를 가능하게 만들었지만, 이를 대규모로 구현하는 것은 새로운 도전 과제였습니다. 하지만 동시에, 적절한 기술적 해결책들을 통해 이러한 도전들을 극복할 수 있다는 것도 증명했습니다.
특히 일관성, 성능, 확장성이라는 세 가지 핵심 과제를 해결하는 방식은 정말 많은 인사이트를 주고, 굳이 인가 시스템이 아니더라도 대규모 시스템을 설계할 때 좋은 아이디어가 될 것이라 확신합니다.
감사합니다.