Terminal RBAC
Terminal access is controlled by the terminal_spec field on roles. This follows Teleport's access control model.
Configuring via the Admin UI
- Go to Admin > Roles and edit a role
- Click the Terminal Access tab
- Click Enable Terminal Access (if not already enabled)
- Use the Form editor to configure:
- Session Options — max TTL, idle timeout, concurrent session limit
- Allow Rules — which Linux logins and host labels this role can use
- Deny Rules — logins and hosts to explicitly block (overrides allow)
- Switch to JSON mode for advanced editing or to paste a spec directly
- Save the role — the backend validates the spec before storing
When a user has multiple roles with terminal access, specs are merged automatically: options use the most restrictive values, allow rules are unioned (any role grants access), and deny rules are unioned (any role can deny).
Role Configuration
Each role can have a terminal_spec JSON object with three sections:
Options
| Field | Type | Default | Description |
|---|---|---|---|
max_session_ttl | duration string | 8h | Maximum session duration |
idle_timeout | duration string | 30m | Disconnect after inactivity |
max_sessions | integer | 10 | Max concurrent sessions per user |
disconnect_timeout | duration string | 30s | How long PTY stays alive after disconnect (5s–5m) |
create_host_user_mode | string | off | User creation mode: off, keep (persist), drop (delete on disconnect) |
host_groups | string array | [] | Linux groups to add auto-created users to |
host_sudoers | string array | [] | Sudoers rules written to /etc/sudoers.d/proxima-<username> (mode 0440, visudo-validated) |
default_shell | string | "" | Login shell for auto-created users (falls back to /bin/bash if binary not found) |
uid_min | integer | 0 | Lower bound of deterministic UID range (must be ≥ 1000; 0 = disabled) |
uid_max | integer | 0 | Upper bound of deterministic UID range (must be > uid_min; range must span ≥ 100) |
record_sessions | bool | true | Record terminal sessions. When true, PTY output is captured in asciicast v2 format and uploaded to object storage. |
Allow Rules
| Field | Type | Description |
|---|---|---|
logins | string array | Linux users this role can assume |
host_labels | object | Label matching rules (key → string or string array) |
Deny Rules
Same structure as allow. Deny always overrides allow.
Example Configurations
Full access (admin)
{
"options": {"max_session_ttl": "12h", "idle_timeout": "1h", "max_sessions": 10, "disconnect_timeout": "60s"},
"allow": {"logins": ["root", "ubuntu", "deploy"], "host_labels": {"*": "*"}},
"deny": {}
}
Staging/dev only (developer)
{
"options": {"max_session_ttl": "4h", "idle_timeout": "15m", "max_sessions": 10, "disconnect_timeout": "30s"},
"allow": {"logins": ["deploy", "app-user"], "host_labels": {"env": ["staging", "dev"]}},
"deny": {"logins": ["root"], "host_labels": {"env": "production"}}
}
No terminal access (viewer)
Don't set terminal_spec, or set allow.logins to empty array.
Permissions
| Permission | Description |
|---|---|
terminal:write | Connect to host terminals |
terminal_sessions:read | View all active and historical terminal sessions |
terminal_sessions:write | Force-terminate active terminal sessions |
Validation Rules
The backend validates terminal_spec on save:
| Field | Constraint |
|---|---|
max_session_ttl | Valid Go duration, 1m–24h |
idle_timeout | Valid Go duration, 1m–12h |
max_sessions | Integer 1–100 (0 = default 10) |
disconnect_timeout | Valid Go duration, 5s–5m (0 = default 30s) |
allow.logins / deny.logins | Non-empty strings, no whitespace, max 64 chars. * wildcard allowed |
host_labels keys | Non-empty strings |
host_labels values | String or array of strings. *: * wildcard allowed |
Host User Auto-Creation
When create_host_user_mode is set to keep or drop, the agent automatically creates the Linux user on the target host if it doesn't exist.
Minimal Kubernetes distributions (RKE2, k3s, Talos) lack user management binaries. Host user creation, sudoers management, and custom shells are automatically disabled on these nodes. Connect as root or another existing user instead.
Modes
| Mode | Behavior |
|---|---|
off | No auto-creation (default). User must exist on the host. |
keep | Create user if missing. User persists after disconnect. |
drop | Create user if missing. User deleted when all sessions end. |
Host Groups
The host_groups option specifies which Linux groups the auto-created user is added to (e.g., docker, sudo). Non-existent groups are auto-created.
Tracking
Auto-created users are tracked via system groups:
proxima-keep— users created in keep modeproxima-drop— users created in drop mode (cleaned up by agent)
Host User Options
Sudoers Management
Roles can define sudoers rules that are automatically written to /etc/sudoers.d/proxima-<username> when a user connects:
terminal_spec:
options:
create_host_user_mode: keep
host_sudoers:
- "ALL=(ALL) NOPASSWD: ALL"
- "ALL=(ALL) /usr/bin/systemctl"
Each rule is prefixed with the login username. The agent validates the file with visudo -cf before activating it. If validation fails, the file is removed and a warning is logged — the session is not blocked.
In drop mode, the sudoers file is deleted alongside the user.
Default Shell
Configure the login shell for auto-created users:
terminal_spec:
options:
default_shell: "/bin/zsh"
The agent checks that the shell binary exists before using it. If not found, falls back to /bin/bash with a warning.
Stable UID Ranges
Assign deterministic UIDs from a range so the same username always gets the same UID across all hosts (important for NFS/shared storage):
terminal_spec:
options:
uid_min: 10000
uid_max: 15000
UIDs are computed via FNV-1a hash of the username. If a UID collision occurs, the agent falls back to natural allocation.
Multi-Role Merge Semantics
When a user has multiple roles, options merge with least-permissive semantics:
| Field | Merge Rule |
|---|---|
create_host_user_mode | Most restrictive: off > drop > keep |
host_sudoers | Union of all entries (deduplicated) |
host_groups | Union of all groups |
default_shell | First non-empty value |
uid_min / uid_max | Intersection (narrowest overlap) |
max_session_ttl | Shortest |
idle_timeout | Shortest |
max_sessions | Smallest |
record_sessions | Any role enables → recording on (security wins) |
Login Templates
Role terminal specs support Go template syntax in allow.logins and deny.logins to derive Linux usernames from SSO identity claims. Templates are resolved at connect time using the user's OIDC traits.
Template Variables
| Variable | Derivation | Example |
|---|---|---|
.email | User's email from OIDC claims | [email protected] |
.email_prefix | Everything before @ | j.yerzakov |
.email_domain | Everything after @ | proximaops.io |
.username | Email prefix, dots/hyphens removed | jyerzakov |
.name | User's display name | Jamshid Yerzakov |
.external.<claim> | Any raw OIDC claim | .external.hd → proximaops.io |
Template Functions
| Function | Usage | Description |
|---|---|---|
lower | {{lower .email_prefix}} | Lowercase |
upper | {{upper .email_prefix}} | Uppercase |
replace | {{replace .email_prefix "." "_"}} | String replacement |
trimPrefix | {{trimPrefix "svc-" .username}} | Trim prefix |
trimSuffix | {{trimSuffix "-admin" .username}} | Trim suffix |
default | {{default "deploy" .username}} | Fallback value |
printf | {{printf "prxm-%s" .username}} | Formatted string |
Example
terminal_spec:
allow:
logins:
- "{{.email_prefix}}" # j.yerzakov
- "{{.username}}" # jyerzakov
- "deploy" # static
- '{{printf "prxm-%s" .username}}' # prxm-jyerzakov
host_labels:
env: [staging, dev]
deny:
logins:
- "root"
Templates are validated at role save time (parse + dry-run with dummy data). Invalid templates are rejected with a clear error message. At connect time, resolved logins that don't match the Linux username pattern ([a-z_][a-z0-9_.-]*) are silently filtered out.
Authorization Flow
- User requests terminal access to a host
- Backend resolves user's role and
terminal_spec - Deny rules checked first — if login or host labels match deny, access denied
- Allow rules checked — login must be in allow list AND host labels must match
- If no rule matches, default deny