diff --git a/docs/tenant-usergroup-policy.md b/docs/tenant-usergroup-policy.md index fb31b518..345b0b2d 100644 --- a/docs/tenant-usergroup-policy.md +++ b/docs/tenant-usergroup-policy.md @@ -1,68 +1,154 @@ -# 유저 그룹 및 테넌트 통합 권한 정책 (Integrated Policy) +# 통합 테넌트 및 권한 아키텍처 정책 (Integrated Tenant & ReBAC Policy) -이 문서는 Baron SSO의 테넌트(Tenant)와 유저 그룹(User Group) 간의 관계 및 권한 상속에 관한 공식 정책을 정의합니다. +이 문서는 Baron SSO 시스템 내 B2C(개인)부터 B2B(단일기업), B2B2B(그룹사) 및 사내 조직도(`UserGroup`)에 이르는 모든 격리 공간과 권한 상속을 다루는 **다형성 테넌트(Polymorphic Tenant)**의 공식 정책을 정의합니다. + +이 정책은 기존의 단순 `Tenant` 및 `UserGroup` 모델을 발전시켜, Ory Kratos(Identity), PostgreSQL(Business), Ory Keto(ReBAC) 3개의 분산 시스템 간 역할을 명확히 하고 데이터 정합성을 강제합니다. + +--- ## 1. 기본 원칙 (Core Axioms) -### 1.1 유저 그룹의 테넌트성 (User Group as a Tenant) -- **모든 테넌트가 유저 그룹은 아니지만, 모든 유저 그룹은 반드시 테넌트의 속성을 가집니다.** -- 유저 그룹은 "사용자들의 집합"인 동시에, 그 자체가 권한을 담고 다른 자원을 소유할 수 있는 **격리된 공간(Tenant)**으로 취급됩니다. +### 1.1 "모든 격리 공간은 테넌트이다" (Everything is a Tenant) +- 개인 워크스페이스, 기업 고객, 지주사, 그리고 사내의 특정 팀이나 본부(`UserGroup`) 등 **모든 종류의 격리 공간은 최상위 범용 단위인 `Tenant`로 취급**됩니다. +- 과거에 혼용되던 `UserGroup` 네임스페이스는 폐기되며, 격리 공간을 나타내는 Keto(ReBAC) 네임스페이스는 `Tenant` 하나로 단일화됩니다. 권한 상속 로직은 테넌트의 성격과 무관하게 동일하게 동작합니다. -### 1.2 권한 상속 로직의 단일화 (Unified Inheritance) -- 테넌트 간의 상속(Parent-Child Tenant)과 유저 그룹의 권한 전파(Group-Member)는 **기술적으로 동일한 ReBAC 로직**을 사용합니다. -- `UserGroup:members` 관계는 `Tenant:members`와 동일한 우선순위를 가지며, 시스템은 이를 구분 없이 하나의 상속 트리로 처리합니다. +### 1.2 "권한 주체와 자원의 분리" (Namespaces) +시스템의 완벽한 권한 통제를 위해, Keto에는 `Tenant` 외에도 다음과 같은 필수 네임스페이스가 존재합니다. +- **`Tenant`:** 격리된 공간 (회사, 부서, 개인) +- **`RelyingParty`:** 테넌트가 소유하는 자원/앱 (OIDC 클라이언트) +- **`System`:** 테넌트에 종속되지 않는 전역 권한 (Super Admin 등) -### 1.3 그룹장-어드민 연동 (Leader-Admin Mapping) -- 특정 유저 그룹에 명시적으로 **'그룹장(Group Leader)'**을 지정하면, 시스템은 해당 사용자를 해당 유저 그룹(테넌트)의 **'테넌트 어드민(Tenant Admin)'**으로 자동 격상합니다. -- 그룹장은 해당 그룹이 소유한 모든 하위 테넌트 및 리소스에 대해 완전한 제어권을 가집니다. +### 1.3 소유(Ownership)와 가시성(Visibility)의 분리 +- 시스템의 모든 자원(예: RelyingParty, 앱)은 반드시 특정 `Tenant`가 소유(`manage`)합니다. +- 그러나 자원의 소유권과 **누가 접근할 수 있는가(가시성, `access`)는 별개**입니다. 내부망용 앱(Private)과 대국민 서비스(Public)를 동일한 기업(Tenant)이 동시에 소유하고 제어할 수 있습니다. -## 2. 권한 흐름도 (Mermaid) +### 1.4 단일 진실 공급원 분리 (Separation of SoT) +- **Kratos (Identity):** "누구인가?" (인증, 이메일, 패스워드 등 순수 식별 정보). 테넌트, 직급 등 관계형 데이터는 절대 보관하지 않습니다. +- **PostgreSQL (Business):** "어디에 속하며 조직 구조는 어떠한가?" (직급, 조직도, 테넌트 설정 등). +- **Keto (ReBAC):** "무엇을 할 수 있는가?" (권한 및 상속). + +--- + +## 2. 하이브리드(다형성) 테넌트 아키텍처 + +데이터베이스의 `tenants` 테이블은 가장 가벼운 신분증(Identity) 역할을 수행하며, 세부 비즈니스 설정은 타입별로 별도의 테이블에 1:1 조인(Join)하여 관리합니다. + +### 2.1 테넌트 유형 (Tenant Types) +| 타입 (Enum) | 설명 | 특징 및 1:1 조인 테이블 | +| :--- | :--- | :--- | +| `PERSONAL` | B2C 개인 워크스페이스 | 일반 사용자 가입 시 1:1로 생성. 조직도(UserGroup) 기능 비활성화. | +| `COMPANY` | B2B 일반 기업/법인 | 독립된 비즈니스. 사내 조직도를 가짐. 무거운 설정은 `company_settings`에 저장. | +| `COMPANY_GROUP`| B2B2B 지주사/그룹사 | 여러 `COMPANY`를 하위로 거느리며 최고 관리자 권한을 통합. `company_settings` 조인. | +| `USER_GROUP` | 사내 조직 (본부/팀 등) | `COMPANY` 내부에 속하는 조직. 사내 조직도 메타데이터는 `user_groups` 테이블에 저장. | + +--- + +## 3. ReBAC 권한 상속 및 관계 튜플 (Keto Tuples) + +이전에는 `UserGroup` 네임스페이스가 존재했으나, 이제 모든 권한 검증은 오직 `Tenant` 네임스페이스 내에서 처리됩니다. + +### 3.1 조직장(Leader)과 어드민(Admin) 상속 +특정 부서(테넌트)의 그룹장(`owners`)으로 임명되면, 해당 부서 및 그 하위 부서의 최고 관리자(`admins`) 권한을 자동으로 상속받습니다. +- **조직장 임명:** `Tenant:<조직ID>#owners@User:<유저ID>` +- **자동 상속 룰:** `Tenant:<조직ID>#admins@Tenant:<조직ID>#owners` + +### 3.2 계층 간 권한 상속 (Hierarchy) +지주사(COMPANY_GROUP) $\rightarrow$ 법인(COMPANY) $\rightarrow$ 사내조직(USER_GROUP) 간의 상속은 모두 `parents` 튜플을 사용합니다. +- **계층 설정:** `Tenant:<하위ID>#parents@Tenant:<상위ID>` +- **자동 상속 룰:** 상위 테넌트의 `admins`는 하위 테넌트의 `manage` 및 `view` 권한을 모두 가집니다. + +### 3.3 리소스 제어 및 RP Admin (Relying Party) +RP(앱)는 별도의 가상 테넌트를 만들지 않고 자원(Object) 자체의 다중 상속을 통해 권한을 제어합니다. + +- **앱 관리 (Manage):** + - `RelyingParty:<앱ID>#parents@Tenant:<소유테넌트ID>` (앱 소유권 지정. 해당 테넌트의 최고 관리자가 앱을 관리할 수 있음) + - `RelyingParty:<앱ID>#admins@User:<유저ID>` (특정 유저를 **RP Admin**으로 직접 지정) +- **Private 앱 접근 (Access):** `RelyingParty:<앱ID>#access@Tenant:<소유테넌트ID>#members` (소유한 회사의 멤버만 접근) +- **Public 앱 접근 (Access):** `RelyingParty:<앱ID>#access@System:authenticated_users#members` (전역 인증 유저 누구나 접근) + +--- + +## 4. 권한 흐름도 (Mermaid) ```mermaid graph TD - %% Roles - Leader[Group Leader / 그룹장] - Member[Group Member / 멤버] - - %% Entities (Polymorphic) - subgraph UG_T [User Group / Specialized Tenant] - UG_ID[Group: Hanmac 운영팀] + %% Types + subgraph COMPANY_GROUP [지주사 테넌트] + CG[Tenant: 한맥 그룹] end - subgraph Child_T [Child Tenants / 하위 테넌트] - T1[Tenant: 한맥 엔지니어링] - T2[Tenant: 한맥 IT] + subgraph COMPANY [법인 테넌트] + C1[Tenant: 한맥 IT] end - %% Policy Links - Leader -- "Explicitly Assigned" --> UG_ID - Leader -. "Automatically Becomes" .-> Admin[Tenant Admin] - - Member -- "is member of" --> UG_ID - - %% Inheritance (Identical Logic) - UG_ID -- "Inherits Access To" --> T1 - UG_ID -- "Inherits Access To" --> T2 + subgraph USER_GROUP [사내조직 테넌트] + UG1[Tenant: 개발본부] + UG2[Tenant: 클라우드팀] + end - %% Effective Access - Admin -- "Full Control" --> UG_ID - Member -- "Shared Access" --> T1 - Member -- "Shared Access" --> T2 + subgraph USERS [사용자] + CEO[User: 그룹 최고경영자] + DEV_L[User: 팀장] + DEV[User: 팀원] + RP_ADM[User: RP 전담 관리자] + end + + subgraph RESOURCES [자원] + RP1[RelyingParty: 사내 인트라넷] + end + + %% Hierarchy Tuples + C1 -- "parents" --> CG + UG1 -- "parents" --> C1 + UG2 -- "parents" --> UG1 + RP1 -- "parents" --> C1 + + %% Memberships + CEO -- "owners" --> CG + DEV_L -- "owners" --> UG2 + DEV -- "members" --> UG2 + + %% RP Admin + RP_ADM -- "admins" --> RP1 + + %% Effective Admin Control (Inherited) + CEO -. "Inherits Admin Control" .-> C1 + CEO -. "Inherits Admin Control" .-> UG1 + CEO -. "Inherits Admin Control" .-> UG2 + CEO -. "Inherits Manage" .-> RP1 + DEV_L -. "Inherits Admin Control" .-> UG2 %% Styles - style UG_ID fill:#f9f,stroke:#333,stroke-width:2px - style Leader fill:#ff9,stroke:#333 - style Admin fill:#ffd,stroke:#333,stroke-dasharray: 5 5 + style CG fill:#dfd,stroke:#333 + style C1 fill:#dfd,stroke:#333 + style UG1 fill:#f9f,stroke:#333 + style UG2 fill:#f9f,stroke:#333 + style CEO fill:#ff9,stroke:#333 + style DEV_L fill:#ff9,stroke:#333 + style RP_ADM fill:#ff9,stroke:#333 + style RP1 fill:#bbf,stroke:#333 ``` -## 3. 기술적 구현 가이드 (Implementation) +--- -### 3.1 Keto Relationship Tuples -- **그룹장 임명:** `UserGroup:#owners@User:` -- **어드민 자동 승격:** `Tenant:#admins@UserGroup:#owners` (그룹 소유자는 해당 테넌트의 어드민) -- **멤버십:** `UserGroup:#members@User:` +## 5. 분산 시스템 정합성 강제 정책 (Data Consistency Check) -### 3.2 기대 효과 -- **정책 단순화:** '어드민'과 '그룹장'을 별도로 관리할 필요가 없어 시스템 복잡도가 감소합니다. -- **책임 명확화:** 그룹의 장이 해당 자원의 최종 책임자가 되는 직관적인 거버넌스를 수립합니다. -- **일관된 UX:** 사용자는 자신이 관리하는 것이 '테넌트'인지 '그룹'인지 고민할 필요 없이 동일한 관리 도구를 사용합니다. +인증(Kratos), 비즈니스 로직(PG), 권한(Keto)이 어긋나지 않도록 다음의 안전 장치를 반드시 구현해야 합니다. + +### 5.1 트랜잭셔널 아웃박스 패턴 (Transactional Outbox) +- **목적:** 백엔드 DB에는 저장되었으나, 일시적인 네트워크 장애로 인해 Keto(ReBAC)에 권한이 저장되지 않는 '부분 실패(Partial Failure)' 방지. +- **방식:** DB에 새로운 테넌트(`UserGroup` 등) 생성 시, 동일 DB 트랜잭션 내에서 `keto_outbox` 테이블에 튜플 이벤트를 함께 저장합니다. 이후 Background Worker가 이를 안전하게 Keto로 동기화(재시도 보장)합니다. + +### 5.2 삭제 정책 연동 (Delete Cascade) +- **보안 원칙 (즉시 회수):** 백엔드에서 테넌트나 유저를 Soft Delete(`deleted_at`) 처리하는 즉시, Outbox를 통해 **Keto의 관련된 모든 튜플을 Hard Delete**하여 권한을 즉각적으로 회수합니다. +- **Kratos 계정 삭제:** 사용자가 계정을 영구 탈퇴할 경우, Kratos에서 데이터가 지워지기 직전에 백엔드로 Webhook을 전송하여 Keto 권한 및 로컬 `users` 테이블의 프로필 데이터를 먼저 비우도록 연동합니다. + +### 5.3 Kratos-Backend 실시간 동기화 (Webhooks) +- Kratos에서의 회원 가입(`after_registration`) 및 정보 변경(`after_settings`) 발생 시, 즉시 Webhook을 발생시켜 Backend의 `users` 테이블(프로필 로컬 캐시)을 Upsert 합니다. + +### 5.4 정기 대사 스크립트 (Reconciliation Cron Job) +- 매일 1회 이상 배치 작업을 돌려 분산 시스템 간 엣지 케이스 오류를 복구합니다. +- **검증 항목:** + 1. Keto에 남아있는 고아 튜플(Orphaned Tuples) 검출 및 자동 삭제. + 2. DB의 `user_groups` 멤버십에는 존재하나 Keto 튜플에서 누락된 권한 색출 및 복원. + 3. Kratos와 Backend 간 유저 ID 매핑 상태 일치 여부 스캔. \ No newline at end of file