Skip to content

feat(rbac): enforce fine-grained console permissions - #310

Merged
YuZhiYuanDev merged 4 commits into
databk:mainfrom
yardbirds0:feature/fine-grained-rbac
Sep 12, 2026
Merged

feat(rbac): enforce fine-grained console permissions#310
YuZhiYuanDev merged 4 commits into
databk:mainfrom
yardbirds0:feature/fine-grained-rbac

Conversation

@yardbirds0

@yardbirds0 yardbirds0 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds fine-grained, server-side role-based access control (RBAC) to RustDesk Console.

It introduces:

  • A centralized permission catalog
  • Persistent custom roles
  • Global and device-group-scoped grants
  • Server-side function-, object-, and property-level authorization
  • Constrained role delegation
  • Protected accounts
  • Immediate permission freshness after security-sensitive changes
  • Minimal-data strategy assignment candidates
  • Transactional authorization for administrative batch operations
  • Console authorization audit records
  • A single immutable system-owner boundary

The goal is to let organizations delegate specific administrative work without turning every operator into a full Console administrator.

Examples include:

  • A read-only auditor who can inspect devices, users, strategies, address books, and audit records
  • A device operator limited to selected device groups
  • A user administrator who cannot manage roles or system settings
  • A strategy assigner who can assign policies without reading their full configuration
  • A system administrator with all assignable permissions, while still remaining distinct from the unique system owner

All authorization decisions are enforced by the backend. Frontend menu visibility and disabled controls are treated only as user-experience helpers.

Paired frontend PR: databk/rustdesk-console-web#281

Motivation

Before this change, Console authorization was primarily based on whether a user was an administrator, with additional checks distributed across individual features.

That model could not safely express requirements such as:

  • View devices but do not edit or delete them
  • Edit devices only in selected device groups
  • Manage users without changing roles or system settings
  • Assign strategies without reading sensitive strategy configuration
  • Allow one administrator to delegate only permissions they already possess
  • Protect high-impact accounts from other delegated administrators

Authentication and authorization are separate concerns. A valid login proves who the user is, but it does not prove that the user may perform every Console operation.

This PR therefore adds an explicit, deny-by-default authorization model and applies it consistently to relevant requests.

Identity model

The backend recognizes three identity states.

System owner / super administrator

The original system owner remains the unique super administrator.

The system owner:

  • Has complete Console authority
  • Is not authorized through ordinary RBAC role assignments
  • Cannot be recreated through LDAP or normal user creation
  • Is protected by a database-level single-owner constraint
  • Is the only identity allowed to create, edit, delete, or unprotect role definitions
  • May manage protected accounts
  • May use owner-only system capabilities such as system settings, identity sources, and device-group structure management

If an existing database contains more than one owner account, startup fails explicitly instead of silently selecting or modifying an owner.

Ordinary user

A user without any assigned RBAC role is treated as an ordinary user.

An ordinary user retains only the existing personal address-book basic functionality. This basic capability is not stored as a role permission and cannot be removed through role editing.

RBAC user

A non-owner user may receive one or more persisted custom roles.

Effective authorization is calculated from the union of the assigned role grants, subject to scope and protection rules. Assigning a real RBAC role replaces the UI representation of the virtual ordinary-user identity; the virtual identity itself is never stored as a database role.

Permission catalog

The backend permission catalog is the source of truth for:

  • Permission code
  • Resource
  • Action
  • Human-readable description
  • Scope type
  • Assignability
  • System-only status
  • Required prerequisite permissions

The catalog covers the following resource areas.

Resource Supported operations Scope
Shared address books View settings, edit shared address books, create and manage sharing Global
Audit View audit records Global
Devices View, edit metadata, change status, delete, disconnect Global or selected device groups
Strategies View, create, edit, delete, assign Global or selected device groups where applicable
User groups View, create, edit, delete, manage members Global
Users View, create, edit, change status, delete, manage security, force logout Global
Roles View and assign roles Global
Role definitions Create, edit, and delete roles System owner only

The following capabilities are intentionally not assignable through ordinary roles:

  • System settings management
  • Device-group structure management
  • Identity-source management
  • Role creation
  • Role editing
  • Role deletion

They remain visible to clients as owner-only capabilities so the UI can explain the boundary instead of presenting them as missing or unknown permissions.

Permission dependencies

Permissions may declare prerequisites.

For example, a write operation generally requires the corresponding view permission. The backend validates these dependencies when a role is created or updated.

This prevents unusable or misleading roles such as being allowed to edit a resource that the same user is not permitted to retrieve.

Persisted roles are also filtered defensively when effective permissions are calculated. If stored data is incomplete or damaged, missing prerequisite permissions fail closed instead of granting the dependent operation.

Strategy assignment is an intentional exception: a user may receive strategies.assign without full strategies.view. In that case, the user can access only the minimal strategy candidate projection needed for assignment.

Scope model

Permissions support two scope modes.

Global scope

A global grant applies to all resources covered by that permission.

Example:

devices.edit -> global

The user may edit every device they are otherwise allowed to access.

Selected device-group scope

A scoped grant applies only to explicitly selected device groups.

Example:

devices.edit -> Finance device group
devices.status -> Finance device group

The user may operate on Finance devices but cannot use the same actions on Sales or Engineering devices.

Scope checks are applied to:

  • Lists
  • Details
  • Single-resource mutations
  • Batch mutations
  • Strategy assignment candidates
  • Device-group-related projections

When the same permission is granted globally and through selected device groups, the global grant wins because it already covers the narrower grants.

Client-supplied user IDs, role IDs, device IDs, or device-group IDs are never trusted as proof of authorization.

Request authorization flow

Relevant backend requests follow this authorization sequence:

Authenticate the session
-> Verify that the account is active and the token is current
-> Check the required function permission
-> Load and verify the target object
-> Apply device-group scope rules where required
-> Check whether the target account is protected
-> Execute the business operation
-> Record the administrative authorization result

This design keeps authentication, function authorization, object authorization, and target protection as separate checks.

A missing permission check must not turn into an implicit allow. Protected routes use centralized metadata and guards, while service-level checks enforce object, scope, and write-boundary constraints.

Role-definition management

Only the system owner may:

  • Create roles
  • Edit role names, notes, permissions, or protection status
  • Delete roles
  • Disable protection on a protected role

Role payloads are validated against the backend permission catalog.

The backend rejects:

  • Unknown permission codes
  • Duplicate permission codes
  • System-only permissions in normal role payloads
  • Missing prerequisite permissions
  • Invalid or mixed scope assignments
  • Device-group grants for permissions that do not support device-group scope
  • Invalid device-group identifiers

Role presets are intentionally not persisted as backend identities.

The frontend presets:

  • Global read-only
  • Device operations
  • User administrator
  • Shared address-book administrator
  • Strategy maintainer
  • Device strategy assigner
  • System administrator

are convenience templates that compile into explicit permission selections and role metadata. After submission, the backend treats the result as an ordinary validated role. If the user modifies a preset, it simply becomes a custom permission set.

This avoids coupling backend authorization to UI template names.

Constrained role delegation

roles.view and roles.assign are separate permissions.

A delegated administrator with roles.assign may assign roles only when all of the following conditions are satisfied:

  • The target is not the caller
  • The target is not the system owner
  • The target is not protected
  • The role's permissions do not exceed the caller's effective permissions
  • The role's device-group scope does not exceed the caller's effective scope
  • The role does not grant protected delegation capabilities that the caller may not delegate
  • Existing assignments that the caller is not allowed to remove remain locked
  • The complete resulting role combination does not create a privilege escalation

The backend returns target-specific role eligibility, including whether each role can be assigned or removed and a stable reason code when it cannot.

The frontend may use this information to disable ineligible roles, but the backend repeats the complete validation when the assignment is saved.

This prevents request forgery, stale-page submission, combined-role escalation, and omission of locked assignments.

Protected accounts

A role may mark its members as protected accounts.

Protection is role metadata, not a normal permission code.

A non-owner administrator cannot perform administrative operations against a protected account, including:

  • Edit the account
  • Enable or disable the account
  • Delete the account
  • Change security settings
  • Force logout
  • Modify user-group membership
  • Assign or remove strategies
  • Assign or remove roles

The restriction is enforced across single-item routes, batch routes, alternate mutation paths, and candidate lists.

The system owner remains able to manage protected accounts.

The system-administrator frontend preset enables account protection by default, but protection is still stored and validated as explicit role metadata.

Before the system owner disables protection on a role that already has members, the backend reports the affected member count and requires an explicit confirmed mutation. The current role state and member count are revalidated at the transactional write boundary.

Personal self-service security operations remain separate from administrative operations.

Session and permission freshness

Role and account-security changes must not leave old authority active in an existing session.

The backend invalidates or rechecks relevant sessions after operations such as:

  • Role assignment or removal
  • Account disablement
  • Password changes
  • Security-setting changes
  • Forced logout
  • Other token-revoking account operations

Permission changes therefore take effect on the next protected backend request. A user cannot keep exercising revoked permissions simply because an older page remains open.

Pending login, TFA, and passkey flows are also invalidated where the corresponding security operation requires it.

Strategy assignment without full strategy access

A user may have permission to assign a strategy without permission to inspect its complete configuration.

For this case, the backend provides a minimal candidate representation containing only assignment-required fields such as:

  • Strategy identifier
  • Strategy name
  • Non-sensitive display metadata

The response does not include the full strategy configuration or secret-bearing fields.

Normal strategy detail endpoints still require the full strategy-view permission.

This preserves practical delegation without turning strategy assignment into an indirect data-exposure path.

Transaction and batch semantics

Security-sensitive writes are authorized again at the transaction boundary.

For supported administrative batch operations:

  • All targets are validated before mutation
  • Protected or unauthorized targets reject the complete operation
  • Invalid identifiers are handled explicitly
  • A partial write is not reported as complete success
  • Affected-row mismatches fail closed
  • Concurrent protection or assignment changes are revalidated
  • Audit data matches the committed result

This applies to relevant role, user, user-group, strategy, security, and status operations.

The goal is to prevent time-of-check/time-of-use authorization races and misleading partial-success responses.

Audit records

The Console audit model records administrative authorization activity with sufficient context to reconstruct what happened.

Records include relevant fields such as:

  • Actor user
  • Action
  • Target resource type
  • Target identifier
  • Result
  • Reason
  • Timestamp
  • Before/after state for relevant privilege changes
  • Affected-member count for protection changes

Actor identity remains attributable even when related user data later changes or is removed.

Audit access itself is protected by audit.view.

Address-book behavior

Personal address-book access remains a basic authenticated-user capability and is not converted into a role permission.

Shared address-book administration is covered separately by explicit RBAC permissions.

Existing RustDesk client-facing address-book compatibility routes remain available because they are consumed by RustDesk clients. They were not removed merely because the Console Web application uses newer management endpoints.

Shared-address-book ACL operations continue to use their existing resource-level access rules and are not silently converted into unrestricted Console-administrator operations.

Database and startup behavior

The RBAC schema is added through the project's existing TypeORM synchronization path.

The changes include storage for:

  • Roles
  • Role permissions
  • User-role assignments
  • Assignment scope
  • Device-group grants
  • Protected-role metadata
  • Authorization audit data
  • Session/token freshness state where required

The existing system owner is preserved.

A partial unique database constraint enforces that at most one user may be marked as the system owner. Startup accepts databases containing zero or one owner, but rejects databases containing multiple owners instead of silently repairing them.

A database backup is recommended before deploying this change to an existing installation.

Compatibility and boundaries

This PR does not modify:

  • hbbs
  • hbbr
  • RustDesk rendezvous configuration
  • Relay configuration
  • RustDesk server keys
  • Client heartbeat identity
  • RustDesk client connection protocol

The RBAC implementation protects Console management operations. It does not change how RustDesk clients connect to the RustDesk server.

Existing non-owner users without roles are intentionally reduced to ordinary-user access. Administrators should assign explicit roles before expecting those accounts to retain management access.

The implementation intentionally does not introduce:

  • Arbitrary ABAC expressions
  • A custom policy language
  • Role hierarchy editing
  • Dynamic separation-of-duty rules
  • Cross-tenant sharing
  • Wildcard permissions
  • Self-service role assignment
  • Delegated role-definition editing

These features would substantially increase the authorization and testing surface and are outside the scope of this PR.

Security invariants

The implementation follows these invariants:

  • Authentication does not imply authorization
  • Authorization is enforced on the server
  • Relevant operations are denied unless explicitly granted
  • Function-, object-, scope-, and property-level checks are separate
  • Client-supplied identifiers do not establish authority
  • System-only permissions cannot enter ordinary role payloads
  • Delegated administrators cannot grant more authority than they possess
  • Protected accounts cannot be managed through alternate or batch routes
  • Role or account-security changes invalidate stale authority
  • Administrative changes are attributable through audit records

Verification

Automated verification completed for the final backend branch:

  • Jest: 12 suites, 126 tests passed
  • TypeScript type check: passed
  • Production build: passed
  • ESLint on changed backend files: passed
  • git diff --check: passed

Coverage includes:

  • Permission-catalog validation
  • Permission dependencies
  • Global and device-group-scoped authorization
  • Function- and object-level denial
  • User-role replacement
  • Delegated role eligibility
  • Self-assignment prevention
  • Combined-role escalation prevention
  • Locked-assignment preservation
  • Protected-account single and batch routes
  • Concurrent protection and assignment changes
  • Account state and token revocation
  • Pending login/session invalidation
  • Minimal strategy candidate projection
  • Shared-address-book ACL compatibility
  • Unique system-owner initialization
  • LDAP owner-escalation prevention
  • Console audit projections
  • Invalid and missing batch identifiers
  • Transaction rollback and affected-row mismatch handling

A read-only concurrency smoke test also completed without 5xx responses, timeouts, database-lock failures, or process termination.

Suggested review scenarios

  1. Start with the existing system owner and verify complete Console access.
  2. Sign in as a user without roles and verify that only personal address-book functionality remains.
  3. Assign a global read-only role and verify that mutation endpoints remain denied.
  4. Assign device operations for one device group and verify list, detail, single-write, and batch-write scope enforcement.
  5. Grant strategy assignment without strategy view and verify that only minimal candidate data is available.
  6. Mark a role as protected and verify that non-owner administrators cannot manage its members through any user, group, strategy, security, logout, or role route.
  7. Grant delegated role assignment and verify self-assignment, scope escalation, combined-role escalation, and locked-role removal are rejected.
  8. Remove a role and verify that the revoked authority disappears on the next protected request.
  9. Submit invalid and mixed batch targets and verify that the operation does not partially commit.
  10. Verify that the existing RustDesk heartbeat and client connection paths are unchanged.

Summary by CodeRabbit

  • New Features

    • Added role-based access control with permission management, role assignment, scoped device-group access, and protected-account handling.
    • Added role, user-role, permission, audit, address-book sharing, strategy candidate, and active-connection endpoints.
    • Added device filters for status, online state, operating system, and device group.
    • Login responses now include a stable user identifier.
  • Bug Fixes

    • Improved token, account, session, and authorization validation.
    • Added transactional safeguards for address-book, user, device, and role changes.
    • Enforced a single system owner during startup.
  • Security

    • Added granular permissions and authorization auditing across administrative features.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Role-Based Access Control (RBAC)

4 participants