forked from baron/baron-sso
네임스페이스 확장 및 정책 문서 동기화
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Namespace, Subject, Context, SubjectSet } from "@ory/keto-definitions"
|
||||
import { Namespace, Context, SubjectSet } from "@ory/keto-definitions"
|
||||
|
||||
class User implements Namespace {}
|
||||
|
||||
@@ -20,6 +20,8 @@ class Tenant implements Namespace {
|
||||
admins: (User | SubjectSet<System, "super_admins">)[]
|
||||
members: (User | SubjectSet<System, "super_admins"> | SubjectSet<Tenant, "admins"> | SubjectSet<Tenant, "owners">)[]
|
||||
parents: Tenant[]
|
||||
developer_console_viewer: (User | SubjectSet<System, "super_admins">)[]
|
||||
developer_console_grant_manager: (User | SubjectSet<System, "super_admins">)[]
|
||||
}
|
||||
|
||||
permits = {
|
||||
@@ -39,7 +41,18 @@ class Tenant implements Namespace {
|
||||
this.related.parents.traverse((p) => p.permits.manage_admins(ctx)),
|
||||
|
||||
create_subtenant: (ctx: Context): boolean =>
|
||||
this.permits.manage(ctx)
|
||||
this.permits.manage(ctx),
|
||||
|
||||
view_dev_console: (ctx: Context): boolean =>
|
||||
this.related.developer_console_viewer.includes(ctx.subject) ||
|
||||
this.permits.grant_dev_permissions(ctx) ||
|
||||
this.permits.manage(ctx) ||
|
||||
this.related.parents.traverse((p) => p.permits.view_dev_console(ctx)),
|
||||
|
||||
grant_dev_permissions: (ctx: Context): boolean =>
|
||||
this.related.developer_console_grant_manager.includes(ctx.subject) ||
|
||||
this.permits.manage_admins(ctx) ||
|
||||
this.related.parents.traverse((p) => p.permits.grant_dev_permissions(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,17 +61,75 @@ class RelyingParty implements Namespace {
|
||||
admins: (User | SubjectSet<System, "super_admins"> | SubjectSet<Tenant, "admins"> | SubjectSet<Tenant, "owners">)[]
|
||||
parents: Tenant[]
|
||||
access: (User | SubjectSet<Tenant, "members"> | SubjectSet<System, "authenticated_users"> | SubjectSet<System, "super_admins">)[]
|
||||
creator: (User | SubjectSet<System, "super_admins">)[]
|
||||
config_editor: (User | SubjectSet<System, "super_admins">)[]
|
||||
secret_rotator: (User | SubjectSet<System, "super_admins">)[]
|
||||
jwks_viewer: (User | SubjectSet<System, "super_admins">)[]
|
||||
jwks_operator: (User | SubjectSet<System, "super_admins">)[]
|
||||
consent_viewer: (User | SubjectSet<System, "super_admins">)[]
|
||||
consent_revoker: (User | SubjectSet<System, "super_admins">)[]
|
||||
relationship_viewer: (User | SubjectSet<System, "super_admins">)[]
|
||||
status_operator: (User | SubjectSet<System, "super_admins">)[]
|
||||
}
|
||||
|
||||
permits = {
|
||||
view: (ctx: Context): boolean =>
|
||||
this.related.admins.includes(ctx.subject) ||
|
||||
this.related.parents.traverse((t) => t.permits.view(ctx)),
|
||||
this.related.config_editor.includes(ctx.subject) ||
|
||||
this.related.secret_rotator.includes(ctx.subject) ||
|
||||
this.related.jwks_viewer.includes(ctx.subject) ||
|
||||
this.related.jwks_operator.includes(ctx.subject) ||
|
||||
this.related.consent_viewer.includes(ctx.subject) ||
|
||||
this.related.consent_revoker.includes(ctx.subject) ||
|
||||
this.related.relationship_viewer.includes(ctx.subject) ||
|
||||
this.related.status_operator.includes(ctx.subject) ||
|
||||
this.related.parents.traverse((t) => t.permits.view(ctx)) ||
|
||||
this.related.parents.traverse((t) => t.permits.view_dev_console(ctx)),
|
||||
|
||||
manage: (ctx: Context): boolean =>
|
||||
this.related.admins.includes(ctx.subject) ||
|
||||
this.related.parents.traverse((t) => t.permits.manage(ctx)),
|
||||
|
||||
create: (ctx: Context): boolean =>
|
||||
this.related.creator.includes(ctx.subject) ||
|
||||
this.related.parents.traverse((t) => t.permits.grant_dev_permissions(ctx)) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
edit_config: (ctx: Context): boolean =>
|
||||
this.related.config_editor.includes(ctx.subject) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
rotate_secret: (ctx: Context): boolean =>
|
||||
this.related.secret_rotator.includes(ctx.subject) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
view_jwks: (ctx: Context): boolean =>
|
||||
this.related.jwks_viewer.includes(ctx.subject) ||
|
||||
this.permits.operate_jwks(ctx) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
operate_jwks: (ctx: Context): boolean =>
|
||||
this.related.jwks_operator.includes(ctx.subject) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
view_consents: (ctx: Context): boolean =>
|
||||
this.related.consent_viewer.includes(ctx.subject) ||
|
||||
this.permits.revoke_consents(ctx) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
revoke_consents: (ctx: Context): boolean =>
|
||||
this.related.consent_revoker.includes(ctx.subject) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
view_relationships: (ctx: Context): boolean =>
|
||||
this.related.relationship_viewer.includes(ctx.subject) ||
|
||||
this.related.parents.traverse((t) => t.permits.grant_dev_permissions(ctx)) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
change_status: (ctx: Context): boolean =>
|
||||
this.related.status_operator.includes(ctx.subject) ||
|
||||
this.permits.manage(ctx),
|
||||
|
||||
access: (ctx: Context): boolean =>
|
||||
this.related.access.includes(ctx.subject) ||
|
||||
this.permits.manage(ctx)
|
||||
|
||||
@@ -34,13 +34,18 @@ classDiagram
|
||||
<<Namespace>>
|
||||
-- Relations --
|
||||
owners: User[]
|
||||
admins: User[] | SubjectSet~Tenant, owners~
|
||||
admins: User[] | SubjectSet~System, super_admins~
|
||||
members: User[]
|
||||
parents: Tenant[]
|
||||
developer_console_viewer: User[]
|
||||
developer_console_grant_manager: User[]
|
||||
-- Permits --
|
||||
view: members OR admins OR parents.view
|
||||
manage: admins OR parents.manage
|
||||
manage: admins OR owners OR parents.manage
|
||||
manage_admins: owners OR parents.manage_admins
|
||||
create_subtenant: manage
|
||||
view_dev_console: developer_console_viewer OR grant_dev_permissions OR manage OR parents.view_dev_console
|
||||
grant_dev_permissions: developer_console_grant_manager OR manage_admins OR parents.grant_dev_permissions
|
||||
}
|
||||
|
||||
class RelyingParty {
|
||||
@@ -49,19 +54,38 @@ classDiagram
|
||||
admins: User[]
|
||||
parents: Tenant[]
|
||||
access: User[] | SubjectSet~Tenant, members~ | SubjectSet~System, authenticated_users~
|
||||
creator: User[]
|
||||
config_editor: User[]
|
||||
secret_rotator: User[]
|
||||
jwks_viewer: User[]
|
||||
jwks_operator: User[]
|
||||
consent_viewer: User[]
|
||||
consent_revoker: User[]
|
||||
relationship_viewer: User[]
|
||||
status_operator: User[]
|
||||
-- Permits --
|
||||
view: admins OR parents.view
|
||||
view: admins OR direct operator relations OR parents.view OR parents.view_dev_console
|
||||
manage: admins OR parents.manage
|
||||
create: creator OR parents.grant_dev_permissions OR manage
|
||||
edit_config: config_editor OR manage
|
||||
rotate_secret: secret_rotator OR manage
|
||||
view_jwks: jwks_viewer OR operate_jwks OR manage
|
||||
operate_jwks: jwks_operator OR manage
|
||||
view_consents: consent_viewer OR revoke_consents OR manage
|
||||
revoke_consents: consent_revoker OR manage
|
||||
view_relationships: relationship_viewer OR parents.grant_dev_permissions OR manage
|
||||
change_status: status_operator OR manage
|
||||
access: access OR manage
|
||||
}
|
||||
|
||||
%% Relationship lines indicating references (SubjectSets or Direct inclusion)
|
||||
User ..> System : super_admins, authenticated_users
|
||||
User ..> Tenant : owners, admins, members
|
||||
User ..> RelyingParty : admins, access
|
||||
User ..> Tenant : owners, admins, members, developer_console_*
|
||||
User ..> RelyingParty : admins, access, operators
|
||||
|
||||
Tenant "1" --> "*" Tenant : parents (상위 조직 상속)
|
||||
Tenant ..> RelyingParty : parents (소유권 상속)
|
||||
Tenant ..> RelyingParty : view_dev_console / grant_dev_permissions (범위 권한)
|
||||
Tenant ..> RelyingParty : access (members 접근 권한)
|
||||
|
||||
System ..> RelyingParty : access (authenticated_users)
|
||||
@@ -77,11 +101,21 @@ classDiagram
|
||||
|
||||
- **Tenant (테넌트/조직):**
|
||||
- `view` (조회): 테넌트의 일반 멤버(`members`), 관리자(`admins`), 그리고 **상위 테넌트(parents)에서 조회 권한을 가진 자**가 조회할 수 있습니다.
|
||||
- `manage` (관리): 테넌트의 관리자(`admins`), 그리고 **상위 테넌트(parents)에서 관리 권한을 가진 자**가 관리할 수 있습니다.
|
||||
- _참고:_ 조직장(`owners`)은 자동으로 `admins` 집합(SubjectSet)에 포함됩니다.
|
||||
- `manage` (관리): 테넌트의 관리자(`admins`), 조직장(`owners`), 그리고 **상위 테넌트(parents)에서 관리 권한을 가진 자**가 관리할 수 있습니다.
|
||||
- `manage_admins`: 조직장(`owners`)과 상위 테넌트의 `manage_admins` 상속 권한으로 판정합니다.
|
||||
- `view_dev_console`: 직접 부여된 DevFront 조회 relation, `grant_dev_permissions`, `manage`, 상위 tenant 상속으로 판정합니다.
|
||||
- `grant_dev_permissions`: 직접 부여된 DevFront 권한 부여 relation, `manage_admins`, 상위 tenant 상속으로 판정합니다.
|
||||
|
||||
- **RelyingParty (OIDC 앱):**
|
||||
- `view` (조회): 앱의 직접 관리자(`admins`) 또는 **이 앱을 소유한 테넌트(parents)에서 조회 권한을 가진 자**가 조회할 수 있습니다.
|
||||
- `view` (조회): 앱의 직접 관리자(`admins`), 직접 운영 relation 보유자(`config_editor`, `jwks_viewer` 등), 또는 **이 앱을 소유한 테넌트(parents)에서 `view` 또는 `view_dev_console` 권한을 가진 자**가 조회할 수 있습니다.
|
||||
- `manage` (관리): 앱의 직접 관리자(`admins`) 또는 **이 앱을 소유한 테넌트(parents)에서 관리 권한을 가진 자**가 관리할 수 있습니다.
|
||||
- `edit_config`, `rotate_secret`, `operate_jwks`, `revoke_consents`, `change_status`: 각 직접 relation 또는 `manage`로 판정합니다.
|
||||
- `view_relationships`: 직접 `relationship_viewer`, 상위 tenant의 `grant_dev_permissions`, 또는 `manage`로 판정합니다.
|
||||
- `access` (접근/로그인 가능 여부): 이 앱에 직접 접근 권한을 부여받은 유저/그룹(`access`), 또는 앱을 관리할 수 있는 권한(`manage`)을 가진 사람이 접근할 수 있습니다.
|
||||
- _접근 대상(access)은 특정 유저, 특정 테넌트의 전 멤버, 또는 전역 인증된 유저(System:authenticated_users)가 될 수 있습니다._
|
||||
|
||||
### 설계 원칙 메모
|
||||
|
||||
- `view_dev_console`는 RP 목록/기본 정보 조회 범위를 주는 tenant 범위 권한입니다.
|
||||
- `view_dev_console`만으로 RP의 개별 운영 액션 permit이 자동 부여되지는 않습니다.
|
||||
- `manage`는 1차 하위호환 permit으로 유지하며, 세부 permit이 완전히 backend/API에 반영되기 전까지 상위 호환 의미를 가집니다.
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
따라서 조직도나 권한이 변경될 때마다 이를 Keto의 관계 튜플로 실시간 변환/전송하고, 권한 검증(Check)은 초고속 병렬 처리가 가능한 Keto 엔진으로 오프로딩(Offloading)합니다.
|
||||
|
||||
## 2. 네임스페이스 (Namespaces)
|
||||
과거 혼용되던 `UserGroup` 네임스페이스는 폐기되며, 철저한 권한 통제를 위해 아래 3개의 네임스페이스만 존재합니다.
|
||||
과거 혼용되던 `UserGroup` 네임스페이스는 폐기되며, 현재 Baron SSO는 아래 4개의 네임스페이스를 기준으로 ReBAC를 구성합니다.
|
||||
|
||||
1. **`Tenant`**: 모든 격리 공간 (회사, 지주사, 사내 부서, 개인 워크스페이스)
|
||||
2. **`RelyingParty`**: 테넌트가 소유하는 자원/앱 (OIDC 클라이언트)
|
||||
3. **`System`**: 테넌트에 종속되지 않는 전역 권한 (Super Admin 등)
|
||||
1. **`User`**: 권한의 subject가 되는 사용자
|
||||
2. **`Tenant`**: 모든 격리 공간 (회사, 지주사, 사내 부서, 개인 워크스페이스, 유저 그룹)
|
||||
3. **`RelyingParty`**: 테넌트가 소유하는 자원/앱 (OIDC 클라이언트)
|
||||
4. **`System`**: 테넌트에 종속되지 않는 전역 권한 (Super Admin 등)
|
||||
|
||||
## 3. 관계 튜플 규칙 (Relationship Tuples)
|
||||
|
||||
@@ -20,6 +21,18 @@
|
||||
- **어드민 자동 상속**: `Tenant:<조직ID>#admins@Tenant:<조직ID>#owners`
|
||||
- **테넌트 계층(부모-자식)**: `Tenant:<하위ID>#parents@Tenant:<상위ID>`
|
||||
*(상위 테넌트의 `admins`는 하위 테넌트의 모든 권한을 상속받습니다.)*
|
||||
- **DevFront 조회 범위 부여**: `Tenant:<조직ID>#developer_console_viewer@User:<유저ID>`
|
||||
- **DevFront 권한 부여 범위 부여**: `Tenant:<조직ID>#developer_console_grant_manager@User:<유저ID>`
|
||||
|
||||
### 3.1.1 Tenant permit 원칙
|
||||
- `view`: 멤버/관리자/상위 tenant 상속 기준의 tenant 조회 권한
|
||||
- `manage`: 관리자/오너/상위 tenant 상속 기준의 tenant 관리 권한
|
||||
- `manage_admins`: 오너 및 상위 tenant 상속 기준의 관리자 관계 관리 권한
|
||||
- `create_subtenant`: `manage`를 가진 주체가 하위 tenant를 생성하는 권한
|
||||
- `view_dev_console`: DevFront 진입 및 tenant 범위 RP 목록/기본 정보 조회 권한
|
||||
- `grant_dev_permissions`: tenant 범위 RP 운영 관계를 부여/회수할 수 있는 상위 권한
|
||||
|
||||
`view_dev_console`와 `grant_dev_permissions`는 `Tenant#manage`와 별개 축으로 분리합니다. 다만 1차 구현에서는 하위호환을 위해 `manage` 또는 `manage_admins`를 가진 주체가 각각 `view_dev_console`, `grant_dev_permissions`도 함께 가지는 모델로 둡니다.
|
||||
|
||||
### 3.2 Relying Party (앱 자원) 제어 및 RP Admin
|
||||
RP에 별도의 가상 테넌트를 만들지 않고, 자원 객체 자체의 다중 상속을 사용합니다.
|
||||
@@ -27,6 +40,44 @@ RP에 별도의 가상 테넌트를 만들지 않고, 자원 객체 자체의
|
||||
- **전담 관리자(RP Admin) 직접 할당**: `RelyingParty:<앱ID>#admins@User:<유저ID>`
|
||||
- **Private 앱 접근 허용**: `RelyingParty:<앱ID>#access@Tenant:<소유테넌트ID>#members`
|
||||
- **Public 앱 접근 허용**: `RelyingParty:<앱ID>#access@System:authenticated_users#members`
|
||||
- **RP 생성 권한 부여**: `RelyingParty:<앱ID>#creator@User:<유저ID>`
|
||||
- **RP 설정 수정 권한 부여**: `RelyingParty:<앱ID>#config_editor@User:<유저ID>`
|
||||
- **Client Secret rotate 권한 부여**: `RelyingParty:<앱ID>#secret_rotator@User:<유저ID>`
|
||||
- **JWKS 조회 권한 부여**: `RelyingParty:<앱ID>#jwks_viewer@User:<유저ID>`
|
||||
- **JWKS 운영 권한 부여**: `RelyingParty:<앱ID>#jwks_operator@User:<유저ID>`
|
||||
- **Consent 조회 권한 부여**: `RelyingParty:<앱ID>#consent_viewer@User:<유저ID>`
|
||||
- **Consent 회수 권한 부여**: `RelyingParty:<앱ID>#consent_revoker@User:<유저ID>`
|
||||
- **Relationship 조회 권한 부여**: `RelyingParty:<앱ID>#relationship_viewer@User:<유저ID>`
|
||||
- **상태 변경 권한 부여**: `RelyingParty:<앱ID>#status_operator@User:<유저ID>`
|
||||
|
||||
### 3.2.1 RelyingParty permit 원칙
|
||||
- `view`: RP 상세 및 기본 메타데이터 조회 권한
|
||||
- `manage`: 기존 호환용 상위 관리 권한
|
||||
- `create`: RP 생성 권한
|
||||
- `edit_config`: RP 일반 설정 수정 권한
|
||||
- `rotate_secret`: client secret 재발급/rotate 권한
|
||||
- `view_jwks`: JWKS 상태/캐시/key summary 조회 권한
|
||||
- `operate_jwks`: JWKS refresh/revoke 수행 권한
|
||||
- `view_consents`: consent 목록/상세 조회 권한
|
||||
- `revoke_consents`: consent 회수 권한
|
||||
- `view_relationships`: direct / inherited relationship 조회 권한
|
||||
- `change_status`: 활성/비활성 상태 변경 권한
|
||||
- `access`: 실제 서비스 로그인 및 리소스 접근 권한
|
||||
|
||||
1차 구현 원칙은 다음과 같습니다.
|
||||
- `RelyingParty#manage`는 제거하지 않고 유지합니다.
|
||||
- `manage`는 신규 세부 permit의 상위 호환 permit으로 동작합니다.
|
||||
- `access`는 서비스 접근 권한이며 DevFront 운영 권한과 동일시하지 않습니다.
|
||||
- `Tenant#view_dev_console`는 RP 목록/기본 정보 조회 범위를 주지만, `edit_config`, `operate_jwks`, `revoke_consents` 같은 개별 운영 액션 permit을 자동 부여하지 않습니다.
|
||||
- RP 개별 운영 액션은 `RelyingParty` 세부 permit으로 직접 판정합니다.
|
||||
|
||||
### 3.3 유저 그룹 subject set 규칙
|
||||
현재 구현에서 유저 그룹은 별도 `UserGroup` namespace를 사용하지 않고, `Tenant` namespace 내부의 유저 그룹 tenant와 subject set으로 표현합니다.
|
||||
|
||||
- **유저 그룹 멤버십**: `Tenant:<GroupTenantID>#members@User:<UserID>`
|
||||
- **유저 그룹 전체에 tenant role 부여**: `Tenant:<TenantID>#<Relation>@Tenant:<GroupTenantID>#members`
|
||||
|
||||
즉, 문서에서 말하는 “유저 그룹 멤버 전체”는 실제 Keto tuple에서 `Tenant:<groupId>#members` subject set으로 표현됩니다.
|
||||
|
||||
## 4. 트랜잭셔널 아웃박스를 통한 정합성 확보
|
||||
Keto와의 데이터 일관성 문제는 시스템의 치명적인 아킬레스건입니다.
|
||||
|
||||
@@ -57,13 +57,13 @@ Ory Keto 내부적으로는 다음과 같은 관계 튜플(Relationship Tuples)
|
||||
|
||||
### 3.1 그룹 멤버십 (Group Membership)
|
||||
사용자를 특정 유저 그룹의 멤버로 등록합니다.
|
||||
- **Tuple:** `UserGroup:<GroupID>#members@User:<UserID>`
|
||||
- **의미:** `UserID` 사용자는 `GroupID` 유저 그룹의 멤버이다.
|
||||
- **Tuple:** `Tenant:<GroupID>#members@User:<UserID>`
|
||||
- **의미:** `GroupID`에 해당하는 유저 그룹 tenant의 멤버로 `UserID` 사용자를 등록한다.
|
||||
|
||||
### 3.2 테넌트 권한 할당 (Tenant Role Assignment)
|
||||
유저 그룹 전체에 특정 테넌트에 대한 역할을 부여합니다.
|
||||
- **Tuple:** `Tenant:<TenantID>#<Relation>@UserGroup:<GroupID>#members`
|
||||
- **의미:** `GroupID` 유저 그룹의 모든 멤버는 `TenantID` 테넌트에 대해 `<Relation>`(예: `view`, `manage`, `admins`) 권한을 가진다.
|
||||
- **Tuple:** `Tenant:<TenantID>#<Relation>@Tenant:<GroupID>#members`
|
||||
- **의미:** `GroupID` 유저 그룹 tenant의 모든 멤버는 `TenantID` 테넌트에 대해 `<Relation>`(예: `view`, `manage`, `admins`) 권한을 가진다.
|
||||
|
||||
### 3.3 자원 소유 및 전파 (Resource Ownership)
|
||||
테넌트가 소유한 하위 자원(RP, API Key 등)에 대한 권한 전파 규칙입니다.
|
||||
@@ -76,7 +76,13 @@ Ory Keto 내부적으로는 다음과 같은 관계 튜플(Relationship Tuples)
|
||||
2. **복합 권한 구성:** 하나의 그룹이 여러 테넌트에 대해 서로 다른 수준의 권한을 가질 수 있어, 실제 조직 구조와 프로젝트 협업 모델을 유연하게 반영할 수 있습니다.
|
||||
3. **Zanzibar 스타일 확장성:** Google Zanzibar 논리를 따르는 Ory Keto를 활용함으로써, 향후 수만 명의 사용자와 수천 개의 테넌트 환경에서도 성능 저하 없이 정교한 권한 체크가 가능합니다.
|
||||
|
||||
## 5. 관련 구현 파일
|
||||
## 5. 현재 구현 기준 주의사항
|
||||
|
||||
- 현재 Baron SSO는 별도 `UserGroup` namespace를 사용하지 않습니다.
|
||||
- 유저 그룹은 `Tenant` namespace 내부의 특수 tenant(`type = USER_GROUP`)로 표현합니다.
|
||||
- 따라서 group membership과 group-based role assignment는 모두 `Tenant:<groupId>#members` subject set을 기준으로 해석해야 합니다.
|
||||
|
||||
## 6. 관련 구현 파일
|
||||
- **Backend Service:** `backend/internal/service/user_group_service.go`
|
||||
- **Backend Handler:** `backend/internal/handler/user_group_handler.go`
|
||||
- **Frontend API:** `adminfront/src/lib/adminApi.ts`
|
||||
|
||||
Reference in New Issue
Block a user