gns3-server/docs/gns3-copilot/roadmap/rbac-user-isolation-roadmap.md

23 KiB
Raw Blame History

RBAC User Isolation Roadmap

Overview

GNS3 3.0 ships with a complete RBAC framework (ACE + Role + Privilege models), but the user isolation layer is incomplete. Users can see resources they should not have access to because:

  • Resource creation does not auto-grant the creator an ACE
  • List endpoints return unfiltered results
  • Two GET endpoints have RBAC checks bypassed via FIXME

This document has been updated to reflect the implemented solution in feature/simple-user-isolation branch.

Current State

Resource Route Check List Filtering Auto-ACE on Create ACE Cleanup on Delete
Project Project.Audit/Modify/Allocate Implemented — Three-step filtering Not needed Done
Template Template.Audit FIXME None — all templates returned Missing Done
Node Node.Audit/Modify/Allocate N/A (inherits project) Inherits project N/A
Link Link.Audit/Modify/Allocate N/A (inherits project) Inherits project N/A
Drawing Drawing.Audit/Modify/Allocate N/A (inherits project) Inherits project N/A
Snapshot Snapshot.Audit/Allocate/Restore N/A (inherits project) Inherits project N/A
Image Image.Audit/Allocate None — all images returned Missing Missing
Compute Compute.Audit FIXME None — all computes returned N/A (shared infra) Done
Appliance Appliance.Audit/Allocate None — all appliances returned N/A (builtin) N/A
Symbol Symbol.Audit/Allocate None — all symbols returned N/A (builtin) N/A

Implemented Solution: Project Isolation

Three-Step Permission Check Logic

Status: Implemented in feature/simple-user-isolation branch

# Step 1: ACE check - basic access permission
# Get projects user has ACE for
ace_projects = []
for project in controller.projects.values():
    project_path = f"/projects/{project.id}"
    if await rbac_repo.check_user_has_privilege(current_user.user_id, project_path, "Project.Audit"):
        ace_projects.append(project)

# Step 2: Filter ace_projects by created_by - user's own projects
# Project sharing is only available through resource pools
user_projects = [p.asdict() for p in ace_projects if p.created_by == current_user.username]
projects.extend(user_projects)

# Step 3: Resource pool projects
# Projects shared through resource pools
user_pool_resources = await rbac_repo.get_user_pool_resources(current_user.user_id, "Project.Audit")
project_ids_in_pools = [str(r.resource_id) for r in user_pool_resources if r.resource_type == "project"]
pool_projects = [p.asdict() for p in controller.projects.values() if p.id in project_ids_in_pools]
projects.extend(pool_projects)

Key Design Principles

  1. ACE for basic access control: Controls whether user can access the system
  2. created_by for user isolation: Controls which specific resources user can access
  3. Resource pools for project sharing: The only mechanism for sharing projects between users
  4. No direct ACE sharing: Users cannot configure ACE directly on specific projects to share them

Advantages of This Approach

  • Fault tolerance: Even with broad ACE configuration (path: "/" + propagate: true), user isolation remains effective
  • Clear separation: Basic access, data ownership, and team sharing are clearly separated
  • Simple mechanism: No complex auto-ACE or seen_project_ids tracking required
  • Performance: Leverages existing created_by field, no schema changes needed

Updated Architecture

graph TD
    subgraph Client
        WebUI
        CLI
    end
    
    subgraph "Controller API"
        Auth[get_current_active_user]
        Routes[Resource Routes]
        ACE_Check[Step 1: ACE Check]
        Owner_Filter[Step 2: Filter by created_by]
        Pool_Check[Step 3: Resource Pools]
    end
    
    subgraph "RBAC Engine"
        ACE[(ACE table)]
        Role[(Role table)]
        Privilege[(Privilege table)]
        Checker[check_user_has_privilege]
    end
    
    WebUI --> Auth
    CLI --> Auth
    Auth --> Routes
    Routes --> ACE_Check
    ACE_Check --> Checker
    ACE_Check --> Owner_Filter
    Owner_Filter --> Pool_Check
    Pool_Check --> Checker
    Checker --> ACE
    Checker --> Role
    Role --> Privilege

Business Process

Project listing with three-step filtering

sequenceDiagram
    actor U as User
    participant API as GET /projects
    participant Controller as Controller
    participant RBAC as RBAC Engine
    
    U->>API: List projects
    API->>API: get_current_active_user (not superadmin)
    
    API->>RBAC: Step 1: ACE check on each project
    loop Each project
        RBAC-->>API: ACE results
    end
    
    API->>API: Step 2: Filter by created_by
    API-->>U: User's own projects
    
    API->>RBAC: Step 3: Resource pool projects
    RBAC-->>API: Pool projects
    
    API-->>U: Combined list (own + pool)

Updated Phased Plan

Phase 1 — MVP: Project isolation (COMPLETED)

Goal: Users only see projects they created or were granted access to through resource pools.

Task Files Status Detail
Fix project list filtering projects.pyget_projects() Implemented Three-step filtering: ACE check → created_by filter → resource pools

Result:

  • Users can only see projects they created
  • Team collaboration through resource pools works
  • Fault tolerance: Works correctly even with broad ACE configurations
  • No schema changes required
  • ~30 lines changed

Phase 2 — Template isolation

Goal: Users see their own templates + builtin templates only.

Task Files Detail
Apply same pattern to templates templates.pyget_templates() Use same three-step filtering as projects
Uncomment Template.Audit templates.py Restore has_privilege("Template.Audit") checks

Dependency: Web UI must handle 403 from GET /templates/{id}. Mitigation: keep builtin templates unconditionally visible so the UI always has data.

Phase 3 — Image isolation (optional)

Goal: Users see only images they uploaded.

Task Files
Apply same pattern to images images.pyget_images()
Fix image list filtering images.py
ACE cleanup on delete images.pydelete_image()

Phase 4 — Default ACE for "Users" group (optional)

Goal: Users in "Users" group can create/list resources without admin ACE intervention.

Task Detail
Default ACE on /projects Grant "Users" group → User role → /projects (propagate=False)
Default ACE on /templates Grant "Users" group → User role → /templates (propagate=False)

API Endpoints Changed

Phase 1 (Implemented)

Method Path Change
GET /v3/projects Three-step filtering: ACE → created_by → resource pools

Phase 2 (Planned)

Method Path Change
GET /v3/templates Apply same three-step filtering
GET /v3/templates/{id} Restore Template.Audit check

Key Design Decisions

  1. Project sharing through resource pools only: Users cannot configure ACE directly to share specific projects. All sharing must go through resource pools. This prevents permission configuration chaos and maintains clear ownership semantics.

  2. No auto-ACE required: The three-step filtering logic works without needing automatic ACE creation on project creation. The created_by field provides sufficient ownership information.

  3. Fault-tolerant to ACE configuration: Even if administrators configure broad ACE permissions (like path: "/" + propagate: true), user isolation remains effective because Step 2 filters by created_by.

  4. No DB migration required: Uses existing ACE/role/privilege tables and created_by field. No schema changes needed.

  5. Performance: Project list filtering is O(n) where n is the total number of projects. Each project requires one ACE check. Acceptable for < 500 projects. Can optimize later with batch ACE queries if needed.

Updated References

  • Implementation: gns3server/api/routes/controller/projects.py (feature/simple-user-isolation branch)
  • Discussion: https://github.com/GNS3/gns3-server/discussions/1949
  • RBAC models: gns3server/db/models/acl.py, roles.py, privileges.py
  • RBAC repository: gns3server/db/repositories/rbac.py
  • Auth dependency: gns3server/api/routes/controller/dependencies/authentication.py
  • RBAC dependency: gns3server/api/routes/controller/dependencies/rbac.py
  • Resource pools: gns3server/db/models/pools.py and gns3server/db/repositories/pools.py

Implementation Notes

What Was Implemented

The feature/simple-user-isolation branch implements a robust user isolation mechanism that:

  1. Integrates with existing RBAC framework without breaking changes
  2. Leverages the created_by field that already exists in the Project model
  3. Uses three-step pipeline filtering to avoid complex seen_project_ids tracking
  4. Supports team collaboration through existing resource pool functionality
  5. Is fault-tolerant to ACE misconfiguration - broad ACE permissions don't break user isolation

What Was Not Implemented

The original roadmap's Phase 1 included auto-ACE creation on project creation. This was determined to be unnecessary because:

  • The three-step filtering logic achieves user isolation without auto-ACE
  • Auto-ACE would add complexity without significant benefit
  • Project sharing through resource pools is cleaner than direct ACE configuration

Future Work

The same three-step filtering pattern can be applied to:

  • Templates: Replace the FIXME comment with proper filtering logic
  • Images: Apply the same pattern for user image isolation
  • Other resources: Extend the pattern as needed

This implementation provides a solid foundation for user isolation in GNS3 3.0+ while maintaining compatibility with the existing RBAC framework.

Phase 5 — ACE Architecture Refactoring (Future)

Goal: Improve ACE manageability by supporting multiple paths and resource pools in a single ACE entry.

Current Problem

With the current design where one ACE = one path:

  • ACE explosion: 5 user groups × 10 resource pools = 50 ACE entries
  • Management complexity: Difficult to maintain and understand ACE purpose
  • Performance impact: Permission checking must iterate through many ACE entries

Proposed Solution

Redesign ACE structure to support multiple paths and resource pools in a single ACE entry:

{
  "name": "Development Team Access",
  "description": "Full access for development team",
  "ace_type": "group",
  "group_id": "...",
  "role_id": "...",
  "paths": ["/projects", "/templates", "/images"],
  "resource_pools": ["pool-id-1", "pool-id-2"],
  "propagate": true,
  "allowed": true
}

Database Changes Required

  1. Add name and description to ACE table:
ALTER TABLE acl ADD COLUMN name VARCHAR;
ALTER TABLE acl ADD COLUMN description TEXT;
  1. Create association tables:
CREATE TABLE ace_paths (
    ace_id UUID REFERENCES acl(ace_id),
    path VARCHAR,
    PRIMARY KEY (ace_id, path)
);

CREATE TABLE ace_pools (
    ace_id UUID REFERENCES acl(ace_id),
    resource_pool_id UUID REFERENCES resource_pools(resource_pool_id),
    PRIMARY KEY (ace_id, resource_pool_id)
);
  1. Update permission checking logic to check both paths and resource_pools tables

Benefits

  • Reduced ACE entries: One ACE covers multiple related paths/pools
  • Better organization: Logical grouping with clear names and descriptions
  • Easier management: Edit one ACE instead of multiple related entries
  • Improved performance: Fewer ACE entries to check during permission validation

Implementation Considerations

  • Migration path: Need to migrate existing single-path ACEs to new structure
  • Backward compatibility: API should support both old and new formats during transition
  • UI updates: ACE management interface needs to support multi-path/pool selection
  • Permission checking: Update check_user_has_privilege to check association tables

Phase 6 — Frontend Permission Query API (Future)

Goal: Provide an API endpoint for the Web UI to query the current user's permissions, enabling dynamic UI rendering based on role and ACE configuration.

Problem

Currently the Web UI cannot determine what the authenticated user is allowed to see or do:

  • Users see menu items and buttons they don't have permission to use
  • Clicking a forbidden action results in a 403 error (unexpected UX)
  • No way to hide/show UI elements based on actual permissions

Proposed Solution

Create a GET /v3/me/permissions endpoint that returns the current user's effective permissions:

{
  "user_id": "uuid",
  "is_superadmin": false,
  "permissions": [
    {"path": "/projects", "privileges": ["Project.Audit", "Project.Allocate"]},
    {"path": "/projects/{id}", "privileges": ["Project.Audit", "Project.Modify"]},
    {"path": "/templates", "privileges": ["Template.Audit"]}
  ],
  "pools": [
    {"path": "/pools/{id}", "name": "Team Projects", "privileges": ["Pool.Audit"]}
  ]
}

Benefits

  • Dynamic UI: Frontend can hide inaccessible menus/buttons
  • Better UX: Users only see what they can actually use
  • Reduced errors: Fewer 403 responses from hidden operations
  • Faster feedback: Permission checks happen at render time, not request time

Dependencies

  • Phase 5 (ACE refactoring) may change how permissions are stored, which would affect this API's implementation

Phase 7 — Resource Pool Renaming (Future)

Goal: Rename "Resource Pool" to a more descriptive name that better reflects its actual purpose.

Problem

The current name "Resource Pool" is too generic and doesn't clearly convey its actual function:

  • Ambiguous name: "Resource Pool" could refer to compute pools, connection pools, etc.
  • Unclear purpose: Users don't understand it's primarily for sharing projects
  • Discoverability: Hard to find the right feature when looking for project sharing

Actual Function

Resource pools in GNS3 are used for:

  • Project sharing: Allow users to access projects created by other users
  • Team collaboration: Enable team members to work on shared projects
  • Access control: Provide fine-grained permissions for project access through three-step filtering (ACE check → created_by filter → resource pools)

Proposed Name Options

Option Pros Cons
Project Pool More explicit, indicates it contains projects Still uses "pool" terminology
Shared Projects Directly describes the function Loses the "collection" concept
Team Projects Emphasizes collaboration use case Doesn't cover non-team sharing scenarios

Recommended: Project Pool - strikes a balance between clarity and consistency with existing terminology.

Implementation Scope

Renaming would require changes to:

  • Database tables: resource_poolsproject_pools
  • API routes: /v3/pools/v3/project_pools
  • Schema classes and field names throughout the codebase
  • All documentation and help text
  • Migration script to preserve existing data

Benefits

  • Improved discoverability: Users can easily find the project sharing feature
  • Better onboarding: New users understand the purpose without confusion
  • Clearer API: API endpoints and schemas more self-documenting

Implementation Considerations

  • Breaking change: Requires API version bump or backward compatibility layer
  • Data migration: Existing resource pools must be preserved during table rename
  • Documentation updates: All references in docs, tutorials, and API specs need updating
  • UI changes: Frontend labels and navigation menus need to match new terminology

Phase 8 — Per-User Project Namespace (Future)

Goal: Allow project names to be unique per user instead of globally unique, enabling better user experience.

Current Problem

Although Phase 1 implements user isolation (users only see projects they created or have access to through resource pools), project names remain globally unique:

  • Naming conflicts: Alice and Bob cannot both create a project named "My Project"
  • Unnecessary restrictions: Even though projects are isolated, users must coordinate globally unique names
  • Poor user experience: Users get confusing error messages when trying to use common names like "Test Project"

Proposed Solution

Change project uniqueness from global to per-user:

Current:

UNIQUE(name)  -- Project names must be globally unique

Proposed:

UNIQUE(user_id, name)  -- Project names unique per user

Benefits

  • Better UX: Users can name projects whatever they want without worrying about global conflicts
  • Natural naming: Common names like "Test Project" or "Demo" can coexist between users
  • No coordination needed: Teams don't need to maintain a shared project naming registry
  • Consistent isolation: Projects are isolated both in visibility AND naming

Implementation Scope

Changes required:

  • Database schema: Modify Project table unique constraint from (name) to (user_id, name)
  • Migration script: Handle existing projects with conflicting names
  • API validation: Update project creation validation logic
  • Frontend: Remove global name uniqueness checks from UI

Migration Considerations

Handling existing name conflicts: If the database already has projects with the same name but different users:

  • Option 1: Keep existing names, only enforce uniqueness for new projects
  • Option 2: Append suffixes to duplicates (e.g., "My Project (alice)", "My Project (bob)")
  • Option 3: Require admin resolution for conflicts before enabling new constraint

Recommended: Option 1 (grandfather existing projects) for minimal disruption.

Dependencies

  • Phase 1 (User isolation): Must be completed first
  • Database migration required to modify unique constraint

Phase 9 — User Self-Registration (Future)

Goal: Allow users to register their own accounts without requiring manual admin creation.

Current State

Currently, user accounts can only be created by administrators or through direct database operations:

  • Admin burden: Every new user requires manual account creation
  • Poor scalability: Not suitable for public deployments or large organizations
  • Friction: Users cannot immediately start using the system

Proposed Features

Self-Registration Flow:

  1. User provides email, username, and password
  2. System validates input and creates account
  3. Optional email verification to confirm email address
  4. Account created with default role (typically "User" role)
  5. User can immediately log in and start creating projects

Email Verification (Optional):

  • Send verification email with confirmation link/code
  • Verify email address before granting full access
  • Prevent spam account creation
  • Require SMTP server configuration

Implementation Components

  1. New API endpoint: POST /v3/access/register (public, no authentication required)
  2. Email service: Integration with SMTP server for sending emails
  3. Configuration: SMTP settings (host, port, credentials, encryption)
  4. Rate limiting: Prevent abuse of self-registration
  5. Captcha integration: Optional bot protection

Configuration

Server:
  email:
    enabled: true
    smtp_host: smtp.example.com
    smtp_port: 587
    smtp_username: noreply@example.com
    smtp_password: secret
    use_tls: true
  registration:
    require_email_verification: true
    default_role: "User"
    allow_public_registration: true

Security Considerations

  • Rate limiting: Prevent spam account creation
  • Email verification: Confirm email ownership
  • Default permissions: New users get limited default role
  • Admin approval (optional): Require admin approval before account activation
  • Superadmin creation: Never allow self-registration as superadmin

Dependencies

  • Phase 10 (Email service): SMTP integration required for email verification

Phase 10 — Email Service Integration (Future)

Goal: Implement email sending capability for notifications, verification, and alerts.

Use Cases

  1. User registration: Email verification links/codes
  2. Password reset: Secure password reset emails
  3. System alerts: Error notifications, system updates
  4. Project sharing: Notify users when projects are shared via resource pools
  5. Usage reports: Periodic usage summaries or quota alerts

Proposed Implementation

Email Service Architecture:

  • Abstract email service interface
  • Support multiple email providers (SMTP, SendGrid, AWS SES, etc.)
  • Email templates with Jinja2 for customization
  • Async email sending to avoid blocking API responses
  • Email queue for retry logic on failures

Email Templates:

  • Registration verification
  • Password reset
  • Project shared notification
  • System alerts
  • Usage reports

API Endpoints:

# Configuration (admin only)
POST /v3/access/config/email
GET /v3/access/config/email
PUT /v3/access/config/email

# Test email (admin only)
POST /v3/access/config/email/test

# Password reset (public)
POST /v3/access/users/password/reset/request
POST /v3/access/users/password/reset/confirm

# Email verification (public)
POST /v3/access/users/verify/email
POST /v3/access/users/verify/confirm

Database Schema

New table for email tracking:

CREATE TABLE email_verification_tokens (
    token_id UUID PRIMARY KEY,
    user_id UUID REFERENCES users(user_id) ON DELETE CASCADE,
    token VARCHAR(255),  # Verification code
    purpose VARCHAR(50),  # 'registration', 'password_reset', etc.
    expires_at DATETIME,
    created_at DATETIME,
    used BOOLEAN DEFAULT FALSE
);

Configuration

Server:
  email:
    enabled: true
    provider: "smtp"  # or "sendgrid", "aws_ses", etc.
    from_address: "noreply@example.com"
    from_name: "GNS3 Server"
    reply_to: "support@example.com"
    smtp:
      host: smtp.example.com
      port: 587
      username: noreply@example.com
      password: encrypted_password
      use_tls: true
    templates_dir: /etc/gns3/email_templates

Security Considerations

  • Encrypted credentials: SMTP passwords stored encrypted in database
  • Token expiration: Verification tokens expire after configurable time
  • Rate limiting: Prevent email spamming
  • Async sending: Don't block API responses on email operations
  • Retry logic: Handle temporary email service failures
  • Privacy: Don't expose user information in error messages

Dependencies

  • Phase 9 (Self-registration): User self-registration requires email verification
  • Encryption utilities for storing SMTP credentials securely