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 certificates issued cross-cluster under this
role. First-issue user certs are clamped separately at 24h (the
global hard cap); max_session_ttl only tightens that further
for leaf-cluster issuance. The
shortest value across all of a user's roles wins. require_session_mfastring ""Require a per-session MFA challenge before the session starts.
One of totp, webauthn,
hardware-key, or empty. The most restrictive value
across the user's roles wins. port_forwardingbool true (built-in roles)Allow SSH local port forwarding (ssh -L), the tunnel
that IDE remote-development tools such as VS Code Remote-SSH rely
on. Enabled when any of the user's roles sets it true;
otherwise the forwarding channel is refused and an
access.denied.port_forwarding audit event (code T4002W) is emitted, while a granted forward records port_forwarding.start
(T4001I) with the target address. The shipped
admin and ssh-access roles set this true;
the field's bare zero value is false, so a custom
role that omits it denies forwarding. Forwarded traffic is not
session content and is not recorded. disconnect_expired_certbool falseForcibly disconnect a live session the moment the client
certificate expires. Default (off) leaves an established session
running past expiry — only the next connection has to
re-authenticate — which matches normal SSH behavior. Turn it on
for a hard cutoff (useful for long-lived IDE sessions). Enabled
when any of the user's roles sets it true (OR across
roles). file_copybool falseAllow SCP / SFTP file transfers. Enabled when
any of the user's roles sets it true; otherwise transfers
are refused and the proxy emits
access.denied.file_copy. record_sessionstring best_effortPer-role override of the recording-failure contract.
best_effort (default) lets sessions continue if the recording
stream breaks (audit event emitted);
strict kills the session on stream failure regardless of
cluster-default mode. forward_agentbool falseAllow SSH agent forwarding (ssh -A /
ForwardAgent yes). Enabled when
any role allows it. create_host_userbool falseAuto-provision the requested OS login on the node when it does
not exist (agent-based nodes only). Pairs with the
host_groups / host_sudoers
fields in allow. create_host_user_modestring offCleanup policy for auto-provisioned host users:
keep (leave the account in place after session end), drop (remove on last-session close), or off.
Most-permissive wins (keep > drop >
off). create_host_user_default_shellstring ""Default login shell for auto-provisioned host users. First
non-empty value across the user's roles wins. device_trust_modestring ""Require the session to come from an enrolled trusted device. required, optional, or empty. required wins over
optional. auditd_enabledbool falseEmit auditd hand-off events for the session (Linux auditd). Any role with the option set to true enables it. pin_source_ipbool falsePin the issued certificate to the client IP at login time. A
subsequent connection from a different IP is denied with access.denied /
T4001W. Any role with the option set to true enables
it. require_session_joinlist []Repeatable. Each entry requires a population of users (matched by
a predicate over their roles) to join the session in a given mode
(observer or
peer) before the session unblocks. The session is
held at start time until the join condition is met. on_leave: terminate ends the session if the required participants leave.
allow.host_groups, allow.host_sudoers
Used in combination with create_host_user: true. When a
host user is auto-provisioned on a node, the agent adds them to every
group listed in allow.host_groups across the user's roles, and
writes any allow.host_sudoers
fragments into a per-(role, user) file under
/etc/sudoers.d/. Both lists union across roles — write
minimal roles and let assignment do the addition.
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 records the user, the action that was attempted, and a short reason;
it does not name the specific role or rule that fired the denial, so you have
to cross-reference against the user's assigned roles).
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.