Align the design document with the actual codebase: - Correct pseudo-code to use get_accessible_project_ids() batch API - Add super admin bypass section - Clarify seen_project_ids is now a lightweight dedup set - Add get_accessible_project_ids() implementation details - Update base branch reference and add MEMORY.md index entry
8.8 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| rbac-user-isolation-design | GNS3 RBAC user isolation design and implementation thought process |
|
RBAC User Isolation Design Summary
Core Problem
GNS3 3.0 has a complete RBAC framework (ACE + Role + Privilege), but lacks user isolation implementation, causing users to see resources they shouldn't have access to.
Design Conflict
Traditional RBAC's path permission model fundamentally conflicts with user data isolation:
- Path permission model: Controls which paths users can access (e.g.,
/projects) - User data isolation: Controls which specific resources users can access (e.g., alice's project vs bob's project)
Final Implementation Approach
Three-Step Permission Check Logic
# Step 1: Batch ACE + resource pool check (3 DB queries regardless of project count)
direct_ace_ids, pool_accessible_ids = await rbac_repo.get_accessible_project_ids(
current_user.user_id, "Project.Audit", all_project_ids
)
# Step 2: Filter direct ACE projects by created_by (user's own projects)
# Direct project sharing is only available through resource pools
for p in controller.projects.values():
if p.id in direct_ace_ids and p.created_by == current_user.username:
projects.append(p.asdict())
# Step 3: Resource pool projects (no created_by filter)
for p in controller.projects.values():
if p.id in pool_accessible_ids:
projects.append(p.asdict())
Super Admin Bypass
if current_user.is_superadmin:
return [p.asdict() for p in controller.projects.values()]
Super admins skip all three steps and see every project.
seen_project_ids Deduplication
A simple seen_project_ids set prevents the same project from appearing twice when it exists in both direct ACE and pool results. This is a lightweight dedup, not the complex blocking mechanism from earlier rejected designs.
Key Design Decisions
1. ACE vs created_by Relationship
- ACE for basic access control: Whether user can access the system
- created_by for user isolation: Which specific resources user can access
- Step 2 filtering is critical: Even with broad ACE, created_by filtering ensures user isolation
2. Project Sharing Mechanism
- Project sharing only through resource pools: Cannot configure ACE directly for specific projects
- Avoids complexity of direct ACE sharing: Prevents permission configuration chaos
3. Broad ACE Fault Tolerance
Even with configuration: ACE: Users group + User role + "/" + propagate: True
Result: Users still only see projects they created
Reason: Step 2 created_by filtering removes other users' projects
RbacRepository.get_accessible_project_ids()
The core batch-check method in gns3server/db/repositories/rbac.py uses 3 DB queries:
- User ACEs: Direct ACE entries matching user + privilege
- Group ACEs: Group-level ACE entries via
UserGroupmembership - All resources with pool memberships: Preloads all resources and their pool relationships
Then it computes two sets:
direct_ace_ids: Project paths matching user or group ACEs (path-based check)pool_accessible_ids: Projects in pools the user/group can access (pool-based check)
Pool IDs are precomputed into a pool_id -> set(project_ids) map for O(1) lookup.
Problems Solved
Problem 1: Missing User Isolation
- Issue: All users can see all projects
- Solution: Filter by created_by to implement user isolation
Problem 2: Broad ACE Breaking Isolation
- Issue:
path: "/" + propagate: truebreaks user isolation - Solution: Step 2 created_by filtering ensures user isolation even with broad ACE
Problem 3: Permission Check Order Conflicts
- Issue: seen mechanism blocks subsequent checks (from earlier design phases)
- Solution: Simple pipeline-style filtering with lightweight dedup set
Technical Details
API Layer Permission Check
@router.get("/projects") # Note: no has_privilege decorator
async def get_projects(current_user=..., rbac_repo=...):
# Permission checks in business logic
Duplicate Prevention
- Simple
seen_project_idsset for deduplication between direct ACE and pool results - Not the complex blocking mechanism from earlier phases
Performance Considerations
- Super admin path: O(1) — returns all projects directly
- Regular user path: 3 fixed DB queries regardless of project count
- Path-based ACE check: O(n * m) in worst case, where n = projects, m = ACE entries
- Pool lookup: O(1) via precomputed pool->project map
Use Cases
Scenario 1: Personal Use
alice creates project → alice only sees alice's projects ✅
bob creates project → bob only sees bob's projects ✅
No extra configuration needed, automatic user isolation
Scenario 2: Team Collaboration
Admin creates resource pool → adds projects → team members can access
alice creates project → alice still sees her own projects ✅
Team sharing + user isolation coexist
Scenario 3: Broad ACE Configuration
ACE: Users group + "/" + propagate: True
alice still only sees alice's projects ✅
User isolation unaffected by ACE configuration
Design Principles
1. Separation of Concerns
- Basic access permission: Controlled by ACE
- Data ownership: Controlled by created_by
- Team sharing: Controlled by resource pools
2. Defensive Design
- User isolation remains effective even with improper ACE configuration
- Secure by default: users only see their own resources
3. Simplicity
- Avoid complex seen mechanisms and mutual exclusion logic
- Clear pipeline-style check order
Relationship with Original ACE System
Preserved Components
- ✅ ACE framework (permission check mechanism)
- ✅ Role and privilege definitions
- ✅ Resource pool functionality
Improved Components
- ✅ Added user isolation (created_by filtering)
- ✅ Clarified project sharing mechanism (only through resource pools)
- ✅ Simplified permission check logic
Removed Components
- ❌ Removed
/projectspath privilege check onget_projectsroute - ❌ Removed the old complex
seen_project_idsblocking mechanism - ❌ Avoided "can see all non-pool projects" privilege leak
Implementation Details
- Main modification:
gns3server/api/routes/controller/projects.py - Function:
get_projects() - Batch check method:
gns3server/db/repositories/rbac.py::RbacRepository.get_accessible_project_ids() - Privilege dependency:
gns3server/api/routes/controller/dependencies/rbac.py::has_privilege() - Branch:
feature/simple-user-isolation - Base branch:
master
Key Commits
- Implemented basic created_by filtering
- Implemented three-layer permission check (with logic issues)
- Fixed to correct three-step check logic
Design Evolution Process
Phase 1: Simple created_by Filtering
user_projects = [p for p in all_projects() if p.created_by == user.username]
Issue: Didn't integrate with ACE system
Phase 2: Three-Layer Check (Wrong Version)
# Used complex seen_project_ids blocking mechanism
# Issue: Broad ACE breaks user isolation
Phase 3: Three-Step Check (Correct Version)
# Step 1: Batch ACE check via get_accessible_project_ids()
# Step 2: Filter direct_ace_ids by created_by
# Step 3: Resource pool projects (no created_by filter)
Solution: User isolation works even with broad ACE
Relationship with RBAC Roadmap
This implementation addresses items mentioned in the roadmap:
- Phase 1 (MVP): Basic project isolation implementation
- Auto-ACE on create: Still needs to be implemented separately
- Template isolation: Can use same design pattern
Unsolved Issues
- Auto-ACE on project creation: Part of roadmap Phase 1 —
create_project()setscreated_bybut doesn't create ACE entries - Template and image isolation: Can apply same design pattern
- Default ACE configuration: Need reasonable default permissions for Users group
Design Limitations
- Project sharing only through resource pools: No direct ACE configuration for sharing
- ACE configuration required: Users need basic ACE to access system
- Performance considerations: ACE check queries database for each project path
Future Improvement Directions
- Auto-create ACE: Automatically add ACE for creator when creating projects
- Default ACE strategy: Configure reasonable default permissions for Users group
- Performance optimization: Cache ACE check results to reduce database queries
This design achieves effective user isolation while maintaining RBAC system integrity, and solves the problem of broad ACE configurations breaking isolation.
Key insight: The Step 2 created_by filtering is the critical innovation that allows ACE and user isolation to coexist properly.