Skip to content

Ask the HotKV docs

Answers come only from these docs and can be wrong; check the linked pages before you rely on them.

Roles and single sign-on

Last updated

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.

  1. 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 is IAM.AUTH oidc - <token>.
  2. HotKV asks the directory or the provider whether the credentials are right. The client waits; other clients are served meanwhile.
  3. HotKV works out the roles of the login: the roles given to the user name, to ldap:name or oidc:name, and to each group the login is in (group:name).
  4. 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 do CLIENT LIST and the audit log). A login with no role is refused, with NOPERM.

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 500
IAM.ROLE.CREATE office "@all" IP_ALLOWLIST 10.0.0.0/8,fd00::/8

The 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).

IAM.USER.ASSIGN alice app
IAM.USER.ASSIGN group:developers app
IAM.USER.ASSIGN "group:cn=ops,ou=groups,dc=example,dc=com" admin
IAM.USER.ASSIGN oidc:bob reporting
IAM.USER.UNASSIGN alice app

The 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 yes
iam.ldap.url ldaps://ldap.example.com
iam.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.

DirectiveDefaultCONFIG SETDescription
iam.ldap.enabled
TOML: [iam.ldap] enabled
Type: yes/no
noNoTurn LDAP login on (AUTH user password for a name the ACL does not have, and IAM.AUTH ldap).
iam.ldap.url
TOML: [iam.ldap] url
Type: string
(empty)Noldap://host[:389] (upgraded to TLS with STARTTLS before any password is sent) or ldaps://host[:636].
iam.ldap.bind-dn
TOML: [iam.ldap] bind_dn
Type: string
(empty)NoThe service account that searches for the user. Empty means an anonymous search, which many directories refuse.
iam.ldap.bind-password
TOML: [iam.ldap] bind_password
Type: string
(empty)NoPassword of iam.ldap.bind-dn.
iam.ldap.search-base
TOML: [iam.ldap] search_base
Type: string
(empty)NoWhere users are searched for, in the whole subtree below it.
iam.ldap.user-filter
TOML: [iam.ldap] user_filter
Type: string
(uid={username})NoThe search that finds one user. {username} is the login name, escaped. For Active Directory use (sAMAccountName={username}).
iam.ldap.user-dn-template
TOML: [iam.ldap] user_dn_template
Type: string
(empty)NoInstead 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
TOML: [iam.ldap] group_attribute
Type: string
memberOfNoThe 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
TOML: [iam.ldap] group_search_base
Type: string
(empty)NoFor 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
TOML: [iam.ldap] group_filter
Type: string
(member={dn})NoThe group search. {dn} is the user's DN, escaped.
iam.ldap.group-name-attribute
TOML: [iam.ldap] group_name_attribute
Type: string
cnNoThe attribute that names a group found by the group search.
iam.ldap.start-tls
TOML: [iam.ldap] start_tls
Type: yes/no
yesNoUpgrade 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
TOML: [iam.ldap] tls_verify
Type: yes/no
yesNoCheck the directory's certificate against iam.ldap.ca-file and the host in the URL.
iam.ldap.ca-file
TOML: [iam.ldap] ca_file
Type: path
(empty)NoCertificate authorities that vouch for the directory (PEM). Empty means the system's.
iam.ldap.timeout-ms
TOML: [iam.ldap] timeout_ms
Type: milliseconds
5000NoTime allowed for a whole login: connecting, TLS and every reply.
iam.ldap.cache-ttl
TOML: [iam.ldap] cache_ttl
Type: seconds
300NoHow 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.
iam.oidc.enabled yes
iam.oidc.issuer https://login.example.com/
iam.oidc.audience hotkv
iam.oidc.jwks-uri https://login.example.com/.well-known/jwks.json

The 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.

DirectiveDefaultCONFIG SETDescription
iam.oidc.enabled
TOML: [iam.oidc] enabled
Type: yes/no
noNoTurn OpenID Connect login on (IAM.AUTH oidc).
iam.oidc.issuer
TOML: [iam.oidc] issuer
Type: string
(empty)NoThe iss a token must have, exactly as the provider writes it.
iam.oidc.audience
TOML: [iam.oidc] audience
Type: string
(empty)NoThe aud a token must name: this server's client ID at the provider. Required: without it OIDC login stays off.
iam.oidc.jwks-uri
TOML: [iam.oidc] jwks_uri
Type: string
(empty)NoWhere the provider publishes the keys its tokens are signed with. https:// only.
iam.oidc.jwks-file
TOML: [iam.oidc] jwks_file
Type: path
(empty)NoA 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
TOML: [iam.oidc] username_claim
Type: string
subNoThe claim that names the user. preferred_username and email are common.
iam.oidc.groups-claim
TOML: [iam.oidc] groups_claim
Type: string
groupsNoThe claim that lists the user's groups (a list, or one string).
iam.oidc.leeway
TOML: [iam.oidc] leeway
Type: seconds
60NoHow far a clock may be off, for the exp and nbf of a token.
iam.oidc.jwks-cache-ttl
TOML: [iam.oidc] jwks_cache_ttl
Type: seconds
3600NoHow 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
TOML: [iam.oidc] timeout_ms
Type: milliseconds
5000NoTime allowed for fetching the provider's keys.
iam.oidc.ca-file
TOML: [iam.oidc] ca_file
Type: path
(empty)NoCertificate authorities that vouch for the provider's HTTPS certificate (PEM). Empty means the system's.

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 closed
IAM.SESSION.KILL alice # every session of a user

Sessions 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.

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.

DirectiveDefaultCONFIG SETDescription
iam.auth-threads
TOML: [iam] auth_threads
Type: integer
2NoThreads 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
TOML: [iam] auth_queue
Type: integer
64NoLogins that may wait for one of those threads. More are told BUSY.
iam.max-login-failures
TOML: [iam] max_login_failures
Type: integer
10NoFailed 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, …).

  • A memory limit on a role. Nothing counts the memory of one login. Use maxmemory for the server.
  • Tokens in AUTH and HELLO. A token goes in IAM.AUTH. HELLO ... AUTH takes ACL users only.