RBAC Configuration
Mezite uses role-based access control (RBAC) to govern who can SSH into which nodes. Roles define allow and deny rules that match against node labels, specify allowed OS logins, and set session options. This guide covers the role system in depth: structure, label matching, deny-overrides-allow semantics, template variables, built-in roles, and practical examples.
Role Structure
A Mezite role has three main sections: allow, deny, and
options. The allow and deny sections specify which nodes a
user can or cannot access. Options control session behavior.
kind: role
version: v1
metadata:
name: example-role
description: "Human-readable description of this role"
spec:
options:
# Maximum session TTL for users with this role
max_session_ttl: 12h
# Whether to require MFA for SSH sessions ("", "off", "totp", "hardware_key", ...)
require_session_mfa: ""
# Allow port forwarding
port_forwarding: true
# Allow SCP/SFTP file transfers
file_copy: true
allow:
# Label selectors — which nodes this role grants access to
node_labels:
env: staging
# Which OS logins are permitted
logins:
- ubuntu
- deploy
# Roles this user may request via an access request (optional)
request_roles:
- ssh-production
# Roles whose requests this user may review/approve (optional)
review_roles:
- ssh-production
deny:
# Deny rules override allow rules
node_labels:
sensitivity: restricted
logins:
- root Label-Based Matching
Labels are key-value pairs attached to nodes via the agent configuration. Roles grant or deny SSH access based on label selectors.
Exact Match
# Matches nodes with env=production AND team=backend
node_labels:
env: production
team: backend Wildcard Match
# Matches any value for the env label
node_labels:
env: "*"
# Matches all nodes (any label, any value)
node_labels:
"*": "*" Multi-Value Match
# Matches nodes where env is staging OR development
node_labels:
env:
- staging
- development Regex Match
# Matches nodes where team starts with "eng-"
node_labels:
team: "^eng-.*$" When multiple labels are specified, all must match (AND logic). When multiple values are specified for a single label, any can match (OR logic).
Deny Overrides Allow
Mezite uses a deny-overrides-allow evaluation model. When a user has multiple roles, the system:
-
Collects all
allowrules from every role assigned to the user. -
Collects all
denyrules from every role assigned to the user. - Grants access only if at least one allow rule matches AND no deny rule matches.
This means a single deny rule in any role will block access, even if other roles explicitly allow it. Use deny rules sparingly and intentionally.
# Role A: allows SSH to all production nodes
kind: role
metadata:
name: ssh-all-production
spec:
allow:
node_labels:
env: production
logins:
- ubuntu
- deploy
deny: {}
---
# Role B: denies SSH to PCI nodes
kind: role
metadata:
name: deny-pci
spec:
allow: {}
deny:
node_labels:
compliance: pci
logins:
- root
- ubuntu
- deploy
# If a user has both Role A and Role B:
# - They CAN access env=production nodes (allowed by A)
# - They CANNOT access compliance=pci nodes (denied by B)
# - A production node with compliance=pci is DENIED (deny wins) Allowed Logins
The logins field in a role specifies which OS-level usernames
the Mezite user can authenticate as on remote nodes. The login requested at
connection time (via
msh ssh --login=) must appear in the allow.logins list and must not appear in the deny.logins list.
spec:
allow:
node_labels:
env: production
logins:
- ubuntu
- deploy
- "{{internal.logins}}" # Expands to the user's own Mezite username
deny:
logins:
- root # Never allow root, even if another role permits it Template Variables
Roles support template variables that are expanded at evaluation time.
This allows you to write generic roles that adapt to each user based on
their identity and traits. The implementation lives in
server/rbac/templates.go.
| Variable | Description | Example Value |
|---|---|---|
{{internal.logins}} | Currently resolves to a single-element list containing the user's
Mezite username — the auth server overrides any operator-supplied logins trait at certificate-issue time. Multi-login support via traits is
not implemented yet. | ["alice"] |
{{internal.<trait>}} | Any other trait recorded on the user record | {{internal.team}} → platform |
{{external.<trait>}} | A trait propagated from an SSO connector (e.g.
external.username, external.email) | alice@example.com |
{{email.local(external.email)}} | Function: returns the local part of an email address | alice |
Template variable usage yaml kind: role
version: v1
metadata:
name: team-scoped-ssh
description: "SSH access scoped to the user's team nodes"
spec:
allow:
# Only access nodes labeled with the user's team trait
node_labels:
team: "{{internal.team}}"
# Login as one of the user's recorded logins, or as the SSO username
logins:
- "{{internal.logins}}"
- "{{external.username}}"
deny: {}
When a user with the team=platform trait uses this role, the
label selector expands to team: platform, restricting them
to nodes owned by their team.
Session Options
The options section of a role controls session behavior. When
a user has multiple roles, the most restrictive option value wins.
Option Type Default Description max_session_ttlduration 12hMaximum lifetime for user certificates require_session_mfastring ""MFA type required per-session port_forwardingbool falseAllow SSH port forwarding. Disabled unless at least one of the
user's roles sets this to true. file_copybool falseAllow SCP and SFTP file transfers. Disabled unless at least one
of the user's roles sets this to true.
Restrictive session options yaml kind: role
metadata:
name: ssh-restricted
spec:
options:
max_session_ttl: 4h
require_session_mfa: "totp"
port_forwarding: false
file_copy: false
allow:
node_labels:
env: production
sensitivity: high
logins:
- ubuntu
deny:
logins:
- root
Built-in Roles
Mezite bootstraps four built-in roles on startup (see
server/rbac/defaults.go). You can assign these directly or
use them as templates for custom roles.
admin
Full cluster administrator. SSH access to every node, all administrative
resources, and the ability to join any moderated session in any mode.
Built-in: admin yaml kind: role
version: v1
metadata:
name: admin
description: "Full cluster administrator"
spec:
options:
max_session_ttl: 12h
forward_agent: true
port_forwarding: true
file_copy: true
record_session: best_effort
allow:
logins: [root, "{{internal.logins}}", testuser, ubuntu]
node_labels:
"*": "*"
join_sessions:
- name: admin-any-session
rules:
- resources: ["*"]
verbs: ["*"]
editor
Can manage users, roles, tokens, and connectors. Read access to
sessions, audit events, and nodes — but no SSH access by default.
Built-in: editor yaml kind: role
version: v1
metadata:
name: editor
description: "Can manage users and roles but limited server access"
spec:
options:
max_session_ttl: 8h
allow:
rules:
- resources: [user, role, token, connector]
verbs: ["*"]
- resources: [session, event, node]
verbs: [read, list]
viewer
Read-only access to cluster state — users, roles, tokens, nodes,
sessions, and audit events. Cannot SSH into nodes.
Built-in: viewer yaml kind: role
version: v1
metadata:
name: viewer
description: "Read-only access to cluster state"
spec:
options:
max_session_ttl: 4h
allow:
rules:
- resources: [user, role, token, node, session, event]
verbs: [read, list]
ssh-access
Basic SSH access to non-production nodes. Allows logging in as the
external SSO username or ubuntu on nodes labelled
env=staging or env=dev; denies
env=production.
Built-in: ssh-access yaml kind: role
version: v1
metadata:
name: ssh-access
description: "Basic SSH access to non-production nodes"
spec:
options:
max_session_ttl: 8h
forward_agent: true
file_copy: true
record_session: best_effort
allow:
logins: ["{{external.username}}", ubuntu]
node_labels:
env: [staging, dev]
deny:
node_labels:
env: production
Creating Roles with mezctl
Use mezctl to create, update, and manage roles.
Role management commands bash # Create a role from a JSON file (the --from-file flag is required)
mezctl roles create --from-file=ssh-production.json
# Or inline with --name and --spec
mezctl roles create --name=ssh-production --spec='{"options":{},"allow":{}}'
# List all roles
mezctl roles ls
# NAME VERSION DESCRIPTION
# admin v1 Full cluster administrator
# editor v1 Can manage users and roles but limited server access
# viewer v1 Read-only access to cluster state
# ssh-access v1 Basic SSH access to non-production nodes
# ssh-production v1 SSH access to production nodes
# Show a specific role (full spec rendered as JSON)
mezctl roles get ssh-production
# Update a role from a file
mezctl roles update --from-file=ssh-production.json
# Delete a role
mezctl roles delete ssh-production
# Assigning roles to a user
# There is no in-place "update user roles" CLI today. The roles list is
# set when the user is created (mezctl users create --roles=...); to
# change a user's roles, modify the user via the gRPC API directly or
# recreate the user with the new role set.
Example Role Definitions
SSH to Production (Non-Root)
ssh-production.yaml yaml kind: role
metadata:
name: ssh-production
description: "SSH access to production nodes, non-root"
spec:
options:
max_session_ttl: 8h
allow:
node_labels:
env: production
logins:
- ubuntu
- deploy
deny:
logins:
- root
node_labels:
sensitivity: restricted
Team-Scoped Access with Template Variables
ssh-team-scoped.yaml yaml kind: role
version: v1
metadata:
name: ssh-team-scoped
description: "SSH access restricted to the user's team nodes"
spec:
options:
max_session_ttl: 12h
allow:
node_labels:
team: "{{internal.team}}"
logins:
- "{{internal.logins}}"
- ubuntu
deny:
logins:
- root
Staging-Only with File Copy Disabled
ssh-staging-readonly.yaml yaml kind: role
metadata:
name: ssh-staging-readonly
description: "SSH to staging, no file transfers or port forwarding"
spec:
options:
max_session_ttl: 4h
port_forwarding: false
file_copy: false
allow:
node_labels:
env: staging
logins:
- ubuntu
deny: {}
Requestable Production Access
can-request-production.yaml yaml kind: role
version: v1
metadata:
name: can-request-production
description: "Can request temporary production SSH access"
spec:
allow:
# No direct node access — only request permissions
request_roles:
- ssh-production
deny: {}
Role Evaluation Order
When a user attempts to SSH into a node, Mezite evaluates roles in this
order:
- Collect all roles assigned to the user.
-
Merge all
allow rules — the union of all allowed node labels
and logins.
-
Merge all
deny rules — the union of all denied node labels
and logins.
-
Check if any allow rule matches the target node and requested login.
-
Check if any deny rule matches the target node and requested login.
- Grant access only if step 4 is true and step 5 is false.
Debugging Role Evaluation
There is no dedicated mezctl access check command yet — debug
role evaluation by inspecting the user and each of their assigned roles, and
by watching the audit log for denial events (any access.denied* event includes the role and rule that caused the denial).
Inspect users and roles bash # List users and the roles each one holds
mezctl users list
# USERNAME ROLES STATUS
# alice ssh-access,ssh-production active
# Print the full spec for a role
mezctl roles get ssh-production
# Watch the audit log for denial events emitted at session setup
mezctl audit ls --type=access.denied --since=1h
mezctl audit ls --type=access.denied.port_forwarding --since=1h
mezctl audit ls --type=access.denied.file_copy --since=1h
Next Steps
- SSH Access — Apply SSH-specific RBAC policies.
- Access Requests — Set up approval
workflows for elevated access.
- SSO Setup — Map SSO attributes to Mezite
roles automatically.
- Audit Logging — Monitor role evaluation
in audit logs.