Roles and single sign-on
ACL users have a password you keep on the server. With
IAM (identity and access management) people log in with the account they
already have, through your LDAP directory (OpenLDAP, Active Directory) or an
OpenID Connect identity provider (Okta, Microsoft Entra ID, Keycloak,
Google and others), and what they can do comes from roles you define once.
IAM needs the enterprise-iam license feature and is for On-Premise
installations.
A login runs as its roles and as nothing else. Each role is a set of commands
and keys, checked the way ACL rules are, with the same
NOPERM errors and the same entries in ACL LOG. A
connection that does not log in through IAM (the default user, an ACL user)
is checked exactly as before.
How a login works
Section titled “How a login works”- The client connects and logs in. For an LDAP user that is
AUTH alice password, which every client library can send at connect. For a token it isIAM.AUTH oidc - <token>. - HotKV asks the directory or the provider whether the credentials are right. The client waits; other clients are served meanwhile.
- HotKV works out the roles of the login: the roles given to the user name,
to
ldap:nameoroidc:name, and to each group the login is in (group:name). - A login with at least one role becomes a session, and the connection
runs as an identity named after it, such as
ldap:alice#7(ACL WHOAMI shows it, and so doCLIENT LISTand the audit log). A login with no role is refused, withNOPERM.
Changes apply at once. When you take a role from a user, change it, or delete it, the sessions that had it lose what it gave on their next command, on every node of a cluster.
IAM.ROLE.CREATE app "@read,@write,@connection,-@dangerous" KEY_PREFIXES app:IAM.ROLE.CREATE reporting "get,mget,scan,ping" KEY_PREFIXES report:,shared:* RATE_LIMIT 500IAM.ROLE.CREATE office "@all" IP_ALLOWLIST 10.0.0.0/8,fd00::/8The second argument lists what the role allows, separated by commas:
| Entry | Meaning |
|---|---|
get, config|get |
Allow a command, or one subcommand of it |
@read, @write, @connection, @all |
Allow a category of commands (* is @all) |
-flushall, -@dangerous |
Never allow it, whatever else the role allows |
KEY_PREFIXES lists the keys the role reaches. An entry with no pattern
character is a prefix (app: means app:*), and one with *, ? or [ is a
pattern. Channels follow the keys: a role limited to app: can publish and
subscribe on app: channels only. With no KEY_PREFIXES, the role reaches all
keys. RATE_LIMIT is commands per second for all the connections of one login
together. IP_ALLOWLIST lists the addresses or networks (CIDR) a login must
come from to have the role; from anywhere else it does not get it.
Most client libraries send a few commands of their own at connect (PING,
CLIENT SETINFO, SELECT), so an application role usually wants @connection.
A role is checked when you create it: an unknown command or category, a bad
network, or a role that allows nothing is an error. So is a role that would let
its users run HotKV’s administrative commands that change the server, such as
IAM.USER.ASSIGN or TENANT.CREATE, which @write includes: that would let
them give themselves any role. Remove them with -@admin or -@dangerous, or
name @admin or @all if an administrator’s role is what you mean.
IAM.ROLE.INFO shows a role in the form you created it, IAM.ROLE.LIST lists
them, and IAM.ROLE.DELETE removes one.
Every server has two roles to begin with: admin (every command on every key)
and readonly (the read and connection commands, on every key, and none of the
administrative ones).
Who gets which role
Section titled “Who gets which role”IAM.USER.ASSIGN alice appIAM.USER.ASSIGN group:developers appIAM.USER.ASSIGN "group:cn=ops,ou=groups,dc=example,dc=com" adminIAM.USER.ASSIGN oidc:bob reportingIAM.USER.UNASSIGN alice appThe first argument is one of:
| Form | Matches |
|---|---|
alice |
The user of that name, whichever way they logged in |
ldap:alice, oidc:alice |
The user, only through that source |
group:name |
Everyone the directory or the token lists in that group, by its name (cn=ops,... is ops) or by its full DN |
IAM.USER.LIST shows who has what. Names are matched exactly as typed at
login, including case.
iam.ldap.enabled yesiam.ldap.url ldaps://ldap.example.comiam.ldap.bind-dn "cn=hotkv,ou=service,dc=example,dc=com"iam.ldap.bind-password "..."iam.ldap.search-base "ou=people,dc=example,dc=com"HotKV finds the user with a search (as the service account bind-dn), then
checks the password by logging in to the directory as that user. The directory’s certificate is checked against
iam.ldap.ca-file (the system’s authorities if you give none) and against the
host in the URL. With ldap:// HotKV upgrades the connection with STARTTLS
before any password is sent. For Active Directory, set
iam.ldap.user-filter "(sAMAccountName={username})".
AUTH <user> <password> goes to the directory for a name the ACL does not
have. A name the ACL has is always the ACL’s, so an LDAP user can never take
the place of an ACL user. HELLO ... AUTH takes ACL users only: log in with
AUTH or IAM.AUTH ldap <user> <password> instead.
| Directive | Default | CONFIG SET | Description |
|---|---|---|---|
iam.ldap.enabled | no | No | Turn LDAP login on (AUTH user password for a name the ACL does not have, and IAM.AUTH ldap). |
iam.ldap.url | (empty) | No | ldap://host[:389] (upgraded to TLS with STARTTLS before any password is sent) or ldaps://host[:636]. |
iam.ldap.bind-dn | (empty) | No | The service account that searches for the user. Empty means an anonymous search, which many directories refuse. |
iam.ldap.bind-password | (empty) | No | Password of iam.ldap.bind-dn. |
iam.ldap.search-base | (empty) | No | Where users are searched for, in the whole subtree below it. |
iam.ldap.user-filter | (uid={username}) | No | The search that finds one user. {username} is the login name, escaped. For Active Directory use (sAMAccountName={username}). |
iam.ldap.user-dn-template | (empty) | No | Instead of a search, the DN of every user, such as uid={username},ou=people,dc=example,dc=com ({username} is escaped as a DN value). No service account is needed. |
iam.ldap.group-attribute | memberOf | No | The attribute of the user's entry that lists their groups (memberOf in Active Directory and OpenLDAP with the memberof overlay). Empty means none. |
iam.ldap.group-search-base | (empty) | No | For a directory with no group attribute: search below this base for the groups that list the user. Empty turns the group search off. |
iam.ldap.group-filter | (member={dn}) | No | The group search. {dn} is the user's DN, escaped. |
iam.ldap.group-name-attribute | cn | No | The attribute that names a group found by the group search. |
iam.ldap.start-tls | yes | No | Upgrade ldap:// to TLS before any password is sent. no sends passwords in the clear. ldaps:// is TLS from the first byte either way. |
iam.ldap.tls-verify | yes | No | Check the directory's certificate against iam.ldap.ca-file and the host in the URL. |
iam.ldap.ca-file | (empty) | No | Certificate authorities that vouch for the directory (PEM). Empty means the system's. |
iam.ldap.timeout-ms | 5000 | No | Time allowed for a whole login: connecting, TLS and every reply. |
iam.ldap.cache-ttl | 300 | No | How long a password the directory accepted is remembered. 0 means every login asks the directory. A changed password or group applies after at most this long. |
OpenID Connect
Section titled “OpenID Connect”iam.oidc.enabled yesiam.oidc.issuer https://login.example.com/iam.oidc.audience hotkviam.oidc.jwks-uri https://login.example.com/.well-known/jwks.jsonThe client gets a token from your provider, then sends it:
IAM.AUTH oidc - <token>HotKV accepts a token only if all of this holds: it is signed with a key the
provider publishes at jwks-uri (RS256, RS384, RS512, PS256, PS384, PS512,
ES256, ES384 or ES512), its iss is iam.oidc.issuer exactly, its aud names
iam.oidc.audience, it has not expired (exp) and is already valid (nbf).
The name after oidc can be -, or the name the token must carry.
The session ends when the token’s exp passes, and the connection is closed;
the client gets a new token and logs in again.
| Directive | Default | CONFIG SET | Description |
|---|---|---|---|
iam.oidc.enabled | no | No | Turn OpenID Connect login on (IAM.AUTH oidc). |
iam.oidc.issuer | (empty) | No | The iss a token must have, exactly as the provider writes it. |
iam.oidc.audience | (empty) | No | The aud a token must name: this server's client ID at the provider. Required: without it OIDC login stays off. |
iam.oidc.jwks-uri | (empty) | No | Where the provider publishes the keys its tokens are signed with. https:// only. |
iam.oidc.jwks-file | (empty) | No | A file with the same key set, instead of iam.oidc.jwks-uri: for a provider you cannot reach, or keys you pin. |
iam.oidc.username-claim | sub | No | The claim that names the user. preferred_username and email are common. |
iam.oidc.groups-claim | groups | No | The claim that lists the user's groups (a list, or one string). |
iam.oidc.leeway | 60 | No | How far a clock may be off, for the exp and nbf of a token. |
iam.oidc.jwks-cache-ttl | 3600 | No | How long the provider's keys are kept before they are fetched again. A token signed with a key HotKV does not know fetches them at once, at most every ten seconds. |
iam.oidc.timeout-ms | 5000 | No | Time allowed for fetching the provider's keys. |
iam.oidc.ca-file | (empty) | No | Certificate authorities that vouch for the provider's HTTPS certificate (PEM). Empty means the system's. |
Sessions
Section titled “Sessions”IAM.SESSION.LIST shows every open session on the node: who, from where,
through which source, with which roles, and when it ends. A session ends when
its connection closes, when its token expires, when the client logs in again
(with AUTH, IAM.AUTH or RESET), and when you end it:
IAM.SESSION.KILL 7 # one session, and its connection is closedIAM.SESSION.KILL alice # every session of a userSessions belong to the node they are on. Roles and who has them are the same on every node of a cluster: a change made on one node reaches the others within about a second.
Guarding against guessing
Section titled “Guarding against guessing”An address that fails iam.max-login-failures logins (default 10) in a
minute is turned away for the rest of it. Failed logins are in
ACL LOG and count in acl_access_denied_auth. The
password or token of IAM.AUTH is never shown by SLOWLOG or MONITOR. If
many clients share one address, for instance behind a proxy, raise the limit or
set it to 0.
At most iam.auth-queue logins (default 64) wait for the iam.auth-threads
threads (default 2) that ask the directory or the provider. More are told
BUSY. A directory that does not answer is given up on after its timeout.
| Directive | Default | CONFIG SET | Description |
|---|---|---|---|
iam.auth-threads | 2 | No | Threads that ask a directory or an identity provider whether a login is right, so a slow one never holds a thread that serves clients. |
iam.auth-queue | 64 | No | Logins that may wait for one of those threads. More are told BUSY. |
iam.max-login-failures | 10 | No | Failed IAM.AUTH from one address in a minute, after which that address is turned away for the rest of the minute. 0 means no limit. |
In hotkv.toml the settings are under [iam], [iam.ldap] and [iam.oidc],
without the prefix and with underscores (bind_dn, search_base,
jwks_uri, auth_threads, …).
What is not supported
Section titled “What is not supported”- A memory limit on a role. Nothing counts the memory of one login. Use
maxmemoryfor the server. - Tokens in
AUTHandHELLO. A token goes inIAM.AUTH.HELLO ... AUTHtakes ACL users only.
Next steps
Section titled “Next steps”- Security for TLS, ACL users, encryption at rest and the audit log
- IAM.ROLE.CREATE, IAM.USER.ASSIGN and IAM.AUTH
Was this page helpful?
Thanks for the feedback.