NetStacksNetStacks

Roles & Permissions

Enterprise

Define roles from the 14 NetStacks permission keys, build custom roles, and understand how the users.manage-gated RBAC controls access across the Controller.

Overview

NetStacks Controller uses role-based access control (RBAC) to govern what each user can do. A role is a named list of permission keys stored as a JSON array. When a user attempts an action — through the Admin UI, the Terminal, or the API — the Controller checks whether the user holds the permission key required by that endpoint.

The system seeds three roles in the default organization — admin,operator, and viewer. The admin role is protected (it cannot be deleted) and holds the single wildcard permission *, which grants everything. You can edit any role's permissions, and you can create additional custom roles from any combination of the 14 granular permission keys.

Permissions are additive

A user may hold any number of roles. Their effective permission set is the union of all permissions from all assigned roles. Permissions are never subtracted — if any role grants a key, the user has it. Holding the* wildcard in any role grants every permission.

Controller-only feature

Roles and RBAC apply to the NetStacks Controller (Enterprise). Standalone Terminals run with local capabilities and do not call the Controller's permission system.

Permission Catalog

There are 14 granular permission keys plus the * wildcard. This is the complete, authoritative catalog — the same list returned by the GET /api/admin/roles/permissions endpoint and rendered in the role editor. No other keys exist; any key outside this list is invalid.

KeyLabelDescriptionCategory
devices.accessDevice InventoryView device inventory and detailsDevices & Network
devices.manageDevice ManagementCreate, edit, delete devices and NetBox syncDevices & Network
snapshots.manageConfig SnapshotsRun and manage config snapshots and backupsDevices & Network
config.deployConfig DeploymentDeploy stacks, templates, and config changes to devicesDevices & Network
topologies.accessTopologiesView and edit network topologiesDevices & Network
sessions.connectSSH/Telnet SessionsOpen SSH and Telnet sessions to network devicesSessions & Connectivity
agents.manageAI AgentsCreate, edit, and run AI agent tasksAI & Automation
scripts.runScriptsExecute user scripts on devicesAI & Automation
mops.manageMOPsCreate, edit, and execute Methods of ProcedureAI & Automation
knowledge.accessKnowledge SearchSearch the knowledge baseAI & Automation
knowledge.manageKnowledge ManagementUpload, edit, and delete knowledge base documentsAI & Automation
users.manageUsers & RolesManage user accounts, roles, and role assignmentsAdministration
credentials.manageCredentials VaultManage the credential vault — create, edit, delete credentialsAdministration
system.manageSystem SettingsSystem settings, license, backup/restore, pluginsAdministration
*All PermissionsFull unrestricted access (wildcard)—
Role management is gated by users.manage

The permission that lets a user create, edit, delete, or assign roles is users.manage. There is no separate roles.* permission. Any role with users.manage (or *) can administer all roles.

Built-in Roles

The three seeded roles and their exact permission sets are listed below. Only admin is protected from deletion; operator and viewer can be edited or deleted like any custom role.

  • admin (protected) — ["*"]. Full unrestricted access to every feature, including user/role management, settings, and audit logs.
  • operator — devices.access, devices.manage, snapshots.manage, config.deploy, topologies.access, sessions.connect, agents.manage, scripts.run, mops.manage, knowledge.manage, knowledge.access. Operational access to devices, sessions, automation, and knowledge. No users.manage, credentials.manage, or system.manage.
  • viewer — devices.access, topologies.access, knowledge.access. Authenticated read-level access to the device inventory, topologies, and knowledge search only.

Permission matrix

Permissionadminoperatorviewer
devices.accessYesYesYes
devices.manageYesYes—
snapshots.manageYesYes—
config.deployYesYes—
topologies.accessYesYesYes
sessions.connectYesYes—
agents.manageYesYes—
scripts.runYesYes—
mops.manageYesYes—
knowledge.accessYesYesYes
knowledge.manageYesYes—
users.manageYes——
credentials.manageYes——
system.manageYes——

In the matrix above, admin shows “Yes” for every row because its stored permission list is the single * wildcard, which the permission check treats as granting all keys.

How It Works

Permission model

Permission keys follow a resource.action naming convention (for example devices.manage). The check is performed by the RequirePermission<P> request extractor in the API: it loads the user's permissions from PostgreSQL on every protected request and compares them to the key the endpoint requires.

Matching is exact, with one special case: the * wildcard grants everything. There is no category-level expansion — holding devices.manage does not imply devices.access, and there is no devices.* key. If an endpoint requires devices.access, the user's role list must contain either devices.access or *.

// Effective check, conceptually:
// 1. If the user has "*", allow.
// 2. Else if the user's permission list contains the exact required key, allow.
// 3. Else deny (403).

user.permissions = ["devices.access", "topologies.access"]
required = "devices.manage"   // -> DENIED (no exact match, no "*")
required = "devices.access"   // -> ALLOWED (exact match)

Permission Grid UI

The role editor (Admin → Roles → a role) renders the catalog as a collapsible category grid populated from GET /api/admin/roles/permissions. The catalog has four categories:

  • Devices & Network — devices.access, devices.manage, snapshots.manage, config.deploy, topologies.access
  • Sessions & Connectivity — sessions.connect
  • AI & Automation — agents.manage, scripts.run, mops.manage, knowledge.access, knowledge.manage
  • Administration — users.manage, credentials.manage, system.manage

An All Permissions master toggle at the top maps to the * wildcard and grants full unrestricted access. Each category header has a checkbox that toggles every permission in that group at once (it shows an indeterminate state when only some keys in the category are selected). While * is selected, all individual and category checkboxes are shown as checked and disabled, since the wildcard already covers them.

Enforcement

Permissions are enforced server-side on every API request, and the Terminal and Admin UI additionally use them for progressive enhancement. At login, clients read the user's permission list from the GET /api/capabilities endpoint, which returns a permissions array alongside licensed feature flags. The UI hides or disables controls the user cannot use — for example, a user lacking sessions.connect does not get session controls — but the API check is the authority. Because permissions are read from the database per request, role changes take effect immediately, without re-login.

Step-by-Step Guide

Viewing roles

  1. Go to Admin → Roles.
  2. The list shows each role with its name, description, and a protected badge on the admin role. The Delete button is disabled for protected roles.
  3. Click a role to open the editor and view or change its permissions in the grid.

Creating a custom role

  1. Go to Admin → Roles and click Create Role.
  2. Enter a name (must be unique within the organization) and an optional description.
  3. Open the new role and select permissions in the grid. For a NOC operator who should connect to devices and run automation but not touch the vault or settings, select: devices.access, devices.manage, config.deploy, sessions.connect, scripts.run, mops.manage, knowledge.access.
  4. Click Save Changes. The role is immediately assignable.
Principle of least privilege

Grant only the keys a team needs. sessions.connect lets a user open SSH sessions to devices without granting credentials.manage — they can connect using stored credentials without being able to view, edit, or export the vault.

Updating a role

  1. Open the role from Admin → Roles.
  2. Adjust the name, description, or permission checkboxes.
  3. Click Save Changes. The new permissions apply to every assigned user on their next API request (permissions are loaded per request, not cached in tokens).
Removing a permission affects all assigned users

Removing a key from a role immediately revokes that access for every user holding the role. Open the role detail first to review its user count before editing.

Assigning roles to users

  1. Go to Admin → Users and open a user.
  2. Add or remove roles in the user's roles section.
  3. Save. The user's effective permissions (the union of all assigned roles) update immediately.

Code Examples

Role endpoints live under /api/admin/roles. Every role operation — list, get, create, update, delete — and the permission-catalog endpoint require the users.manage permission (the admin role has it via *).

List the permission catalog

This is the authoritative source for the grid. Use it to discover valid keys programmatically.

list-permissions.shbash
curl https://netstacks.dc1.example.net/api/admin/roles/permissions \
  -H "Authorization: Bearer $TOKEN"
permissions-catalog.jsonjson
{
  "categories": [
    {
      "name": "Devices & Network",
      "permissions": [
        { "key": "devices.access", "label": "Device Inventory", "description": "View device inventory and details", "category": "Devices & Network" },
        { "key": "devices.manage", "label": "Device Management", "description": "Create, edit, delete devices and NetBox sync", "category": "Devices & Network" },
        { "key": "snapshots.manage", "label": "Config Snapshots", "description": "Run and manage config snapshots and backups", "category": "Devices & Network" },
        { "key": "config.deploy", "label": "Config Deployment", "description": "Deploy stacks, templates, and config changes to devices", "category": "Devices & Network" },
        { "key": "topologies.access", "label": "Topologies", "description": "View and edit network topologies", "category": "Devices & Network" }
      ]
    },
    {
      "name": "Sessions & Connectivity",
      "permissions": [
        { "key": "sessions.connect", "label": "SSH/Telnet Sessions", "description": "Open SSH and Telnet sessions to network devices", "category": "Sessions & Connectivity" }
      ]
    },
    {
      "name": "AI & Automation",
      "permissions": [
        { "key": "agents.manage", "label": "AI Agents", "description": "Create, edit, and run AI agent tasks", "category": "AI & Automation" },
        { "key": "scripts.run", "label": "Scripts", "description": "Execute user scripts on devices", "category": "AI & Automation" },
        { "key": "mops.manage", "label": "MOPs", "description": "Create, edit, and execute Methods of Procedure", "category": "AI & Automation" },
        { "key": "knowledge.access", "label": "Knowledge Search", "description": "Search the knowledge base", "category": "AI & Automation" },
        { "key": "knowledge.manage", "label": "Knowledge Management", "description": "Upload, edit, and delete knowledge base documents", "category": "AI & Automation" }
      ]
    },
    {
      "name": "Administration",
      "permissions": [
        { "key": "users.manage", "label": "Users & Roles", "description": "Manage user accounts, roles, and role assignments", "category": "Administration" },
        { "key": "credentials.manage", "label": "Credentials Vault", "description": "Manage the credential vault — create, edit, delete credentials", "category": "Administration" },
        { "key": "system.manage", "label": "System Settings", "description": "System settings, license, backup/restore, plugins", "category": "Administration" }
      ]
    }
  ]
}

List all roles

list-roles.shbash
curl https://netstacks.dc1.example.net/api/admin/roles \
  -H "Authorization: Bearer $TOKEN"
list-roles-response.jsonjson
{
  "roles": [
    {
      "id": "00000000-0000-0000-0000-000000000001",
      "name": "admin",
      "description": "Full system access - all permissions",
      "permissions": ["*"],
      "protected": true,
      "created_at": "2026-01-01T00:00:00Z"
    },
    {
      "id": "00000000-0000-0000-0000-000000000003",
      "name": "operator",
      "description": "Operational access to all features: devices, sessions, credentials, AI, tunnels, agents, MOPs, scripts, knowledge",
      "permissions": [
        "devices.access", "devices.manage", "snapshots.manage", "config.deploy",
        "topologies.access", "sessions.connect", "agents.manage", "scripts.run",
        "mops.manage", "knowledge.manage", "knowledge.access"
      ],
      "protected": false,
      "created_at": "2026-01-01T00:00:00Z"
    },
    {
      "id": "00000000-0000-0000-0000-000000000002",
      "name": "viewer",
      "description": "Authenticated access only - no admin or operator privileges",
      "permissions": ["devices.access", "topologies.access", "knowledge.access"],
      "protected": false,
      "created_at": "2026-01-01T00:00:00Z"
    }
  ]
}

Get one role (with user count)

get-role.shbash
curl https://netstacks.dc1.example.net/api/admin/roles/00000000-0000-0000-0000-000000000003 \
  -H "Authorization: Bearer $TOKEN"
get-role-response.jsonjson
{
  "id": "00000000-0000-0000-0000-000000000003",
  "name": "operator",
  "description": "Operational access to all features ...",
  "permissions": ["devices.access", "devices.manage", "config.deploy", "sessions.connect"],
  "protected": false,
  "created_at": "2026-01-01T00:00:00Z",
  "user_count": 7
}

Create a custom role

Returns the new role's id. Body keys: name, description (optional), permissions (array of valid keys).

create-role.shbash
curl -X POST https://netstacks.dc1.example.net/api/admin/roles \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "NOC Operator",
    "description": "Device and automation access for the NOC team",
    "permissions": [
      "devices.access",
      "devices.manage",
      "config.deploy",
      "sessions.connect",
      "scripts.run",
      "mops.manage",
      "knowledge.access"
    ]
  }'
create-role-response.jsonjson
{ "id": "d4e5f6a7-b8c9-0123-def0-456789012345" }

Update a role

Send only the fields you want to change. Replacing permissions overwrites the whole list, so include every key the role should keep. Returns { "success": true }.

update-role.shbash
curl -X PUT https://netstacks.dc1.example.net/api/admin/roles/d4e5f6a7-b8c9-0123-def0-456789012345 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "permissions": [
      "devices.access",
      "devices.manage",
      "config.deploy",
      "sessions.connect",
      "scripts.run",
      "mops.manage",
      "knowledge.access",
      "agents.manage"
    ]
  }'

Delete a custom role

Protected roles (the seeded admin role) return 400 Bad Request.

delete-role.shbash
curl -X DELETE https://netstacks.dc1.example.net/api/admin/roles/d4e5f6a7-b8c9-0123-def0-456789012345 \
  -H "Authorization: Bearer $TOKEN"

Read a user's effective permissions

capabilities.shbash
curl https://netstacks.dc1.example.net/api/capabilities \
  -H "Authorization: Bearer $TOKEN"
capabilities-response.jsonjson
{
  "permissions": ["devices.access", "devices.manage", "config.deploy", "sessions.connect"],
  "features": [ /* licensed feature flags */ ]
}

Questions & Answers

How many permissions does NetStacks have?
Fourteen granular permission keys plus the * wildcard: devices.access, devices.manage, snapshots.manage, config.deploy, topologies.access, sessions.connect, agents.manage, scripts.run, mops.manage, knowledge.access, knowledge.manage, users.manage, credentials.manage, and system.manage. Fetch the live list from GET /api/admin/roles/permissions.
What permissions does the default admin role have?
The admin role's stored permission list is the single wildcard ["*"], which grants every key in the catalog. It is the only seeded role with users.manage, credentials.manage, and system.manage, and it is the only protected role.
Which permission lets someone manage roles?
users.manage. Every role endpoint — list, get, create, update, delete — and the permission-catalog endpoint are gated on it. There is no separate roles.view or roles.* permission.
Do wildcard keys like devices.* exist?
No. The only wildcard is *, which grants everything. Permission matching is otherwise exact: devices.manage does not imply devices.access, and there is no devices.* key. Grant each specific key you need.
Can a user have multiple roles?
Yes. Effective permissions are the union of all assigned roles — never subtracted. Assigning both viewer and a custom role gives the user every key from both.
How do I see a user's effective permissions?
Call GET /api/capabilities as that user (it returns a permissions array), or review each assigned role in Admin → Users. The effective set is the union of all role permission lists.
What happens when I remove a permission from a role?
Every user holding that role loses the affected access on their next API request. Permissions are read from the database per request rather than cached in the token, so there is no delay and no re-login needed.
Can I edit or delete the built-in roles?
You can edit the permissions of all three seeded roles. You can delete operator and viewer. The admin role is protected and cannot be deleted (the API returns 400), guaranteeing at least one administrative role always exists.

Troubleshooting

A user cannot access a feature

  • Check the user's roles in Admin → Users.
  • Confirm at least one assigned role holds the exact key the feature requires. For example, opening device SSH/Telnet sessions requires sessions.connect; deploying configuration requires config.deploy.
  • Remember there is no category expansion — devices.manage alone does not grant devices.access. Add each key explicitly, or grant *.

403 Forbidden on an API call

  • A 403 means the token is valid but the user lacks the required permission. Each endpoint is guarded by a RequirePermission<P> extractor — for example, role and user endpoints use AdminPermission, which maps to users.manage.
  • Permissions are loaded from the database on every request, so a recent role change applies without re-login. If it still fails, re-check the role's permission list.

Cannot delete the admin role

  • The seeded admin role is protected. DELETE returns 400 Bad Request, and the Admin UI disables its Delete button.
  • You can still edit the admin role's permissions — only deletion is blocked. Other roles can be edited and deleted.

Role name already exists

  • Role names must be unique within the organization. Creating or renaming to an existing name returns 400 Bad Request. An empty name is also rejected with 400.

Created a role with no effect

  • Verify the keys you sent are valid — only the 14 catalog keys and * are recognized. Misspelled keys are stored but never match a permission check. Cross-check against GET /api/admin/roles/permissions.
  • User Management — Create users and assign roles to control their effective permissions.
  • Authentication — Configure local, LDAP, and OIDC sign-in for the accounts that roles are assigned to.
  • Audit Logs — Track role changes via the role.created, role.updated, and role.deleted events.
  • API Authentication — How tokens and the per-request permission check enforce RBAC on every endpoint.
  • Credential Vault — The vault managed by credentials.manage and used over sessions.connect.
  • Auth Troubleshooting — Resolve login and permission issues, including 401/403 responses.