RBAC Guide
AlmaForge uses role-based access control (RBAC). Permissions are
attached to roles, not to users, and users gain permissions only by
being assigned one or more roles. This page is the field-by-field
reference for the Role resource that carries those permissions.
Human users receive roles through their SSO connector
and can receive temporary elevation through an
Access Request. Keep permanent assignments
in the connector's claim mapping rather than editing temporary SSO
user records.
A role has two halves:
allow: what holders of this role can do.deny: what they cannot do, even ifallowwould otherwise permit it.
AlmaForge evaluates a user's roles when their certificate is issued and on every connection to a resource. Additional roles can grant more permissions, but a connection requires one role to allow both the target and the requested identity, such as a Linux login or database user. A matching deny condition takes precedence over an allow. Resource discovery, connection access, and administrative API permissions are distinct. A role can let a user find a target without granting a connection to it.
AlmaForge ships five built-in roles you can assign as-is or use as a starting point.
Resource shape
# Minimal Role resource showing the canonical document shape.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: example
spec:
options: {}
allow: {}
deny: {}
spec and options field names are camelCase. Snake_case input from
older clusters is accepted on read. Output is always camelCase.
Manage roles with the Web UI, with alma, or through the
API:
alma get rolealma get role/example -o yamlalma apply -f role.yamlKeep an existing admin session available while changing access roles, then test the affected user's sign-in and resource access. See break-glass access for recovery if a role change removes administrative access.
Built-in roles
AlmaForge ships five preset roles. They are real Role resources.
You can read, edit, or delete them like any other role. The labels
on each preset (internal/resource-type: preset) flag them in the
Web UI.
access
Connect to enrolled servers, applications, databases, and Kubernetes
clusters, and nothing else. It carries no rules at all, so it reads
neither the audit log, nor session recordings, nor the instance
inventory. The preset member role can request this role. Its
certificate lifetime is capped at twelve hours. Principal lists template from the user's
traits and fall back to the accounts common Linux cloud images ship
with.
access role YAML
apiVersion: almaforge.com/v1
kind: Role
metadata:
annotations:
almaforge.com/description: Connect to every resource without administering the cluster
labels:
internal/resource-type: preset
name: access
spec:
allow:
appLabels:
'*': '*'
databaseLabels:
'*': '*'
databaseNames:
- '{{internal.db_names}}'
databaseRoles:
- '{{internal.db_roles}}'
databaseServiceLabels:
'*': '*'
databaseUsers:
- '{{internal.db_users}}'
kubernetesGroups:
- '{{internal.kubernetes_groups}}'
kubernetesLabels:
'*': '*'
kubernetesResources:
- kind: '*'
name: '*'
namespace: '*'
verbs:
- '*'
kubernetesUsers:
- '{{internal.kubernetes_users}}'
serverLabels:
'*': '*'
serverLogins:
- '{{internal.logins}}'
- root
- ec2-user
- ubuntu
deny: {}
options:
forwardAgent: true
maxSessionTTL: 12h0m0s
portForwarding: true
sshFileCopy: true
admin
Manage every resource, including the roles and users that decide who
else has access, and connect to everything the cluster knows about as
root, system:masters or any database user. Certificate lifetime is
capped at one hour.
admin role YAML
apiVersion: almaforge.com/v1
kind: Role
metadata:
annotations:
almaforge.com/description: Full access to manage every resource, and to connect to all of them as root, system:masters or any database user
labels:
internal/resource-type: preset
name: admin
spec:
allow:
appLabels:
'*': '*'
databaseLabels:
'*': '*'
databaseNames:
- '*'
databaseServiceLabels:
'*': '*'
databaseUsers:
- '*'
kubernetesGroups:
# Hardwired into the Kubernetes API server's authorizer, which means it
# bypasses that cluster's own RBAC.
- system:masters
kubernetesLabels:
'*': '*'
kubernetesResources:
- kind: '*'
name: '*'
namespace: '*'
verbs:
- '*'
rules:
- resources:
- '*'
verbs:
- '*'
serverLabels:
'*': '*'
serverLogins:
# The accounts common Linux cloud images ship with.
- root
- ec2-user
- ubuntu
deny: {}
options:
forwardAgent: true
# Short by design: this bounds every session the role grants, including a
# redeemed break-glass session.
maxSessionTTL: 1h0m0s
portForwarding: true
sshFileCopy: true
auditor
Read every resource, plus the audit log and session recordings, and change none of it. It also sees the server, database and Kubernetes inventory, because what is enrolled is itself audit evidence, and it can connect to none of it: reaching any of them needs an identity on top of a label match and this role has none. The certificate it receives carries a login that cannot name a real Linux account.
Applications are the exception and are deliberately not included. A plain web or TCP application authorizes on the label match alone, so listing them here would grant access rather than sight.
auditor role YAML
apiVersion: almaforge.com/v1
kind: Role
metadata:
annotations:
almaforge.com/description: Read-only review of audit events, recordings, and configuration
labels:
internal/resource-type: preset
name: auditor
spec:
allow:
# Wildcard labels make the inventory visible. With no login, Kubernetes
# identity or database user anywhere in the role, none of it can be
# connected to. Applications are absent on purpose: a plain web or TCP
# application authorizes on the label match alone, so a grant here would
# be access rather than sight.
databaseLabels:
'*': '*'
databaseServiceLabels:
'*': '*'
kubernetesLabels:
'*': '*'
kubernetesResources:
- kind: '*'
name: '*'
namespace: '*'
verbs:
- '*'
serverLabels:
'*': '*'
rules:
# The auditor reads everything at the redacted tier: list plus
# read, where read returns secrets masked. Secret-bearing kinds
# served outside the redaction chokepoint (tokens, trusted
# clusters, CAs) require readwithsecrets, which this does not
# grant, so the auditor is denied there rather than shown a
# secret. Session read authorizes recording replay.
- resources:
- '*'
verbs:
- list
- read
deny: {}
options:
maxSessionTTL: 8h0m0s
member
The role a user starts with. It carries no rules, labels, or logins,
so it can see every resource in the cluster and open none of them.
What it can do is ask: a member requests either the access role or
specific resources, and a reviewer decides.
member role YAML
apiVersion: almaforge.com/v1
kind: Role
metadata:
annotations:
almaforge.com/description: See every resource and request access to it
labels:
internal/resource-type: preset
name: member
spec:
allow:
request:
# searchAsRoles makes every resource visible for the purpose of
# asking for it. roles is what an approved request grants, narrowed
# to the resources named in the request.
searchAsRoles:
- access
roles:
- access
deny: {}
options:
maxSessionTTL: 24h0m0s
reviewer
Approve or deny Access Requests. Approving is a privilege of its own: the role grants no access to anything it approves, and nobody can review their own request, so manual approval requires another reviewer.
reviewer role YAML
apiVersion: almaforge.com/v1
kind: Role
metadata:
annotations:
almaforge.com/description: Approve or deny access requests
labels:
internal/resource-type: preset
name: reviewer
spec:
allow:
reviewRequests:
previewAsRoles:
- access
roles:
- access
deny: {}
options:
maxSessionTTL: 12h0m0s
Examples
Each example below is a complete role you can copy and apply.
SSH server access
A developer role: SSO-templated Linux logins, host user auto-provisioning, access limited to dev and staging environments.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: dev-ssh
spec:
options:
maxSessionTTL: 8h
createHostUserMode: keep
allow:
# Use the claim name supplied by the OIDC provider.
# The connector name is not part of the trait key.
serverLogins:
- '{{external.preferred_username}}'
hostGroups: ['developers', 'docker']
serverLabels:
env: ['dev', 'staging']
Database access with auto-provisioned users
Read-only access to staging databases. AlmaForge creates a database user named after the SSO email's local part on first connect.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: db-readonly
spec:
options:
createDbUserMode: keep
allow:
databaseLabels:
env: 'staging'
# Provision a database user named after the local part of the
# SSO email claim supplied by the identity provider.
databaseUsers:
- '{{email.local(external.email)}}'
databaseNames: ['*']
databaseRoles: ['reader']
Kubernetes namespace access
Access to staging clusters with broad read across the namespace and exec/portforward limited to pods. Cluster-wide resources stay out of reach.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: k8s-team-alpha
spec:
allow:
kubernetesLabels:
env: 'staging'
kubernetesGroups: ['developers']
kubernetesResources:
# Read across the namespace.
- kind: '*'
namespace: 'team-alpha'
name: '*'
verbs: [get, list, watch]
# Exec and port-forward, but only into pods.
- kind: pod
namespace: 'team-alpha'
name: '*'
verbs: [exec, portforward]
Application access
Web and TCP app access scoped by label. Start here and tighten the label selector before production.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: aws-engineer
spec:
allow:
# Launch web and TCP apps labeled env=dev or env=staging. Add or
# tighten labels to scope which apps this role can reach.
appLabels:
env: ['dev', 'staging']
Request elevated access
A baseline role that lets its holder request temporary elevation to a more privileged role. The grant needs approval and expires on its own. Attach it alongside the user's everyday role.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: access-requester
spec:
allow:
request:
# Roles this user may request temporarily. The grant needs
# approval and expires automatically. Attach this role
# alongside the user's baseline (non-elevated) role.
roles: ['dba']
maxDuration: 2h
Approve access requests
The reviewer side of the same flow. This role lets its holder approve or deny requests for a privileged role, without granting that role's access. Attach it to your reviewers alongside their everyday role.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: access-approver
spec:
allow:
reviewRequests:
# Roles whose access requests a holder may approve or deny.
# Attach this role to your reviewers alongside their everyday
# role. It grants no infrastructure access of its own.
roles: ['dba']
# previewAsRoles lets a reviewer see the resources a request
# covers, so they can judge it before approving.
previewAsRoles: ['dba']
Restrict access to your own session recordings
The preset auditor role grants access to all sessions and
recordings. A user holding auditor is not limited by this rule.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: own-recordings
spec:
allow:
rules:
- resources: [Recording]
verbs: [list, read]
# An SSH recording lists everyone who was in the session, so a
# user who joined one sees it as well as one they started. A
# database session has a single identity and no participants, so
# the second half is what matches it.
where: contains(recording.participants, user.metadata.name) || equals(recording.user, user.metadata.name)
Restrict access to your own active sessions
Session access defaults to permissive for the user's own sessions,
so this uses deny to filter out everything else:
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: own-active-sessions
spec:
allow:
rules:
- resources: [Session]
verbs: ['*']
deny:
rules:
- resources: [Session]
verbs: [list, read, update, delete]
where: '!contains(session.participants, user.metadata.name)'
spec.options
When a user has multiple roles, numeric limits merge to the strictest
value: the shortest TTL, the shortest timeout, and the lowest
connection limit. Recording policy merges the same way, with strict
overriding bestEffort.
The booleans in the table below merge with OR. Agent forwarding, port
forwarding, X11, and file copy are on if any role grants them, and
pinSourceIp and disconnectExpiredCert are on if any role sets them.
This covers the booleans the table documents and no others.
createHostUserMode is not one of them, and
merges the other way.
| Field | Description |
|---|---|
maxSessionTTL | Maximum lifetime of issued user certificates. |
clientIdleTimeout | Disconnect idle interactive sessions after this duration. |
disconnectExpiredCert | Terminate active sessions when the client's certificate expires. |
maxSessions | Maximum session channels per SSH connection. |
maxConnections | Maximum concurrent sessions per user, cluster-wide. |
maxKubernetesConnections | Maximum concurrent Kubernetes sessions per user. |
forwardAgent | Permit SSH agent forwarding. |
portForwarding | Permit TCP port forwarding over SSH. |
permitX11Forwarding | Permit X11 forwarding through OpenSSH clients. |
sshFileCopy | Permit SCP and SFTP. Not permitted unless set. |
pinSourceIp | Bind issued SSH certificates to the client IP that authenticated. |
recordSession | Per-protocol session recording mode (see below). Audit modes. |
lock | Locking mode. One of bestEffort, strict. strict wins on merge. |
requestAccess | Access Request strategy. One of optional, always, reason. |
requestPrompt | Prompt shown to users for the Access Request "reason" field. |
createHostUserMode | Auto-provision Linux host users. One of off, drop, keep. |
createDbUserMode | Auto-provision database users. One of off, keep, bestEffortDrop. |
certExtensions | Custom extensions added to issued SSH certificates (see below). |
recordSession has default and protocol-specific fields such as
ssh. Each accepts bestEffort or strict. certExtensions is a
list of extension names and values.
# Role options for strict SSH recording and a custom certificate extension.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: recorded-github-access
spec:
options:
recordSession:
default: bestEffort
ssh: strict
certExtensions:
- name: login@github.com
value: '{{external["github_login"]}}'
spec.allow and spec.deny
Both sections accept the same fields. deny is evaluated first, so it
overrides allow.
Labels
A role grants access to enrolled resources by matching their labels. A label entry is a key plus one or more values:
# Role that allows staging servers except database and backup workloads.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: staging-servers
spec:
allow:
serverLabels:
env: stage
region: [us-west-1, eu-central-1]
deny:
serverLabels:
workload: [database, backup]
This grants access to nodes labeled env=stage and in either
region, except those also labeled workload=database or
workload=backup. Multiple keys are AND. Multiple values for one
key are OR. Values can be a literal string, a list, the wildcard
*, or a Go regular expression anchored with ^ and $.
The label fields by resource kind:
| Field | Resource |
|---|---|
serverLabels | SSH servers |
appLabels | Applications |
databaseLabels | Databases |
databaseServiceLabels | Database Service agents |
kubernetesLabels | Kubernetes clusters |
Every *Labels field has a parallel *LabelsExpression predicate
field (serverLabelsExpression, appLabelsExpression, etc.). Use the
predicate form when you need OR across keys, negation, or matching
on label keys:
# Role that matches staging servers or servers owned by one of the user's teams.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: team-servers
spec:
allow:
serverLabelsExpression: |
labels["env"] == "staging" ||
contains(user.spec.traits["teams"], labels["team"])
When a role sets both *Labels and *LabelsExpression, both must
match in allow. Either match denies access in deny.
Resource labels can be dynamic. An agent can set them from the output of a command at startup, letting you write rules like "any host running PostgreSQL is reachable only by the DBA group".
Principals
These fields list the identities a user can assume on a target resource. Group them by infrastructure type:
- Servers:
serverLogins,hostGroups,hostSudoers - Kubernetes:
kubernetesGroups,kubernetesUsers,kubernetesResources - Database:
databaseUsers,databaseNames,databaseRoles
hostGroups and hostSudoers apply when host user
auto-provisioning is enabled via createHostUserMode.
kubernetesResources is structured. Each entry is
{kind, namespace, name, verbs} and constrains which objects in a
matched cluster the user may touch. Wildcards and anchored regular
expressions are supported on namespace and name. See
Kubernetes Access for the full
kind and verb lists.
Rules
rules controls access to dynamic AlmaForge resources such as
roles, users, tokens, and audit events. Each rule is a list of
resource kinds plus a list of verbs, with an optional where
predicate:
# Administrative and audit permissions scoped by resource kind.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: resource-manager
spec:
allow:
rules:
- resources: [Role, User, AuthConnector]
verbs: [list, create, read, update, delete]
- resources: [Recording, Event]
verbs: [list, read]
- resources: [Recording]
verbs: [list, read]
where: contains(recording.participants, user.metadata.name)
Verbs:
| Verb | Use |
|---|---|
list | Enumerate resources of a kind. |
read | Read one resource by name, with secret fields redacted. |
readwithsecrets | Read one resource including its secret fields. |
create | Create a new resource. |
update | Modify an existing resource. |
delete | Delete a resource. |
use | Use a resource (for example, an OIDC integration). |
* | All verbs above. |
Every user can read the roles they hold without a rule granting it. A
role definition is the user's own permission set, so a user sees what
they are allowed to do. It covers no other role, it does not grant
list, and it does not include readwithsecrets. A deny rule on
Role overrides it, as deny overrides any allow.
Resource kinds are PascalCase. Common kinds, grouped by purpose:
- Identity:
Role,User - SSO:
AuthConnector,OIDC,OIDCConnector,Github,GithubConnector - Targets:
Server,AppTarget,DatabaseTarget,KubernetesTarget - Sessions:
Recording(recordings),Session(active),Event(audit) - Trust:
CertificateAuthority - Operations:
Lock,Token,AccessRequest,AccessPluginData - Cluster config:
AuthConfig,RecordingConfig,NetworkConfig,ClusterName,StorageConfig
The full kind list is in the Declarative Configuration.
A user with create on Token can mint a join token for any agent
type and any trust relationship. Treat any role that grants Token
write access as administrative.
Impersonation
impersonate permits issuing certificates on behalf of other users
and roles. Always scope it with where. An unscoped impersonate
role is equivalent to admin.
# Impersonation role restricted to roles carrying the user's group label.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: ci-impersonation
spec:
allow:
impersonate:
users: ['*']
roles: [jenkins]
where: >
contains(user.spec.traits["group"],
impersonate_role.metadata.labels["group"])
Access Requests
request is what a user can ask for. reviewRequests is what they
can approve. Request roles support glob and ^...$ regular
expression matchers. searchAsRoles lets the user temporarily assume
roles to search for Resource Access Request targets. maxDuration
caps requested duration at 7 days. claimsToRoles derives role names
from SSO claims using regex capture groups. See
Access Request roles and the full operator
flow in Just-in-Time Access.
# Request and review policy with thresholds and claim-derived roles.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: access-request-policy
spec:
allow:
request:
roles: [common, 'dev-*']
searchAsRoles: [access]
maxDuration: 7d
thresholds:
- approve: 2
deny: 1
claimsToRoles:
- claim: projects
value: '^product-(.*)$'
roles: ['$1-admin']
annotations:
groups: ['{{external["groups"]}}']
reviewRequests:
roles: [dbadmin]
previewAsRoles: [dbadmin]
where: 'contains(reviewer.traits["team"], "dbadmin")'
Templates
Most role fields accept template variables that resolve at login
from the user's traits and SSO claims. Variables appear in
{{ ... }}.
Templates work in label fields, principal fields,
impersonate.where, and reviewRequests.where. They do not expand
inside the role-name lists request.roles, request.searchAsRoles,
reviewRequests.roles, and reviewRequests.previewAsRoles. Those
use claimsToRoles.
Internal variables
Internal variables resolve from the traits available when a template is
evaluated. The variables below are the ones AlmaForge sets, and an
unrecognized name produces an unsupported-variable error. Variable names
use snake_case.
| Variable | Value |
|---|---|
{{internal.logins}} | Linux logins. |
{{internal.kubernetes_users}} | Kubernetes users. |
{{internal.kubernetes_groups}} | Kubernetes groups. |
{{internal.db_users}} | Database users. |
{{internal.db_names}} | Database names. |
{{internal.db_roles}} | Database roles for automatic database user provisioning. |
{{internal.jwt}} | JWT header for application access. |
External variables
External traits come from the user's SSO assertion. OIDC claim names
are used directly: external.email, external.groups, and
external.preferred_username. GitHub supplies external.logins and
external.teams. Check the user's spec.traits to see which values
their provider supplies.
For example, set a serverLogins entry to
'{{external.preferred_username}}'.
Use bracket syntax for claim names containing /. Dot syntax only works
for plain identifiers without /. AlmaForge expands an external
variable to a string or a list of strings. Configure your identity
provider to emit a supported claim type.
Functions
| Function | Use |
|---|---|
email.local(variable) | Local part of an email address. email.local("alice@example.com") returns alice. |
regexp.replace(variable, expression, replacement) | Apply a Go regular expression substitution. Capture groups are referenced as $1, $2, etc. Values that do not match are filtered out. |
regexp.match(expression) | True if any of the user's roles matches the expression. Used in Access Request fields. |
regexp.not_match(expression) | True if no role matches the expression. |
A regexp expression is treated as a Go regular expression when it
starts with ^ and ends with $. Otherwise it is a glob, with *
as the wildcard for one or more characters.
Evaluation rules
How a template field renders for a logged-in user:
- A field can mix literals and variables.
'IAM#{{external["email"]}};'becomes'IAM#alice@example.com;'. - A list-typed variable expands into list entries.
kubernetesGroups: ['{{external["groups"]}}']becomes['admins', 'devs']when the claim is["admins", "devs"]. - An unset trait renders to nothing.
serverLogins: ['{{external["preferred_username"]}}', 'admin']becomes['admin']if the claim is missing. regexp.replacefilters as well as substitutes. Values that do not match the expression are dropped from the result.
Access Request roles
The role-name lists in request and reviewRequests do not expand
{{external.x}}. Use claimsToRoles to derive role names from SSO
claims. Each entry matches a claim against a regular expression and
emits one or more role names. Capture groups are available as $1,
$2, and so on.
claim-derived-request-roles.yaml:
# Access-request roles derived from project names in an SSO claim.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: project-access
spec:
allow:
request:
claimsToRoles:
- claim: projects
value: '^product-(.*)$'
roles: ['$1-admin']
A user whose projects claim contains product-alpha and
product-beta can request the alpha-admin and beta-admin roles.
Predicate language
Roles use two predicate dialects with different field models. Both
share &&, ||, and ! as operators.
where clauses
Used in rules[].where, impersonate.where, and
reviewRequests.where. Two functions:
| Function | Use |
|---|---|
contains(field, value) | True if the list field contains value. |
equals(field, value) | True if field equals value. value may be another field or a string literal. |
Fields:
| Field | Meaning |
|---|---|
user.metadata.name | The user's name. |
user.spec.roles | The user's role names. |
user.spec.traits | The user's traits, including SSO claims. |
recording.participants | Participants on an SSH session recording. |
recording.user | The user a session recording belongs to. |
session.participants | Participants on an active session. |
impersonate_user | The user being impersonated. |
impersonate_role | The role being impersonated. |
reviewer.spec.traits | The reviewing user's traits. |
For example: contains(recording.participants, user.metadata.name) || equals(recording.user, user.metadata.name).
Only an SSH recording carries recording.participants, so a rule naming
it alone matches no database recording. Naming recording.user beside it
covers both: an SSH session a user joined, and a database session that is
theirs. A field the recording does not carry is a mismatch rather than an
error, so either half of the rule can carry it alone.
Label expressions
Used in every *LabelsExpression field. Adds == and != to the
operator set.
Fields:
| Field | Type | Meaning |
|---|---|---|
labels | map[string]string | Static and dynamic labels of the resource. |
user.spec.traits | map[string][]string | All traits of the user. |
Functions (any argument named list may be a list-typed value, a
single string, or a string literal):
| Function | Returns | Use |
|---|---|---|
contains(list, item) | bool | True if list contains an exact match for item. |
contains_any(list, items) | bool | True if list contains any element of items. |
contains_all(list, items) | bool | True if list contains every element of items. |
regexp.match(list, re) | bool | True if any item in list matches re. |
regexp.replace(list, re, replacement) | []string | Replace matches of re in each item of list. |
email.local(list) | []string | Local part of each email. Errors if any value is not an email. |
strings.upper(list) | []string | Uppercase each item. |
strings.lower(list) | []string | Lowercase each item. |
labels_matching(re) | []string | Aggregate of all label values whose key matches re (glob or regexp). |
For example: labels["env"] == "staging" || contains(user.spec.traits["teams"], labels["team"]).
Further reading
- Just-in-Time Access: request, approval, and review lifecycle for the fields above.
- Workload Identity: bot impersonation
and the
system:botsystem role used by workload joins. - Revoking access:
Lockresources for blocking a user, role, login, or session. - SSO Integration: connector setup and
claimsToRolesfor OIDC providers. - Declarative Configuration: full
schema for
Role,User,Lock, andOIDCConnector. - Audit Reference: event codes and modes
referenced by
recordSession.