This page provides details on the expression language that powers Login Rules.
To learn how to add the first login rule to your cluster, checkout out the
Login Rules Guide.
kind: login_rule
version: v1
metadata:
# name is a unique name for the Login Rule in the cluster.
name: example
# expires is optional and usually should not be set for deployed login
# rules, but it can be useful to set an expiry a short time in the future
# while testing new Login Rules to prevent potentially locking yourself out of
# your teleport cluster.
# expires: "2023-01-31T00:00:00-00:00"
spec:
# priority can be used to order the evaluation of multiple Login Rules within
# a cluster.
#
# Login Rules with lower numbered priorities will be applied first, followed
# by rules with priorities in increasing order. In case of a tie, Login Rules
# with the same priority will be ordered by a lexicographical sort of their
# names.
#
# The default value is 0, the supported range is -2147483648 to 2147483647
# (inclusive).
priority: 0
# If set, traits_map will determine the traits of all users who log in to the
# cluster.
#
# This is a YAML map where the key must be a static string which will be the
# final trait key, and the value is a list of predicate expressions which each
# must evaluate to a set of strings. The final trait will be set to the union
# of the resulting string sets of all predicate expressions for that trait
# key.
#
# traits_map must contain the complete set of desired traits. Any external
# traits not found here will not be included in the user's certificates.
#
# Exactly one of traits_map or traits_expression must be set.
traits_map:
groups:
- external.groups
logins:
- strings.lower(external.username)
# traits_expression is a string holding a single predicate expression which
# must evaluate to a dict. This will set all user's traits during login.
#
# Exactly one of traits_map or traits_expression must be set.
traits_expression: |
external.put("logins", strings.lower(external.logins))
Every login rule spec must contain either the traits_map field or a traits_expression field.
They both serve the same purpose of transforming user traits.
The logic difference lies only in the syntax you prefer for your use case, since you can write
every traits_map as an equivalent traits_expression.
traits_map will remove any traits not specifically included, while the
traits_expression syntax allows you add or modify only specific traits
while keeping the rest unchanged.
The traits_map behavior can be useful if you want to keep only a handful
of necessary traits while filtering out all others.
If lower priority Login Rules set traits, those traits must be also included with higher priority traits_map
to remain populated. For example, the following configuration keeps the groups trait unmodified.
Here is an example Login Rule that uses a traits_map to implement the
following rules:
Every user with the groups: devs trait should receive an extra trait
access: [staging].
Every user with the groups: admins trait should receive an extra trait
access: [staging, prod].
Every user should receive a logins trait with the value of their incoming
username trait converted to lowercase.
All traits other than groups, logins, and access should be filtered
out.
kind:login_ruleversion:v1metadata:name:my_expression_rulespec:priority:0traits_map:# the groups trait will be copied unmodified. Do the same for all other# traits which should not be changed, any traits omitted here will *not* be# set for your users and will *not* be used for role mapping.groups:-external["groups"]# the logins traits will be set to the username trait converted to# lowercase.logins:-'strings.lower(external.username)'# the access trait is determined conditionally based on the incoming groups trait.access:-'ifelse(external.groups.contains("devs"), set("staging"), set())'-'ifelse(external.groups.contains("admins"), set("staging", "prod"), set())'
Every traits expression must return a value of type dict which will
be used as the complete set of output traits.
It is possible to construct a dict from scratch as shown above, or you can
make modifications to the incoming traits stored in the external dict like the
following:
kind:login_ruleversion:v1metadata:name:uppercase_loginsspec:priority:0# This example expression will return all incoming traits unmodified except# for the "logins" trait which will be converted to lowercase.traits_expression:|
external.put("logins", strings.lower(external.logins))
dict is a dictionary type mapping from string keys to set values.
When Login Rule expressions access input traits with the
external.<trait> or external[<trait>] syntax, external is a value of type
dict.
Values of type dict can also be constructed and accessed within expressions.
Expressions used for the traits_expression field must return a value of
type dict.
The "dot" accessor returns the set for the given key, or the empty set if there is not value for that key.
dict["key"]
external["user-name"]
The square brace accessor has the same behavior as the "dot" accessor, but supports keys with special characters (including -, ., , etc) which must be quoted for parsing.
dict.add_values returns a copy of the dict with the given values added to
the set at dict[key].
If there is no set already present at dict[key] a new one will be created.
option is meant to be used exclusively as an argument to choose.
It holds a Boolean condition that may cause the option to be selected and a
value which should be returned by the choose expression if that option is in
fact selected.
strings.replaceall implements substring replacement on sets of strings.
The return value is a copy of input where each substring match of match
found in each element of input will be replaced with replacement.
The matching is literal and does not support regular expressions.
strings.split splits each element of input at each match of separator and
returns a set containing the union of all split strings.
This may be useful when an IdP does not have the ability to pass multi-valued
claims, a Login Rule with this helper function can split a single claim value
into a Teleport trait with multiple values.
regexp.replace finds all matches of expression in each element of input
and replaces them with replacement.
Values which do not match the expression will be filtered out.
The replacement supports expansion of capture groups from expression.
$N is used to refer to the Nth captured group, starting at $1.
ifelse implements the classic if-else branch in a pure functional style.
If the first argument evaluates to true the second argument is returned, else
the third argument is returned.
choose implements a functional style of switch statement, returning the first
option argument with a condition evaluating to true.
If no option can be selected at runtime, this will return an error and the login
will not succeed.
It is recommended to add a final option with the condition hardcoded to true
to implement a default option and avoid this scenario.
For example choose(..., option(true, set())) would return the empty set if no
other option can be selected.