Introduction to Kanidm

Kanidm is an identity management server, acting as an authority on account information, authentication and authorisation within a technical environment.

The intent of the Kanidm project is to:

  • Provide a single truth source for accounts, groups and privileges.
  • Enable integrations to systems and services so they can authenticate accounts.
  • Make system, network, application and web authentication easy and accessible.
  • Secure and reliable by default, aiming for the highest levels of quality.
Kani Warning NOTICE
Kanidm is still a work in progress. Many features will evolve and change over time which may not be suitable for all users.

Why do I want Kanidm?

Whether you work in a business, a volunteer organisation, or are an enthusiast who manages their personal services, you need methods of authenticating and identifying to your systems, and subsequently, ways to determine what authorisation and privileges you have while accessing these systems.

We've probably all been in workplaces where you end up with multiple accounts on various systems - one for a workstation, different SSH keys for different tasks, maybe some shared account passwords. Not only is it difficult for people to manage all these different credentials and what they have access to, but it also means that sometimes these credentials have more access or privilege than they require.

Kanidm acts as a central authority of accounts in your organisation and allows each account to associate many devices and credentials with different privileges. An example of how this looks:

                           │                  ││
      ┌───────────────┬───▶│      Kanidm      │◀─────┬─────────────────────────┐
      │               │    │                  ├┘     │                         │
      │               │    └──────────────────┘      │                       Verify
 Account Data         │              ▲               │                       Radius
  References          │              │               │                      Password
      │               │              │               │                         │
      │               │              │               │                  ┌────────────┐
      │               │              │               │                  │            │
      │               │              │            Verify                │   RADIUS   │
┌────────────┐        │        Retrieve SSH     Application             │            │
│            │        │         Public Keys      Password               └────────────┘
│  Database  │        │              │               │                        ▲
│            │        │              │               │                        │
└────────────┘        │              │               │               ┌────────┴──────┐
       ▲              │              │               │               │               │
       │              │              │               │               │               │
┌────────────┐        │       ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌────────────┐
│            │        │       │            │  │            │  │            │  │            │
│  Web Site  │        │       │    SSH     │  │   Email    │  │    WIFI    │  │    VPN     │
│            │        │       │            │  │            │  │            │  │            │
└────────────┘        │       └────────────┘  └────────────┘  └────────────┘  └────────────┘
       ▲              │              ▲               ▲               ▲               ▲
       │              │              │               │               │               │
       │              │              │               │               │               │
       │          Login To           │               │               │               │
   SSO/Oauth     Oauth/SSO       SSH Keys       Application        Radius         Radius
       │              │              │           Password         Password       Password
       │              │              │               │               │               │
       │              │              │               │               │               │
       │              │              │               │               │               │
       │              │        ┌──────────┐          │               │               │
       │              │        │          │          │               │               │
       └──────────────┴────────│  Laptop  │──────────┴───────────────┴───────────────┘
                               │          │
                               │   You    │

A key design goal is that you authenticate with your device in some manner, and then your device will continue to authenticate you in the future. Each of these different types of credentials, from SSH keys, application passwords, to RADIUS passwords and others, are "things your device knows". Each password has limited capability, and can only access that exact service or resource.

This helps improve security; a compromise of the service or the network transmission does not grant you unlimited access to your account and all its privileges. As the credentials are specific to a device, if a device is compromised you can revoke its associated credentials. If a specific service is compromised, only the credentials for that service need to be revoked.

Due to this model, and the design of Kanidm to centre the device and to have more per-service credentials, workflows and automation are added or designed to reduce human handling.

Library documentation

Looking for the rustdoc documentation for the libraries themselves? Click here!

Frequently Asked Questions

... or ones we think people might ask.

Why disallow HTTP (without TLS) between my load balancer and Kanidm?

Because Kanidm is one of the keys to a secure network, and insecure connections to them are not best practice.

Please refer to Why TLS? for a longer explanation.

Why so many crabs?

It's a rust thing.

Will you implement -insert protocol here-

Probably, on an infinite time-scale! As long as it's not Kerberos. Or involves SSL or STARTTLS. Please log an issue and start the discussion!

Why do the crabs have knives?

Don't ask. They just do.

Why won't you take this FAQ thing seriously?

Look, people just haven't asked many questions yet.

Installing the Server

This chapter will describe how to plan, configure, deploy and update your Kanidm instances.

Choosing a Domain Name

Through out this book, Kanidm will make reference to a "domain name". This is your chosen DNS domain name that you intend to use for Kanidm. Choosing this domain name however is not simple as there are a number of considerations you need to be careful of.

Kani Warning Take note!
Incorrect choice of the domain name may have security impacts on your Kanidm instance, not limited to credential phishing, theft, session leaks and more. It is critical you follow the advice in this chapter.


Domain Ownership

It is recommended you use a domain name within a domain that you own. While many examples list throughout this book, it is not recommended to use this outside of testing. Another example of risky domain to use is local. While it seems appealing to use these, because you do not have unique ownership of these domains, if you move your machine to a foreign network, it is possible you may leak credentials or other cookies to these domains. TLS in a majority of cases can and will protect you from such leaks however, but it should not always be relied upon as a sole line of defence.

Failure to use a unique domain you own, may allow DNS hijacking or other credential leaks in some circumstances.


Due to how web browsers and webauthn work, any matching domain name or subdomain of an effective domain may have access to cookies within a browser session. An example is that has access to cookies from and

For this reason your kanidm host (or hosts) should be on a unique subdomain, with no other services registered under that subdomain. For example, consider as a subdomain for exclusive use of kanidm. This is inverse to Active Directory which often has it's domain name selected to be the parent (toplevel) domain (

Failure to use a unique subdomain may allow cookies to leak to other entities within your domain, and may allow webauthn to be used on entities you did not intend for which may or may not lead to some phishing scenarioes.


Good Domain Names

Consider we own If we were to run geographical instances, and have testing environments the following domain and hostnames could be used.


  • origin:
  • domain name:
  • host names:,

This allows us to have named geographical instances such as which still works with webauthn and cookies which are transferable between instances.

It is critical no other hosts are registered under this domain name.


  • origin:
  • domain name:
  • host names:,

Note that due to the name being vs, the testing instance is not a subdomain of production, meaning the cookies and webauthn tokens can NOT be transferred between them. This provides proper isolation between the instances.

Bad Domain Names

idm.local - This is a bad example as .local is an mDNS domain name suffix which means that client machines if they visit another network may try to contact idm.local believing they are on their usual network. If TLS verification were disabled, this would allow leaking of credentials. - This is bad because the use of the top level domain means that any subdomain can access the cookies issued by, effectively leaking them to all other hosts.

Second instance overlap:


  • origin:
  • domain name:


  • origin:
  • domain name:

While the production instance has a valid and well defined subdomain that doesn't conflict, because the dev instance is a subdomain of production, it allows production cookies to leak to dev. Dev instances may have weaker security controls in some cases which can then allow compromise of the production instance.

Preparing for your Deployment

Software Installation Method

NOTE Our preferred deployment method is in containers, and this documentation assumes you're running in docker. Kanidm will alternately run as a daemon/service, and server builds are available for multiple platforms if you prefer this option.

We provide docker images for the server components. They can be found at:

You can fetch these by running the commands:

docker pull kanidm/server:x86_64_latest
docker pull kanidm/radius:latest

If you do not meet the system requirements for your CPU you should use:

docker pull kanidm/server:latest

You may need to adjust your example commands throughout this document to suit your desired server type.

Development Version

If you are interested in running the latest code from development, you can do this by changing the docker tag to kanidm/server:devel or kanidm/server:x86_64_v3_devel instead. Many people run the development version, and it is extremely reliable, but occasional rough patches may occur. If you report issues, we will make every effort to help resolve them.

System Requirements


If you are using the x86_64 cpu-optimised version, you must have a CPU that is from 2013 or newer (Haswell, Ryzen). The following instruction flags are used.

cmov, cx8, fxsr, mmx, sse, sse2, cx16, sahf, popcnt, sse3, sse4.1, sse4.2, avx, avx2,
bmi, bmi2, f16c, fma, lzcnt, movbe, xsave

Older or unsupported CPUs may raise a SIGIL (Illegal Instruction) on hardware that is not supported by the project.

In this case, you should use the standard server:latest image.

In the future we may apply a baseline of flags as a requirement for x86_64 for the server:latest image. These flags will be:

cmov, cx8, fxsr, mmx, sse, sse2
Kani Alert Tip
You can check your cpu flags on Linux with the command `lscpu`


Kanidm extensively uses memory caching, trading memory consumption to improve parallel throughput. You should expect to see 64KB of ram per entry in your database, depending on cache tuning and settings.


You should expect to use up to 8KB of disk per entry you plan to store. At an estimate 10,000 entry databases will consume 40MB, 100,000 entry will consume 400MB.

For best performance, you should use non-volatile memory express (NVME), or other Flash storage media.


You'll need a volume where you can place configuration, certificates, and the database:

docker volume create kanidmd

You should have a chain.pem and key.pem in your kanidmd volume. The reason for requiring Transport Layer Security (TLS, which replaces the deprecated Secure Sockets Layer, SSL) is explained in why tls. In summary, TLS is our root of trust between the server and clients, and a critical element of ensuring a secure system.

The key.pem should be a single PEM private key, with no encryption. The file content should be similar to:


The chain.pem is a series of PEM formatted certificates. The leaf certificate, or the certificate that matches the private key should be the first certificate in the file. This should be followed by the series of intermediates, and the final certificate should be the CA root. For example:

<leaf certificate>
<intermediate certificate>
[ more intermediates if needed ]
<ca/croot certificate>

HINT If you are using Let's Encrypt the provided files "fullchain.pem" and "privkey.pem" are already correctly formatted as required for Kanidm.

You can validate that the leaf certificate matches the key with the command:

openssl ec -in key.pem -pubout | openssl sha1
openssl x509 -in chain.pem -noout -pubkey | openssl sha1

# openssl rsa -noout -modulus -in key.pem | openssl sha1
# openssl x509 -noout -modulus -in chain.pem | openssl sha1

If your chain.pem contains the CA certificate, you can validate this file with the command:

openssl verify -CAfile chain.pem chain.pem

If your chain.pem does not contain the CA certificate (Let's Encrypt chains do not contain the CA for example) then you can validate with this command.

openssl verify -untrusted fullchain.pem fullchain.pem

NOTE Here "-untrusted" flag means a list of further certificates in the chain to build up to the root is provided, but that the system CA root should be consulted. Verification is NOT bypassed or allowed to be invalid.

If these verifications pass you can now use these certificates with Kanidm. To put the certificates in place you can use a shell container that mounts the volume such as:

docker run --rm -i -t -v kanidmd:/data -v /my/host/path/work:/work opensuse/leap:latest /bin/sh -c "cp /work/* /data/"

OR for a shell into the volume:

docker run --rm -i -t -v kanidmd:/data opensuse/leap:latest /bin/sh

Configuring the Server

Configuring server.toml

You need a configuration file in the volume named server.toml. (Within the container it should be /data/server.toml) Its contents should be as follows:

#   The webserver bind address. Will use HTTPS if tls_*
#   is provided. If set to 443 you may require the
#   NET_BIND_SERVICE capability.
#   Defaults to ""
bindaddress = "[::]:8443"
#   The read-only ldap server bind address. The server
#   will use LDAPS if tls_* is provided. If set to 636
#   you may require the NET_BIND_SERVICE capability.
#   Defaults to "" (disabled)
# ldapbindaddress = "[::]:3636"
#   HTTPS requests can be reverse proxied by a loadbalancer.
#   To preserve the original IP of the caller, these systems
#   will often add a header such as "Forwarded" or
#   "X-Forwarded-For". If set to true, then this header is
#   respected as the "authoritive" source of the IP of the
#   connected client. If you are not using a load balancer
#   then you should leave this value as default.
#   Defaults to false
# trust_x_forward_for = false
#   The path to the kanidm database.
db_path = "/data/kanidm.db"
#   If you have a known filesystem, kanidm can tune sqlite
#   to match. Valid choices are:
#   [zfs, other]
#   If you are unsure about this leave it as the default
#   (other). After changing this
#   value you must run a vacuum task.
#   - zfs:
#     * sets sqlite pagesize to 64k. You must set
#       recordsize=64k on the zfs filesystem.
#   - other:
#     * sets sqlite pagesize to 4k, matching most
#       filesystems block sizes.
# db_fs_type = "zfs"
#   The number of entries to store in the in-memory cache.
#   Minimum value is 256. If unset
#   an automatic heuristic is used to scale this.
# db_arc_size = 2048
#   TLS chain and key in pem format. Both must be present
tls_chain = "/data/chain.pem"
tls_key = "/data/key.pem"
#   The log level of the server. May be default, verbose,
#   perfbasic, perffull
#   Defaults to "default"
# log_level = "default"
#   The DNS domain name of the server. This is used in a
#   number of security-critical contexts
#   such as webauthn, so it *must* match your DNS
#   hostname. It is used to create
#   security principal names such as ``
#   so that in a (future)
#   trust configuration it is possible to have unique Service
#   Principal Names (spns) throughout the topology.
#   ⚠️  WARNING ⚠️
#   Changing this value WILL break many types of registered
#   credentials for accounts
#   including but not limited to webauthn, oauth tokens, and more.
#   If you change this value you *must* run
#   `kanidmd domain_name_change` immediately after.
domain = ""
#   The origin for webauthn. This is the url to the server,
#   with the port included if
#   it is non-standard (any port except 443). This must match
#   or be a descendent of the
#   domain name you configure above. If these two items are
#   not consistent, the server WILL refuse to start!
#   origin = ""
origin = ""
#   The role of this server. This affects available features
#   and how replication may interact.
#   Valid roles are:
#   - WriteReplica
#     This server provides all functionality of Kanidm. It
#     allows authentication, writes, and
#     the web user interface to be served.
#   - WriteReplicaNoUI
#     This server is the same as a WriteReplica, but does NOT
#     offer the web user interface.
#   - ReadOnlyReplica
#     This server will not writes initiated by clients. It
#     supports authentication and reads,
#     and must have a replication agreement as a source of
#     its data.
#   Defaults to "WriteReplica".
# role = "WriteReplica"
# [online_backup]
#   The path to the output folder for online backups
# path = "/var/lib/kanidm/backups/"
#   The schedule to run online backups (see
#   every day at 22:00 UTC (default)
# schedule = "00 22 * * *"
#    four times a day at 3 minutes past the hour, every 6th hours
# schedule = "03 */6 * * *"
#   Number of backups to keep (default 7)
# versions = 7

This example is located in examples/server_container.toml.

Kani Warning Warning!
You MUST set the `domain` name correctly, aligned with your `origin`, else the server may refuse to start or some features (e.g. webauthn, oauth) may not work correctly!

Check the configuration is valid.

You should test your configuration is valid before you proceed.

docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd configtest -c /data/server.toml

Default Admin Account

Then you can setup the initial admin account and initialise the database into your volume. This command will generate a new random password for the admin account.

docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd recover_account -c /data/server.toml admin
# success - recover_account password for user admin: vv...

Run the Server

Now we can run the server so that it can accept connections. This defaults to using -c /data/server.toml

docker run -p 443:8443 -v kanidmd:/data kanidm/server:latest

Using the NET_BIND_SERVICE capability

If you plan to run without using docker port mapping or some other reverse proxy, and your bindaddress or ldapbindaddress port is less than 1024 you will need the NET_BIND_SERVICE in docker to allow these port binds. You can add this with --cap-add in your docker run command.

docker run --cap-add NET_BIND_SERVICE --network [host OR macvlan OR ipvlan] \
    -v kanidmd:/data kanidm/server:latest

Updating the Server

Preserving the Previous Image

You may wish to preserve the previous image before updating. This is useful if an issue is encountered in upgrades.

docker tag kanidm/server:latest kanidm/server:<DATE>
docker tag kanidm/server:latest kanidm/server:2022-10-24

Update your Image

Pull the latest version of Kanidm that matches your CPU profile

docker pull kanidm/server:latest
docker pull kanidm/server:x86_64_latest

Perform a backup

See backup and restore

Update your Instance

Kani Warning WARNING
It is not always guaranteed that downgrades are possible. It is critical you know how to backup and restore before you proceed with this step.

Docker updates by deleting and recreating the instance. All that needs to be preserved in your storage volume.

docker stop <previous instance name>

You can test that your configuration is correct, and the server should correctly start.

docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd configtest -c /data/server.toml

You can then follow through with the upgrade

docker run -p PORTS -v kanidmd:/data \

Once you confirm the upgrade is successful you can delete the previous instance

docker rm <previous instance name>

If you encounter an issue you can revert to the previous version.

docker stop <new instance name>
docker start <previous instance name>

If you deleted the previous instance, you can recreate it from your preserved tag instead.

docker run -p ports -v volumes kanidm/server:<DATE>

In some cases the downgrade to the previous instance may not work. If the server from your previous version fails to start, you may need to restore from backup.

Security Hardening

Kanidm ships with a secure-by-default configuration, however that is only as strong as the environment that Kanidm operates in. This could be your container environment or your Unix-like system.

This chapter will detail a number of warnings and security practices you should follow to ensure that Kanidm operates in a secure environment.

The main server is a high-value target for a potential attack, as Kanidm serves as the authority on identity and authorisation in a network. Compromise of the Kanidm server is equivalent to a full-network take over, also known as "game over".

The unixd resolver is also a high value target as it can be accessed to allow unauthorised access to a server, to intercept communications to the server, or more. This also must be protected carefully.

For this reason, Kanidm's components must be protected carefully. Kanidm avoids many classic attacks by being developed in a memory safe language, but risks still exist.

Startup Warnings

At startup Kanidm will warn you if the environment it is running in is suspicious or has risks. For example:

kanidmd server -c /tmp/server.toml
WARNING: permissions on /tmp/server.toml may not be secure. Should be readonly to running uid. This could be a security risk ...
WARNING: /tmp/server.toml has 'everyone' permission bits in the mode. This could be a security risk ...
WARNING: /tmp/server.toml owned by the current uid, which may allow file permission changes. This could be a security risk ...
WARNING: permissions on ../insecure/ca.pem may not be secure. Should be readonly to running uid. This could be a security risk ...
WARNING: permissions on ../insecure/cert.pem may not be secure. Should be readonly to running uid. This could be a security risk ...
WARNING: permissions on ../insecure/key.pem may not be secure. Should be readonly to running uid. This could be a security risk ...
WARNING: ../insecure/key.pem has 'everyone' permission bits in the mode. This could be a security risk ...
WARNING: DB folder /tmp has 'everyone' permission bits in the mode. This could be a security risk ...

Each warning highlights an issue that may exist in your environment. It is not possible for us to prescribe an exact configuration that may secure your system. This is why we only present possible risks.

Should be Read-only to Running UID

Files, such as configuration files, should be read-only to the UID of the Kanidm daemon. If an attacker is able to gain code execution, they are then unable to modify the configuration to write, or to over-write files in other locations, or to tamper with the systems configuration.

This can be prevented by changing the files ownership to another user, or removing "write" bits from the group.

'everyone' Permission Bits in the Mode

This means that given a permission mask, "everyone" or all users of the system can read, write or execute the content of this file. This may mean that if an account on the system is compromised the attacker can read Kanidm content and may be able to further attack the system as a result.

This can be prevented by removing "everyone: execute bits from parent directories containing the configuration, and removing "everyone" bits from the files in question.

Owned by the Current UID, Which May Allow File Permission Changes

File permissions in UNIX systems are a discretionary access control system, which means the named UID owner is able to further modify the access of a file regardless of the current settings. For example:

[william@amethyst 12:25] /tmp > touch test
[william@amethyst 12:25] /tmp > ls -al test
-rw-r--r--  1 william  wheel  0 29 Jul 12:25 test
[william@amethyst 12:25] /tmp > chmod 400 test
[william@amethyst 12:25] /tmp > ls -al test
-r--------  1 william  wheel  0 29 Jul 12:25 test
[william@amethyst 12:25] /tmp > chmod 644 test
[william@amethyst 12:26] /tmp > ls -al test
-rw-r--r--  1 william  wheel  0 29 Jul 12:25 test

Notice that even though the file was set to "read only" to william, and no permission to any other users, user "william" can change the bits to add write permissions back or permissions for other users.

This can be prevent by making the file owner a different UID than the running process for kanidm.

A Secure Example

Between these three issues it can be hard to see a possible strategy to secure files, however one way exists - group read permissions. The most effective method to secure resources for Kanidm is to set configurations to:

[william@amethyst 12:26] /etc/kanidm > ls -al server.toml
-r--r-----   1 root           kanidm      212 28 Jul 16:53 server.toml

The Kanidm server should be run as "kanidm:kanidm" with the appropriate user and user private group created on your system. This applies to unixd configuration as well.

For the database your data folder should be:

[root@amethyst 12:38] /data/kanidm > ls -al .
total 1064
drwxrwx---   3 root     kanidm      96 29 Jul 12:38 .
-rw-r-----   1 kanidm   kanidm  544768 29 Jul 12:38 kanidm.db

This means 770 root:kanidm. This allows Kanidm to create new files in the folder, but prevents Kanidm from being able to change the permissions of the folder. Because the folder does not have "everyone" mode bits, the content of the database is secure because users can now cd/read from the directory.

Configurations for clients, such as /etc/kanidm/config, should be secured with read-only permissions and owned by root:

[william@amethyst 12:26] /etc/kanidm > ls -al config
-r--r--r--    1 root  root    38 10 Jul 10:10 config

This file should be "everyone"-readable, which is why the bits are defined as such.

NOTE: Why do you use 440 or 444 modes?

A bug exists in the implementation of readonly() in rust that checks this as "does a write bit exist for any user" vs "can the current UID write the file?". This distinction is subtle but it affects the check. We don't believe this is a significant issue though, because setting these to 440 and 444 helps to prevent accidental changes by an administrator anyway

Running as Non-root in docker

The commands provided in this book will run kanidmd as "root" in the container to make the onboarding smoother. However, this is not recommended in production for security reasons.

You should allocate unique UID and GID numbers for the service to run as on your host system. In this example we use 1000:1000

You will need to adjust the permissions on the /data volume to ensure that the process can manage the files. Kanidm requires the ability to write to the /data directory to create the sqlite files. This UID/GID number should match the above. You could consider the following changes to help isolate these changes:

docker run --rm -i -t -v kanidmd:/data opensuse/leap:latest /bin/sh
mkdir /data/db/
chown 1000:1000 /data/db/
chmod 750 /data/db/
sed -i -e "s/db_path.*/db_path = \"\/data\/db\/kanidm.db\"/g" /data/server.toml
chown root:root /data/server.toml
chmod 644 /data/server.toml

Note that the example commands all run inside the docker container.

You can then use this to run the Kanidm server in docker with a user:

docker run --rm -i -t -u 1000:1000 -v kanidmd:/data kanidm/server:latest /sbin/kanidmd ...

HINT You need to use the UID or GID number with the -u argument, as the container can't resolve usernames from the host system.

Client tools

To interact with Kanidm as an administrator, you'll need to use our command line tools. If you haven't installed them yet, install them now.

Kanidm configuration

You can configure kanidm to help make commands simpler by modifying ~/.config/kanidm or /etc/kanidm/config.

uri = ""
verify_ca = true|false
verify_hostnames = true|false
ca_path = "/path/to/ca.pem"

Once configured, you can test this with:

kanidm self whoami --name anonymous

Session Management

To authenticate as a user (for use with the command line), you need to use the login command to establish a session token.

kanidm login --name USERNAME
kanidm login --name admin

Once complete, you can use kanidm without re-authenticating for a period of time for administration.

You can list active sessions with:

kanidm session list

Sessions will expire after a period of time (by default 1 hour). To remove these expired sessions locally you can use:

kanidm session cleanup

To log out of a session:

kanidm logout --name USERNAME
kanidm logout --name admin

Installing Client Tools

NOTE As this project is in a rapid development phase, running different release versions will likely present incompatibilities. Ensure you're running matching release versions of client and server binaries. If you have any issues, check that you are running the latest software.

From packages

Kanidm currently is packaged for the following systems:

  • OpenSUSE Tumbleweed
  • OpenSUSE Leap 15.3/15.4
  • MacOS
  • Arch Linux
  • NixOS
  • Fedora 36
  • CentOS Stream 9

The kanidm client has been built and tested from Windows, but is not (yet) packaged routinely.

OpenSUSE Tumbleweed

Kanidm has been part of OpenSUSE Tumbleweed since October 2020. You can install the clients with:

zypper ref
zypper in kanidm-clients

OpenSUSE Leap 15.3/15.4

Using zypper you can add the Kanidm leap repository with:

zypper ar -f obs://network:idm network_idm

Then you need to refresh your metadata and install the clients.

zypper ref
zypper in kanidm-clients

MacOS - Brew

Homebrew allows addition of third party repositories for installing tools. On MacOS you can use this to install the Kanidm tools.

brew tap kanidm/kanidm
brew install kanidm

Arch Linux

Kanidm on AUR


Kanidm in NixOS

Fedora / Centos Stream

Kani Warning Take Note!
Kanidm frequently uses new Rust versions and features, however Fedora and Centos frequently are behind in Rust releases. As a result, they may not always have the latest Kanidm versions available.

Fedora has limited support through the development repository. You need to add the repository metadata into the correct directory:

# Fedora
# Centos Stream 9

You can then install with:

dnf install kanidm-clients


The tools are available as a cargo download if you have a rust tool chain available. To install rust you should follow the documentation for rustup. These will be installed into your home directory. To update these, re-run the install command with the new version.

cargo install --version 1.1.0-alpha.10 kanidm_tools

Tools Container

In some cases if your distribution does not have native kanidm-client support, and you can't access cargo for the install for some reason, you can use the cli tools from a docker container instead.

docker pull kanidm/tools:latest
docker run --rm -i -t \
    -v /etc/kanidm/config:/etc/kanidm/config:ro \
    -v ~/.config/kanidm:/home/kanidm/.config/kanidm:ro \
    -v ~/.cache/kanidm_tokens:/home/kanidm/.cache/kanidm_tokens \
    kanidm/tools:latest \
    /sbin/kanidm --help

If you have a ca.pem you may need to bind mount this in as required.

TIP You can alias the docker run command to make the tools easier to access such as:

alias kanidm="docker run ..."

Checking that the tools work

Now you can check your instance is working. You may need to provide a CA certificate for verification with the -C parameter:

kanidm login --name anonymous
kanidm self whoami -H https://localhost:8443 --name anonymous
kanidm self whoami -C ../path/to/ca.pem -H https://localhost:8443 --name anonymous

Now you can take some time to look at what commands are available - please ask for help at any time.

Administration Tasks

This chapter describes some of the routine administration tasks for running a Kanidm server, such as making backups and restoring from backups, testing server configuration, reindexing, verifying data consistency, and renaming your domain.

Accounts and groups

Accounts and Groups are the primary reasons for Kanidm to exist. Kanidm is optimised as a repository for these data. As a result, there are many concepts and important details to understand.

Service Accounts vs Person Accounts

Kanidm seperates accounts into two types. Person accounts (or persons) are intended for use by humans that will access the system in an interactive way. Service accounts are intended for use by computers or services that need to identify themself to Kanidm. Generally a person or group of persons will be responsible for and will manage service accounts. Because of this distinction these classes of accounts have different properties and methods of authentication and management.


Groups represent a collection of entities. This generally is a collection of persons or service accounts. Groups are commonly used to assign privileges to the accounts that are members of a group. This allows easier administration over larger systems where privileges can be assigned to groups in a logical manner, and then only membership of the groups need administration, rather than needing to assign privileges to each entity directly and uniquely.

Groups may also be nested, where a group can contain another group as a member. This allows hierarchies to be created again for easier administration.

Default Accounts and Groups

Kanidm ships with a number of default service accounts and groups. This is to give you the best out-of-box experience possible, as well as supplying best practice examples related to modern Identity Management (IDM) systems.

There are two builtin system administration accounts.

admin is the default service account which has privileges to configure and administer kanidm as a whole. This account can manage access controls, schema, integrations and more. However the admin can not manage persons by default to seperate the priviliges. As this is a service account is is intended for limited use.

idm_admin is the default service account which has privileges to create persons and to manage these accounts and groups. They can perform credential resets and more.

Both the admin and the idm_admin user should NOT be used for daily activities - they exist for initial system configuration, and for disaster recovery scenarios. You should delegate permissions as required to named user accounts instead.

The majority of the builtin groups are privilige groups that provide rights over Kanidm administrative actions. These include groups for account management, person management (personal and sensitive data), group management, and more.

Recovering the Initial Admin Accounts

By default the admin and idm_admin accounts have no password, and can not be accessed. They need to be "recovered" from the server that is running the kanidmd server.

Kani Warning Warning!
The server must not be running at this point, as it requires exclusive access to the database.
kanidmd recover_account admin -c /etc/kanidm/server.toml
# Successfully recovered account 'admin' - password reset to -> j9YUv...

To do this with Docker, you'll need to stop the existing container and use the "command" argument to access the kanidmd binary.

docker run --rm -it \
    --name kanidmd \
    --hostname kanidmd \
    kanidm/server:latest \
    kanidmd recover_account admin -c /data/server.toml

After the recovery is complete the server can be started again.

Once you have access to the admin account, it is able to reset the credentials of the idm_admin account.

kanidm login -D admin
kanidm service-account credential generate-pw -D admin idm_admin
# Success: wJX...

These accounts will be used through the remainder of this document for managing the server.

Viewing Default Groups

You should take some time to inspect the default groups which are related to default permissions. These can be viewed with:

kanidm group list
kanidm group get <name>

Creating Person Accounts

By default idm_admin has the privileges to create new persons in the system.

kanidm login --name idm_admin
kanidm account create demo_user "Demonstration User" --name idm_admin
kanidm account get demo_user --name idm_admin

kanidm group create demo_group --name idm_admin
kanidm group add_members demo_group demo_user --name idm_admin
kanidm group list_members demo_group --name idm_admin

You can also use anonymous to view accounts and groups - note that you won't see certain fields due to the limits of the access control anonymous access profile.

kanidm login --name anonymous
kanidm account get demo_user --name anonymous

Kanidm allows person accounts to include human related attributes, such as their legal name and email address.

Initially, a person does not have these attributes. If desired, a person may be modified to have these attributes.

# Note, both the --legalname and --mail flags may be omitted
kanidm account person update demo_user --legalname "initial name" --mail "initial@email.address"
Kani Warning Warning!
Persons may change their own displayname, name, and legal name at any time. You MUST NOT use these values as primary keys in external systems. You MUST use the `uuid` attribute present on all entries as an external primary key.

Resetting Person Account Credentials

Members of the idm_account_manage_priv group have the rights to manage person and service accounts security and login aspects. This includes resetting account credentials.

You can perform a password reset on the demo_user, for example as the idm_admin user, who is a default member of this group. The lines below prefixed with # are the interactive credential update interface.

kanidm account credential update demo_user --name idm_admin
# spn:
# Name: Demonstration User
# Primary Credential:
# uuid: 0e19cd08-f943-489e-8ff2-69f9eacb1f31
# generated password: set
# Can Commit: true
# cred update (? for help) # : pass
# New password: 
# New password: [hidden]
# Confirm password: 
# Confirm password: [hidden]
# success
# cred update (? for help) # : commit
# Do you want to commit your changes? yes
# success
kanidm login --name demo_user
kanidm self whoami --name demo_user

Creating Service Accounts

The admin service account can be used to create service accounts.

kanidm service-account create demo_service "Demonstration Service" --name admin
kanidm service-account get demo_service --name admin

Using API Tokens with Service Accounts

Service accounts can have api tokens generated and associated with them. These tokens can be used for identification of the service account, and for granting extended access rights where the service account may previously have not had the access. Additionally service accounts can have expiry times and other auditing information attached.

To show api tokens for a service account:

kanidm service-account api-token status --name admin ACCOUNT_ID
kanidm service-account api-token status --name admin demo_service

By default api tokens are issued to be "read only", so they are unable to make changes on behalf of the service account they represent. To generate a new read only api token:

kanidm service-account api-token generate --name admin ACCOUNT_ID LABEL [EXPIRY]
kanidm service-account api-token generate --name admin demo_service "Test Token"
kanidm service-account api-token generate --name admin demo_service "Test Token" 2020-09-25T11:22:02+10:00

If you wish to issue a token that is able to make changes on behalf of the service account, you must add the "--rw" flag during the generate command. It is recommended you only add --rw when the api-token is performing writes to Kanidm.

kanidm service-account api-token generate --name admin ACCOUNT_ID LABEL [EXPIRY] --rw
kanidm service-account api-token generate --name admin demo_service "Test Token" --rw
kanidm service-account api-token generate --name admin demo_service "Test Token" 2020-09-25T11:22:02+10:00 --rw

To destroy (revoke) an api token you will need it's token id. This can be shown with the "status" command.

kanidm service-account api-token destroy --name admin ACCOUNT_ID TOKEN_ID
kanidm service-account api-token destroy --name admin demo_service 4de2a4e9-e06a-4c5e-8a1b-33f4e7dd5dc7

Api tokens can also be used to gain extended search permissions with LDAP. To do this you can bind with a dn of "" (empty string) and provide the api token in the password.

ldapwhoami -H ldaps://URL -x -D "" -w "TOKEN"
ldapwhoami -H ldaps:// -x -D "" -w "..."
# u:

Resetting Service Account Credentials (Deprecated)

Kani Warning
Api Tokens are a better method to manage credentials for service accounts, and passwords may be removed in the future!

Service accounts can not have their credentials interactively updated in the same manner as persons. Service accounts may only have server side generated high entropy passwords.

To re-generate this password to an account

kanidm service-account credential generate-pw demo_service --name admin

Nested Groups

Kanidm supports groups being members of groups, allowing nested groups. These nesting relationships are shown through the "memberof" attribute on groups and accounts.

Kanidm makes all group membership determinations by inspecting an entry's "memberof" attribute.

An example can be easily shown with:

kanidm group create group_1 --name idm_admin
kanidm group create group_2 --name idm_admin
kanidm account create nest_example "Nesting Account Example" --name idm_admin
kanidm group add_members group_1 group_2 --name idm_admin
kanidm group add_members group_2 nest_example --name idm_admin
kanidm account get nest_example --name anonymous

Account Validity

Kanidm supports accounts that are only able to authenticate between a pair of dates and times; the "valid from" and "expires" timestamps define these points in time.

This can be displayed with:

kanidm account validity show demo_user --name idm_admin
valid after: 2020-09-25T21:22:04+10:00
expire: 2020-09-25T01:22:04+10:00

These datetimes are stored in the server as UTC, but presented according to your local system time to aid correct understanding of when the events will occur.

To set the values, an account with account management permission is required (for example, idm_admin).

You may set these time and date values in any timezone you wish (such as your local timezone), and the server will transform these to UTC. These time values are in iso8601 format, and you should specify this as:

Year-Month-Day T hour:minutes:seconds Z +- timezone offset

Set the earliest time the account can start authenticating:

kanidm account validity begin_from demo_user '2020-09-25T11:22:04+00:00' --name idm_admin

Set the expiry or end date of the account:

kanidm account validity expire_at demo_user '2020-09-25T11:22:04+00:00' --name idm_admin

To unset or remove these values the following can be used, where any|clear means you may use either any or clear.

kanidm account validity begin_from demo_user any|clear --name idm_admin
kanidm account validity expire_at demo_user never|clear --name idm_admin

To "lock" an account, you can set the expire_at value to the past, or unix epoch. Even in the situation where the "valid from" is after the expire_at, the expire_at will be respected.

kanidm account validity expire_at demo_user 1970-01-01T00:00:00+00:00 --name idm_admin

These validity settings impact all authentication functions of the account (kanidm, ldap, radius).

Allowing people accounts to change their mail attribute

By default, Kanidm allows an account to change some attributes, but not their mail address.

Adding the user to the idm_people_self_write_mail group, as shown below, allows the user to edit their own mail.

kanidm group add_members idm_people_self_write_mail_priv demo_user --name idm_admin

Why Can't I Change admin With idm_admin?

As a security mechanism there is a distinction between "accounts" and "high permission accounts". This is to help prevent elevation attacks, where say a member of a service desk could attempt to reset the password of idm_admin or admin, or even a member of HR or System Admin teams to move laterally.

Generally, membership of a "privilege" group that ships with Kanidm, such as:

  • idm_account_manage_priv
  • idm_people_read_priv
  • idm_schema_manage_priv
  • many more ...

...indirectly grants you membership to "idm_high_privilege". If you are a member of this group, the standard "account" and "people" rights groups are NOT able to alter, read or manage these accounts. To manage these accounts higher rights are required, such as those held by the admin account are required.

Further, groups that are considered "idm_high_privilege" can NOT be managed by the standard "idm_group_manage_priv" group.

Management of high privilege accounts and groups is granted through the the "hp" variants of all privileges. A non-conclusive list:

  • idm_hp_account_read_priv
  • idm_hp_account_manage_priv
  • idm_hp_account_write_priv
  • idm_hp_group_manage_priv
  • idm_hp_group_write_priv

Membership of any of these groups should be considered to be equivalent to system administration rights in the directory, and by extension, over all network resources that trust Kanidm.

All groups that are flagged as "idm_high_privilege" should be audited and monitored to ensure that they are not altered.

Backup and Restore

With any Identity Management (IDM) software, it's important you have the capability to restore in case of a disaster - be that physical damage or a mistake. Kanidm supports backup and restore of the database with three methods.

Method 1 - Automatic Backup

Automatic backups can be generated online by a kanidmd server instance by including the [online_backup] section in the server.toml. This allows you to run regular backups, defined by a cron schedule, and maintain the number of backup versions to keep. An example is located in examples/server.toml.

Method 2 - Manual Backup

This method uses the same process as the automatic process, but is manually invoked. This can be useful for pre-upgrade backups

To take the backup (assuming our docker environment) you first need to stop the instance:

docker stop <container name>
docker run --rm -i -t -v kanidmd:/data -v kanidmd_backups:/backup \
    kanidm/server:latest /sbin/kanidmd database backup -c /data/server.toml \
docker start <container name>

You can then restart your instance. DO NOT modify the backup.json as it may introduce data errors into your instance.

To restore from the backup:

docker stop <container name>
docker run --rm -i -t -v kanidmd:/data -v kanidmd_backups:/backup \
    kanidm/server:latest /sbin/kanidmd database restore -c /data/server.toml \
docker start <container name>

Method 3 - Manual Database Copy

This is a simple backup of the data volume.

docker stop <container name>
# Backup your docker's volume folder
docker start <container name>

Restoration is the reverse process.

Database Maintenance


In some (rare) cases you may need to reindex. Please note the server will sometimes reindex on startup as a result of the project changing its internal schema definitions. This is normal and expected - you may never need to start a reindex yourself as a result!

You'll likely notice a need to reindex if you add indexes to schema and you see a message in your logs such as:

Index EQUALITY name not found
Index {type} {attribute} not found

This indicates that an index of type equality has been added for name, but the indexing process has not been run. The server will continue to operate and the query execution code will correctly process the query - however it will not be the optimal method of delivering the results as we need to disregard this part of the query and act as though it's un-indexed.

Reindexing will resolve this by forcing all indexes to be recreated based on their schema definitions (this works even though the schema is in the same database!)

docker stop <container name>
docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd reindex -c /data/server.toml
docker start <container name>

Generally, reindexing is a rare action and should not normally be required.


Vacuuming is the process of reclaiming un-used pages from the sqlite freelists, as well as performing some data reordering tasks that may make some queries more efficient . It is recommended that you vacuum after a reindex is performed or when you wish to reclaim space in the database file.

Vacuum is also able to change the pagesize of the database. After changing db_fs_type (which affects pagesize) in server.toml, you must run a vacuum for this to take effect:

docker stop <container name>
docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd vacuum -c /data/server.toml
docker start <container name>


The server ships with a number of verification utilities to ensure that data is consistent such as referential integrity or memberof.

Note that verification really is a last resort - the server does a lot to prevent and self-heal from errors at run time, so you should rarely if ever require this utility. This utility was developed to guarantee consistency during development!

You can run a verification with:

docker stop <container name>
docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd verify -c /data/server.toml
docker start <container name>

If you have errors, please contact the project to help support you to resolve these.

Rename the domain

There are some cases where you may need to rename the domain. You should have configured this initially in the setup, however you may have a situation where a business is changing name, merging, or other needs which may prompt this needing to be changed.

WARNING: This WILL break ALL u2f/webauthn tokens that have been enrolled, which MAY cause accounts to be locked out and unrecoverable until further action is taken. DO NOT CHANGE the domain name unless REQUIRED and have a plan on how to manage these issues.

WARNING: This operation can take an extensive amount of time as ALL accounts and groups in the domain MUST have their Security Principal Names (SPNs) regenerated. This WILL also cause a large delay in replication once the system is restarted.

You should make a backup before proceeding with this operation.

When you have a created a migration plan and strategy on handling the invalidation of webauthn, you can then rename the domain.

First, stop the instance.

docker stop <container name>

Second, change domain and origin in server.toml.

Third, trigger the database domain rename process.

docker run --rm -i -t -v kanidmd:/data \
    kanidm/server:latest /sbin/kanidmd domain rename -c /data/server.toml

Finally, you can now start your instance again.

docker start <container name>

Monitoring the platform

The monitoring design of Kanidm is still very much in its infancy - take part in the dicussion at


kanidmd currently responds to HTTP GET requests at the /status endpoint with a JSON object of either "true" or "false". true indicates that the platform is responding to requests.

Example URL
Expected responseOne of either true or false (without quotes)
Additional Headersx-kanidm-opid
Content Typeapplication/json

Password Quality and Badlisting

Kanidm embeds a set of tools to help your users use and create strong passwords. This is important as not all user types will require multi-factor authentication (MFA) for their roles, but compromised accounts still pose a risk. There may also be deployment or other barriers to a site rolling out sitewide MFA.

Quality Checking

Kanidm enforces that all passwords are checked by the library "zxcvbn". This has a large number of checks for password quality. It also provides constructive feedback to users on how to improve their passwords if they are rejected.

Some things that zxcvbn looks for is use of the account name or email in the password, common passwords, low entropy passwords, dates, reverse words and more.

This library can not be disabled - all passwords in Kanidm must pass this check.

Password Badlisting

This is the process of configuring a list of passwords to exclude from being able to be used. This is especially useful if a specific business has been notified of compromised accounts, allowing you to maintain a list of customised excluded passwords.

The other value to this feature is being able to badlist common passwords that zxcvbn does not detect, or from other large scale password compromises.

By default we ship with a preconfigured badlist that is updated over time as new password breach lists are made available.

The password badlist by default is append only, meaning it can only grow, but will never remove passwords previously considered breached.

Updating your own Badlist

You can display the current badlist with:

kanidm system pw-badlist show

You can update your own badlist with:

kanidm system pw-badlist upload "path/to/badlist" [...]

Multiple bad lists can be listed and uploaded at once. These are preprocessed to identify and remove passwords that zxcvbn and our password rules would already have eliminated. That helps to make the bad list more efficent to operate over at run time.

Password Rotation

Kanidm will never support this "anti-feature". Password rotation encourages poor password hygiene and is not shown to prevent any attacks.

POSIX Accounts and Groups

Kanidm has features that enable its accounts and groups to be consumed on POSIX-like machines, such as Linux, FreeBSD, or others. Both service accounts and person accounts can be used on POSIX systems.

Notes on POSIX Features

Many design decisions have been made in the POSIX features of Kanidm that are intended to make distributed systems easier to manage and client systems more secure.

UID and GID Numbers

In Kanidm there is no difference between a UID and a GID number. On most UNIX systems a user will create all files with a primary user and group. The primary group is effectively equivalent to the permissions of the user. It is very easy to see scenarios where someone may change the account to have a shared primary group (ie allusers), but without changing the umask on all client systems. This can cause users' data to be compromised by any member of the same shared group.

To prevent this, many systems create a "user private group", or UPG. This group has the GID number matching the UID of the user, and the user sets their primary group ID to the GID number of the UPG.

As there is now an equivalence between the UID and GID number of the user and the UPG, there is no benefit in separating these values. As a result Kanidm accounts only have a GID number, which is also considered to be its UID number as well. This has the benefit of preventing the accidental creation of a separate group that has an overlapping GID number (the uniqueness attribute of the schema will block the creation).

UPG Generation

Due to the requirement that a user have a UPG for security, many systems create these as two independent items. For example in /etc/passwd and /etc/group:

# passwd
# group

Other systems like FreeIPA use a plugin that generates a UPG as a seperate group entry on creation of the account. This means there are two entries for an account, and they must be kept in lock-step.

Kanidm does neither of these. As the GID number of the user must be unique, and a user implies the UPG must exist, we can generate UPG's on-demand from the account. This has a single side effect - that you are unable to add any members to a UPG - given the nature of a user private group, this is the point.

GID Number Generation

Kanidm will have asynchronous replication as a feature between writable database servers. In this case, we need to be able to allocate stable and reliable GID numbers to accounts on replicas that may not be in continual communication.

To do this, we use the last 32 bits of the account or group's UUID to generate the GID number.

A valid concern is the possibility of duplication in the lower 32 bits. Given the birthday problem, if you have 77,000 groups and accounts, you have a 50% chance of duplication. With 50,000 you have a 20% chance, 9,300 you have a 1% chance and with 2900 you have a 0.1% chance.

We advise that if you have a site with >10,000 users you should use an external system to allocate GID numbers serially or consistently to avoid potential duplication events.

This design decision is made as most small sites will benefit greatly from the auto-allocation policy and the simplicity of its design, while larger enterprises will already have IDM or business process applications for HR/People that are capable of supplying this kind of data in batch jobs.

Enabling POSIX Attributes

Enabling POSIX Attributes on Accounts

To enable POSIX account features and IDs on an account, you require the permission idm_account_unix_extend_priv. This is provided to idm_admins in the default database.

You can then use the following command to enable POSIX extensions on a person or service account.

kanidm [person OR service-account] posix set --name idm_admin <account_id> [--shell SHELL --gidnumber GID]

kanidm person posix set --name idm_admin demo_user
kanidm person posix set --name idm_admin demo_user --shell /bin/zsh
kanidm person posix set --name idm_admin demo_user --gidnumber 2001

kanidm service-account posix set --name idm_admin demo_account
kanidm service-account posix set --name idm_admin demo_account --shell /bin/zsh
kanidm service-account posix set --name idm_admin demo_account --gidnumber 2001

You can view the accounts POSIX token details with:

kanidm person posix show --name anonymous demo_user
kanidm service-account posix show --name anonymous demo_account

Enabling POSIX Attributes on Groups

To enable POSIX group features and IDs on an account, you require the permission idm_group_unix_extend_priv. This is provided to idm_admins in the default database.

You can then use the following command to enable POSIX extensions:

kanidm group posix set --name idm_admin <group_id> [--gidnumber GID]
kanidm group posix set --name idm_admin demo_group
kanidm group posix set --name idm_admin demo_group --gidnumber 2001

You can view the accounts POSIX token details with:

kanidm group posix show --name anonymous demo_group

POSIX-enabled groups will supply their members as POSIX members to clients. There is no special or separate type of membership for POSIX members required.

Troubleshooting Common Issues

subuid conflicts with Podman

Due to the way that Podman operates, in some cases using the Kanidm client inside non-root containers with Kanidm accounts may fail with an error such as:

ERRO[0000] cannot find UID/GID for user NAME: No subuid ranges found for user "NAME" in /etc/subuid

This is a fault in Podman and how it attempts to provide non-root containers, when UID/GIDs are greater than 65535. In this case you may manually allocate your users GID number to be between 1000 - 65535, which may not trigger the fault.

SSH Key Distribution

To support SSH authentication securely to a large set of hosts running SSH, we support distribution of SSH public keys via the Kanidm server. Both persons and service accounts support SSH public keys on their accounts.

Configuring Accounts

To view the current SSH public keys on accounts, you can use:

kanidm person|service-account ssh list_publickeys --name <login user> <account to view>
kanidm person|service-account ssh list_publickeys --name idm_admin william

All users by default can self-manage their SSH public keys. To upload a key, a command like this is the best way to do so:

kanidm person|service-account ssh add_publickey --name william william 'test-key' "`cat ~/.ssh/`"

To remove (revoke) an SSH public key, delete them by the tag name:

kanidm person|service-account ssh delete_publickey --name william william 'test-key'

Security Notes

As a security feature, Kanidm validates all public keys to ensure they are valid SSH public keys. Uploading a private key or other data will be rejected. For example:

kanidm person|service-account ssh add_publickey --name william william 'test-key' "invalid"
Enter password:
  ... Some(SchemaViolation(InvalidAttributeSyntax)))' ...

Server Configuration

Public Key Caching Configuration

If you have kanidm_unixd running, you can use it to locally cache SSH public keys. This means you can still SSH into your machines, even if your network is down, you move away from Kanidm, or some other interruption occurs.

The kanidm_ssh_authorizedkeys command is part of the kanidm-unix-clients package, so should be installed on the servers. It communicates to kanidm_unixd, so you should have a configured PAM/nsswitch setup as well.

You can test this is configured correctly by running:

kanidm_ssh_authorizedkeys <account name>

If the account has SSH public keys you should see them listed, one per line.

To configure servers to accept these keys, you must change their /etc/ssh/sshd_config to contain the lines:

PubkeyAuthentication yes
UsePAM yes
AuthorizedKeysCommand /usr/bin/kanidm_ssh_authorizedkeys %u
AuthorizedKeysCommandUser nobody

Restart sshd, and then attempt to authenticate with the keys.

It's highly recommended you keep your client configuration and sshd_configuration in a configuration management tool such as salt or ansible.

NOTICE: With a working SSH key setup, you should also consider adding the following sshd_config options as hardening.

PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
GSSAPIAuthentication no
KerberosAuthentication no

Direct Communication Configuration

In this mode, the authorised keys commands will contact Kanidm directly.

NOTICE: As Kanidm is contacted directly there is no SSH public key cache. Any network outage or communication loss may prevent you accessing your systems. You should only use this version if you have a requirement for it.

The kanidm_ssh_authorizedkeys_direct command is part of the kanidm-clients package, so should be installed on the servers.

To configure the tool, you should edit /etc/kanidm/config, as documented in clients

You can test this is configured correctly by running:

kanidm_ssh_authorizedkeys_direct -D anonymous <account name>

If the account has SSH public keys you should see them listed, one per line.

To configure servers to accept these keys, you must change their /etc/ssh/sshd_config to contain the lines:

PubkeyAuthentication yes
UsePAM yes
AuthorizedKeysCommand /usr/bin/kanidm_ssh_authorizedkeys_direct -D anonymous %u
AuthorizedKeysCommandUser nobody

Restart sshd, and then attempt to authenticate with the keys.

It's highly recommended you keep your client configuration and sshd_configuration in a configuration management tool such as salt or ansible.

Recycle Bin

The recycle bin is a storage of deleted entries from the server. This allows recovery from mistakes for a period of time.

Kani Warning Warning!
The recycle bin is a best effort - when recovering in some cases not everything can be "put back" the way it was. Be sure to check your entries are valid once they have been revived.

Where is the Recycle Bin?

The recycle bin is stored as part of your main database - it is included in all backups and restores, just like any other data. It is also replicated between all servers.

How do Things Get Into the Recycle Bin?

Any delete operation of an entry will cause it to be sent to the recycle bin. No configuration or specification is required.

How Long Do Items Stay in the Recycle Bin?

Currently they stay up to 1 week before they are removed.

Managing the Recycle Bin

You can display all items in the Recycle Bin with:

kanidm recycle-bin list --name admin

You can show a single item with:

kanidm recycle-bin get --name admin <uuid>

An entry can be revived with:

kanidm recycle-bin revive --name admin <uuid>

Edge Cases

The recycle bin is a best effort to restore your data - there are some cases where the revived entries may not be the same as their were when they were deleted. This generally revolves around reference types such as group membership, or when the reference type includes supplemental map data such as the oauth2 scope map type.

An example of this data loss is the following steps:

add user1
add group1
add user1 as member of group1
delete user1
delete group1
revive user1
revive group1

In this series of steps, due to the way that referential integrity is implemented, the membership of user1 in group1 would be lost in this process. To explain why:

add user1
add group1
add user1 as member of group1 // refint between the two established, and memberof added
delete user1 // group1 removes member user1 from refint
delete group1 // user1 now removes memberof group1 from refint
revive user1 // re-add groups based on directmemberof (empty set)
revive group1 // no members

These issues could be looked at again in the future, but for now we think that deletes of groups is rare - we expect recycle bin to save you in "opps" moments, and in a majority of cases you may delete a group or a user and then restore them. To handle this series of steps requires extra code complexity in how we flag operations. For more, see This issue on github.

Why TLS?

You may have noticed that Kanidm requires you to configure TLS in your container.

We are a secure-by-design rather than secure-by-installation system, so TLS for all connections is considered mandatory.

What are Secure Cookies?

secure-cookies is a flag set in cookies that asks a client to transmit them back to the origin site if and only if HTTPS is present in the URL.

Certificate authority (CA) verification is not checked - you can use invalid, out of date certificates, or even certificates where the subjectAltName does not match, but the client must see https:// as the destination else it will not send the cookies.

How Does That Affect Kanidm?

Kanidm's authentication system is a stepped challenge response design, where you initially request an "intent" to authenticate. Once you establish this intent, the server sets up a session-id into a cookie, and informs the client of what authentication methods can proceed.

If you do NOT have a HTTPS URL, the cookie with the session-id is not transmitted. The server detects this as an invalid-state request in the authentication design, and immediately breaks the connection, because it appears insecure.

Simply put, we are trying to use settings like secure_cookies to add constraints to the server so that you must perform and adhere to best practices - such as having TLS present on your communication channels.


Some things to try.

Is the server started?

If you don't see "ready to rock! 🪨" in your logs, it's not started. Scroll back and look for errors!dd

Can you connect?

If the server's running on then a simple connectivity test is done using curl.

Run the following command:

curl -k

This is similar to what you should see:

➜ curl -vk
*   Trying
* Connected to ( port 8443 (#0)
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/cert.pem
*  CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384
* Server certificate:
*  subject: C=AU; ST=Queensland; L=Brisbane; O=INSECURE EXAMPLE; OU=kanidm;
*  start date: Sep 20 09:28:18 2022 GMT
*  expire date: Oct 21 09:28:18 2022 GMT
*  SSL certificate verify result: self signed certificate in certificate chain (19), continuing anyway.
> GET /status HTTP/1.1
> Host:
> User-Agent: curl/7.79.1
> Accept: */*
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< cache-control: no-store, max-age=0
< content-length: 4
< content-type: application/json
< date: Tue, 20 Sep 2022 11:52:23 GMT
< pragma: no-cache
< set-cookie: kanidm-session=+LQJKwL0UdAEMoTc0Zrgne2hU+N2nB+Lcf+J1OoI9n4%3DNE7xuL9yCq7B0Ai+IM3gq5T+YZ0ckDuDoWZKzhPMHmSk3oFSscp9vy9n2a5bBFjWKgeNwdLzRbYc4rvMqYi11A%3D%3D; HttpOnly; SameSite=Strict; Secure; Path=/; Expires=Wed, 21 Sep 2022 11:52:23 GMT
< x-content-type-options: nosniff
< x-kanidm-opid: 8b25f050-7f6e-4ce1-befe-90be3c4f8a98
* Connection #0 to host localhost left intact

This means:

  1. you've successfully connected to a host (,
  2. TLS worked
  3. Received the status response "true"

If you see something like this:

➜ curl -v
*   Trying
* connect to port 8443 failed: Connection refused
* Failed to connect to port 8443 after 5 ms: Connection refused
* Closing connection 0
curl: (7) Failed to connect to port 8443 after 5 ms: Connection refused

Then either your DNS is wrong (it's pointing at or you can't connect to the server for some reason.

If you get errors about certificates, try adding -k to skip certificate verification checking and just test connectivity:

curl -vk

Server things to check

  • Has the config file got bindaddress = "" ? Change it to bindaddress = "[::]:8443", so it listens on all interfaces.
  • Is there a firewall on the server?
  • If you're running in docker, did you expose the port? (-p 8443:8443)

Client things to check

Try running commands with RUST_LOG=debug to get more information:

RUST_LOG=debug kanidm login --name anonymous


This is a glossary of terms used through out this book. While we make every effort to explains terms and acronyms when they are used, this may be a useful reference if something feels unknown to you.

Domain Names

  • domain - This is the domain you "own". It is the highest level entity. An example would be (since you do not own .com).
  • subdomain - A subdomain is a domain name space under the domain. A subdomains of are and Each subdomain can have further subdomains.
  • domain name - This is any named entity within your domain or its subdomains. This is the umbrella term, referring to all entities in the domain.,, are all valid domain names with the domain
  • origin - An origin defines a URL with a protocol scheme, optional port number and domain name components. An example is
  • effective domain - This is the extracted domain name from an origin excluding port and scheme.


  • trust - A trust is when two Kanidm domains have a relationship to each other where accounts can be used between the domains. The domains retain their administration boundaries, but allow cross authentication.
  • replication - This is the process where two or more Kanidm servers in a domain can synchronise their database content.
  • UAT - User Authentication Token. This is a token issue by Kanidm to an account after it has authenticated.
  • SPN - Security Principal Name. This is a name of an account comprising it's name and domain name. This allows distinction between accounts with identical names over a trust boundary


  • entity, object, entry - Any item in the database. Generally these terms are interchangeable, but internally they are referred to as Entry.
  • account - An entry that may authenticate to the server, generally allowing extended permissions and actions to be undertaken.

Access Control

  • privilege - An expression of what actions an account may perform if granted
  • target - The entries that will be affected by a privilege
  • receiver - The entries that will be able to use a privilege
  • acp - an Access Control Profile which defines a set of privileges that are granted to receivers to affect target entries.
  • role - A term used to express a group that is the receiver of an access control profile allowing it's members to affect the target entries.


OAuth is a web authorisation protocol that allows "single sign on". It's key to note OAuth only provides authorisation, as the protocol in its default forms do not provide identity or authentication information. All that Oauth2 provides is information that an entity is authorised for the requested resources.

OAuth can tie into extensions allowing an identity provider to reveal information about authorised sessions. This extends OAuth from an authorisation only system to a system capable of identity and authorisation. Two primary methods of this exist today: RFC7662 token introspection, and OpenID connect.

How Does OAuth2 Work?

A user wishes to access a service (resource, resource server). The resource server does not have an active session for the client, so it redirects to the authorisation server (Kanidm) to determine if the client should be allowed to proceed, and has the appropriate permissions (scopes) for the requested resources.

The authorisation server checks the current session of the user and may present a login flow if required. Given the identity of the user known to the authorisation sever, and the requested scopes, the authorisation server makes a decision if it allows the authorisation to proceed. The user is then prompted to consent to the authorisation from the authorisation server to the resource server as some identity information may be revealed by granting this consent.

If successful and consent given, the user is redirected back to the resource server with an authorisation code. The resource server then contacts the authorisation server directly with this code and exchanges it for a valid token that may be provided to the user's browser.

The resource server may then optionally contact the token introspection endpoint of the authorisation server about the provided OAuth token, which yields extra metadata about the identity that holds the token from the authorisation. This metadata may include identity information, but also may include extended metadata, sometimes refered to as "claims". Claims are information bound to a token based on properties of the session that may allow the resource server to make extended authorisation decisions without the need to contact the authorisation server to arbitrate.

It's important to note that OAuth2 at its core is an authorisation system which has layered identity-providing elements on top.

Resource Server

This is the server that a user wants to access. Common examples could be Nextcloud, a wiki, or something else. This is the system that "needs protecting" and wants to delegate authorisation decisions to Kanidm.

It's important for you to know how your resource server supports OAuth2. For example, does it support RFC 7662 token introspection or does it rely on OpenID connect for identity information? Does the resource server support PKCE S256?

In general Kanidm requires that your resource server supports:

  • HTTP basic authentication to the authorisation server
  • PKCE S256 code verification to prevent certain token attack classes
  • OIDC only - JWT ES256 for token signatures

Kanidm will expose its OAuth2 APIs at the following URLs:

  • user auth url:
  • api auth url:
  • token url:
  • rfc7662 token introspection url:
  • rfc7009 token revoke url:

OpenID Connect discovery - you need to substitute your OAuth2 client id in the following urls:

  • OpenID connect issuer uri:\_id:/
  • OpenID connect discovery:\_id:/.well-known/openid-configuration

For manual OpenID configuration:

  • OpenID connect userinfo:\_id:/userinfo
  • token signing public key:\_id:/public\_key.jwk

Scope Relationships

For an authorisation to proceed, the resource server will request a list of scopes, which are unique to that resource server. For example, when a user wishes to login to the admin panel of the resource server, it may request the "admin" scope from Kanidm for authorisation. But when a user wants to login, it may only request "access" as a scope from Kanidm.

As each resource server may have its own scopes and understanding of these, Kanidm isolates scopes to each resource server connected to Kanidm. Kanidm has two methods of granting scopes to accounts (users).

The first is scope mappings. These provide a set of scopes if a user is a member of a specific group within Kanidm. This allows you to create a relationship between the scopes of a resource server, and the groups/roles in Kanidm which can be specific to that resource server.

For an authorisation to proceed, all scopes requested by the resource server must be available in the final scope set that is granted to the account.

The second is supplemental scope mappings. These function the same as scope maps where membership of a group provides a set of scopes to the account. However these scopes are NOT consulted during authorisation decisions made by Kanidm. These scopes exists to allow optional properties to be provided (such as personal information about a subset of accounts to be revealed) or so that the resource server may make it's own authorisation decisions based on the provided scopes.

This use of scopes is the primary means to control who can access what resources. These access decisions can take place either on Kanidm or the resource server.

For example, if you have a resource server that always requests a scope of "read", then users with scope maps that supply the read scope will be allowed by Kanidm to proceed to the resource server. Kanidm can then provide the supplementary scopes into provided tokens, so that the resource server can use these to choose if it wishes to display UI elements. If a user has a supplemental "admin" scope, then that user may be able to access an administration panel of the resource server. In this way Kanidm is still providing the authorisation information, but the control is then exercised by the resource server.


Create the Kanidm Configuration

After you have understood your resource server requirements you first need to configure Kanidm. By default members of "system_admins" or "idm_hp_oauth2_manage_priv" are able to create or manage OAuth2 resource server integrations.

You can create a new resource server with:

kanidm system oauth2 create <name> <displayname> <origin>
kanidm system oauth2 create nextcloud "Nextcloud Production"

You can create a scope map with:

kanidm system oauth2 update_scope_map <name> <kanidm_group_name> [scopes]...
kanidm system oauth2 update_scope_map nextcloud nextcloud_admins admin
Kani Warning WARNING
If you are creating an OpenID Connect (OIDC) resource server you MUST provide a scope map named openid. Without this, OpenID clients WILL NOT WORK

HINT OpenID connect allows a number of scopes that affect the content of the resulting authorisation token. If one of the following scopes are requested by the OpenID client, then the associated claims may be added to the authorisation token. It is not guaranteed that all of the associated claims will be added.

  • profile - (name, family_name, given_name, middle_name, nickname, preferred_username, profile, picture, website, gender, birthdate, zoneinfo, locale, and updated_at)
  • email - (email, email_verified)
  • address - (address)
  • phone - (phone_number, phone_number_verified)

You can create a supplemental scope map with:

kanidm system oauth2 update_sup_scope_map <name> <kanidm_group_name> [scopes]...
kanidm system oauth2 update_sup_scope_map nextcloud nextcloud_admins admin

Once created you can view the details of the resource server.

kanidm system oauth2 get nextcloud
class: oauth2_resource_server
class: oauth2_resource_server_basic
class: object
displayname: Nextcloud Production
oauth2_rs_basic_secret: <secret>
oauth2_rs_name: nextcloud
oauth2_rs_token_key: hidden

Configure the Resource Server

On your resource server, you should configure the client ID as the "oauth2_rs_name" from Kanidm, and the password to be the value shown in "oauth2_rs_basic_secret". Ensure that the code challenge/verification method is set to S256.

You should now be able to test authorisation.

Resetting Resource Server Security Material

In the case of disclosure of the basic secret, or some other security event where you may wish to invalidate a resource servers active sessions/tokens, you can reset the secret material of the server with:

kanidm system oauth2 reset_secrets

Each resource server has unique signing keys and access secrets, so this is limited to each resource server.

Extended Options for Legacy Clients

Not all resource servers support modern standards like PKCE or ECDSA. In these situations it may be necessary to disable these on a per-resource server basis. Disabling these on one resource server will not affect others.

Kani Warning WARNING
Changing these settings MAY have serious consequences on the security of your resource server. You should avoid changing these if at all possible!

To disable PKCE for a resource server:

kanidm system oauth2 warning_insecure_client_disable_pkce <resource server name>

To enable legacy cryptograhy (RSA PKCS1-5 SHA256):

kanidm system oauth2 warning_enable_legacy_crypto <resource server name>

Example Integrations

Apache mod_auth_openidc

Add the following to a mod_auth_openidc.conf. It should be included in a mods_enabled folder or with an appropriate include.

OIDCRedirectURI /protected/redirect_uri
OIDCCryptoPassphrase <random password here>
OIDCProviderMetadataURL<resource server name>/.well-known/openid-configuration
OIDCScope "openid" 
OIDCUserInfoTokenMethod authz_header
OIDCClientID <resource server name>
OIDCClientSecret <resource server password>
OIDCCookieSameSite On
# Set the `REMOTE_USER` field to the `preferred_username` instead of the UUID.
# Remember that the username can change, but this can help with systems like Nagios which use this as a display name.
# OIDCRemoteUserClaim preferred_username

Other scopes can be added as required to the OIDCScope line, eg: OIDCScope "openid scope2 scope3"

In the virtual host, to protect a location:

<Location />
    AuthType openid-connect
    Require valid-user


Install the module from the nextcloud market place - it can also be found in the Apps section of your deployment as "OpenID Connect user backend".

In Nextcloud's config.php you need to allow connection to remote servers:

'allow_local_remote_servers' => true,

You may optionally choose to add:

'allow_user_to_change_display_name' => false,
'lost_password_link' => 'disabled',

If you forget this, you may see the following error in logs:

Host was not connected to because it violates local access rules

This module does not support PKCE or ES256. You will need to run:

kanidm system oauth2 warning_insecure_client_disable_pkce <resource server name>
kanidm system oauth2 warning_enable_legacy_crypto <resource server name>

In the settings menu, configure the discovery URL and client ID and secret.

You can choose to disable other login methods with:

php occ config:app:set --value=0 user_oidc allow_multiple_user_backends

You can login directly by appending ?direct=1 to your login page. You can re-enable other backends by setting the value to 1


Velociraptor supports OIDC. To configure it select "Authenticate with SSO" then "OIDC" during the interactive configuration generator. Alternately, you can set the following keys in server.config.yaml:

    type: OIDC
    oauth_client_id: <resource server name/>
    oauth_client_secret: <resource server secret>

Velociraptor does not support PKCE. You will need to run the following:

kanidm system oauth2 warning_insecure_client_disable_pkce <resource server name>

Initial users are mapped via their email in the Velociraptor server.config.yaml config:

  - name: <email address>

Accounts require the openid and email scopes to be authenticated. It is recommended you limit these to a group with a scope map due to Velociraptors high impact.

# kanidm group create velociraptor_users
# kanidm group add_members velociraptor_users ...
kanidm system oauth2 create_scope_map <resource server name> velociraptor_users openid email

Vouch Proxy

WARNING Vouch proxy requires a unique identifier but does not use the proper scope, "sub". It uses the fields "username" or "email" as primary identifiers instead. As a result, this can cause user or deployment issues, at worst security bypasses. You should avoid Vouch Proxy if possible due to these issues.

Note: You need to run at least the version 0.37.0

Vouch Proxy supports multiple OAuth and OIDC login providers. To configure it you need to pass:

  client_id: <oauth2_rs_name> # Found in kanidm system oauth2 get XXXX (should be the same as XXXX)
  client_secret: <oauth2_rs_basic_secret> # Found in kanidm system oauth2 get XXXX
  code_challenge_method: S256
  provider: oidc
    - email # Required due to vouch proxy reliance on mail as a primary identifier

The email scope needs to be passed and thus the mail attribute needs to exist on the account:

kanidm person update <ID> --mail "" --name idm_admin

PAM and nsswitch

PAM and nsswitch are the core mechanisms used by Linux and BSD clients to resolve identities from an IDM service like Kanidm into accounts that can be used on the machine for various interactive tasks.

The UNIX Daemon

Kanidm provides a UNIX daemon that runs on any client that wants to use PAM and nsswitch integration. The daemon can cache the accounts for users who have unreliable networks, or who leave the site where Kanidm is hosted. The daemon is also able to cache missing-entry responses to reduce network traffic and main server load.

Additionally, running the daemon means that the PAM and nsswitch integration libraries can be small, helping to reduce the attack surface of the machine. Similarly, a tasks daemon is available that can create home directories on first login and supports several features related to aliases and links to these home directories.

We recommend you install the client daemon from your system package manager:

# OpenSUSE
zypper in kanidm-unixd-clients
# Fedora
dnf install kanidm-unixd-clients

You can check the daemon is running on your Linux system with:

systemctl status kanidm-unixd

You can check the privileged tasks daemon is running with:

systemctl status kanidm-unixd-tasks

NOTE The kanidm_unixd_tasks daemon is not required for PAM and nsswitch functionality. If disabled, your system will function as usual. It is, however, recommended due to the features it provides supporting Kanidm's capabilities.

Both unixd daemons use the connection configuration from /etc/kanidm/config. This is the covered in client_tools.

You can also configure some unixd-specific options with the file /etc/kanidm/unixd:

pam_allowed_login_groups = ["posix_group"]
default_shell = "/bin/sh"
home_prefix = "/home/"
home_attr = "uuid"
home_alias = "spn"
use_etc_skel = false
uid_attr_map = "spn"
gid_attr_map = "spn"

pam_allowed_login_groups defines a set of POSIX groups where membership of any of these groups will be allowed to login via PAM. All POSIX users and groups can be resolved by nss regardless of PAM login status. This may be a group name, spn, or uuid.

default_shell is the default shell for users. Defaults to /bin/sh.

home_prefix is the prepended path to where home directories are stored. Must end with a trailing /. Defaults to /home/.

home_attr is the default token attribute used for the home directory path. Valid choices are uuid, name, spn. Defaults to uuid.

home_alias is the default token attribute used for generating symlinks pointing to the user's home directory. If set, this will become the value of the home path to nss calls. It is recommended you choose a "human friendly" attribute here. Valid choices are none, uuid, name, spn. Defaults to spn.

NOTICE: All users in Kanidm can change their name (and their spn) at any time. If you change home_attr from uuid you must have a plan on how to manage these directory renames in your system. We recommend that you have a stable ID (like the UUID), and symlinks from the name to the UUID folder. Automatic support is provided for this via the unixd tasks daemon, as documented here.

use_etc_skel controls if home directories should be prepopulated with the contents of /etc/skel when first created. Defaults to false.

uid_attr_map chooses which attribute is used for domain local users in presentation. Defaults to spn. Users from a trust will always use spn.

gid_attr_map chooses which attribute is used for domain local groups in presentation. Defaults to spn. Groups from a trust will always use spn.

You can then check the communication status of the daemon:


If the daemon is working, you should see:

[2020-02-14T05:58:37Z INFO  kanidm_unixd_status] working!

If it is not working, you will see an error message:

[2020-02-14T05:58:10Z ERROR kanidm_unixd_status] Error -> 
   Os { code: 111, kind: ConnectionRefused, message: "Connection refused" }

For more information, see the Troubleshooting section.


When the daemon is running you can add the nsswitch libraries to /etc/nsswitch.conf

passwd: compat kanidm
group: compat kanidm

You can create a user then enable POSIX feature on the user.

You can then test that the POSIX extended user is able to be resolved with:

getent passwd <account name>
getent passwd testunix

You can also do the same for groups.

getent group <group name>
getent group testgroup

HINT Remember to also create a UNIX password with something like kanidm account posix set_password --name idm_admin demo_user. Otherwise there will be no credential for the account to authenticate.


WARNING: Modifications to PAM configuration may leave your system in a state where you are unable to login or authenticate. You should always have a recovery shell open while making changes (for example, root), or have access to single-user mode at the machine's console.

Pluggable Authentication Modules (PAM) is the mechanism a UNIX-like system that authenticates users, and to control access to some resources. This is configured through a stack of modules that are executed in order to evaluate the request, and then each module may request or reuse authentication token information.

Before You Start

You should backup your /etc/pam.d directory from its original state as you may change the PAM configuration in a way that will not allow you to authenticate to your machine.

cp -a /etc/pam.d /root/pam.d.backup


To configure PAM on suse you must modify four files, which control the various stages of authentication:


IMPORTANT By default these files are symlinks to their corresponding -pc file, for example common-account -> common-account-pc. If you directly edit these you are updating the inner content of the -pc file and it WILL be reset on a future upgrade. To prevent this you must first copy the -pc files. You can then edit the files safely.

cp /etc/pam.d/common-account-pc  /etc/pam.d/common-account
cp /etc/pam.d/common-auth-pc     /etc/pam.d/common-auth
cp /etc/pam.d/common-password-pc /etc/pam.d/common-password
cp /etc/pam.d/common-session-pc  /etc/pam.d/common-session

The content should look like:

# /etc/pam.d/common-auth-pc
# Controls authentication to this system (verification of credentials)
auth        required
auth        [default=1 ignore=ignore success=ok]
auth        sufficient nullok try_first_pass
auth        requisite uid >= 1000 quiet_success
auth        sufficient ignore_unknown_user
auth        required

# /etc/pam.d/common-account-pc
# Controls authorisation to this system (who may login)
account    [default=1 ignore=ignore success=ok]
account    sufficient
account    [default=1 ignore=ignore success=ok] uid >= 1000 quiet_success quiet_fail
account    sufficient ignore_unknown_user
account    required

# /etc/pam.d/common-password-pc
# Controls flow of what happens when a user invokes the passwd command. Currently does NOT
# interact with kanidm.
password    [default=1 ignore=ignore success=ok]
password    required use_authtok nullok shadow try_first_pass
password    [default=1 ignore=ignore success=ok] uid >= 1000 quiet_success quiet_fail
password    required

# /etc/pam.d/common-session-pc
# Controls setup of the user session once a successful authentication and authorisation has
# occured.
session optional
session required
session optional try_first_pass
session optional
session [default=1 ignore=ignore success=ok] uid >= 1000 quiet_success quiet_fail
session optional
session optional

WARNING: Ensure that pam_mkhomedir or pam_oddjobd are not present in any stage of your PAM configuration, as they interfere with the correct operation of the Kanidm tasks daemon.

Fedora / CentOS

WARNING: Kanidm currently has no support for SELinux policy - this may mean you need to run the daemon with permissive mode for the unconfined_service_t daemon type. To do this run: semanage permissive -a unconfined_service_t. To undo this run semanage permissive -d unconfined_service_t.

You may also need to run audit2allow for sshd and other types to be able to access the UNIX daemon sockets.

These files are managed by authselect as symlinks. You can either work with authselect, or remove the symlinks first.

Without authselect

If you just remove the symlinks:

Edit the content.

# /etc/pam.d/password-auth
auth        required                           
auth        required                            delay=2000000
auth        [default=1 ignore=ignore success=ok] isregular
auth        [default=1 ignore=ignore success=ok]
auth        sufficient                          nullok try_first_pass
auth        [default=1 ignore=ignore success=ok] isregular
auth        sufficient                          ignore_unknown_user
auth        required                           

account     sufficient                         
account     sufficient                         
account     sufficient                          issystem
account     sufficient                          ignore_unknown_user
account     required                           

password    requisite                           try_first_pass local_users_only
password    sufficient                          sha512 shadow nullok try_first_pass use_authtok
password    sufficient                         
password    required                           

session     optional                            revoke
session     required                           
-session    optional                           
session     [success=1 default=ignore]          service in crond quiet use_uid
session     required                           
session     optional                           
# /etc/pam.d/system-auth
auth        required                           
auth        required                            delay=2000000
auth        sufficient                         
auth        [default=1 ignore=ignore success=ok] isregular
auth        [default=1 ignore=ignore success=ok]
auth        sufficient                          nullok try_first_pass
auth        [default=1 ignore=ignore success=ok] isregular
auth        sufficient                          ignore_unknown_user
auth        required                           

account     sufficient                         
account     sufficient                         
account     sufficient                          issystem
account     sufficient                          ignore_unknown_user
account     required                           

password    requisite                           try_first_pass local_users_only
password    sufficient                          sha512 shadow nullok try_first_pass use_authtok
password    sufficient                         
password    required                           

session     optional                            revoke
session     required                           
-session    optional                           
session     [success=1 default=ignore]          service in crond quiet use_uid
session     required                           
session     optional                           

With authselect

To work with authselect:

You will need to create a new profile.

First run the following command:

authselect create-profile kanidm -b sssd

A new folder, /etc/authselect/custom/kanidm, should be created. Inside that folder, create or overwrite the following three files: nsswitch.conf, password-auth, system-auth. password-auth and system-auth should be the same as above. nsswitch should be modified for your use case. A working example looks like this:

passwd: compat kanidm sss files systemd
group: compat kanidm sss files systemd
shadow:     files
hosts:      files dns myhostname
services:   sss files
netgroup:   sss files
automount:  sss files

aliases:    files
ethers:     files
gshadow:    files
networks:   files dns
protocols:  files
publickey:  files
rpc:        files

Then run:

authselect select custom/kanidm

to update your profile.


Check POSIX-status of Group and Configuration

If authentication is failing via PAM, make sure that a list of groups is configured in /etc/kanidm/unixd:

pam_allowed_login_groups = ["example_group"]

Check the status of the group with kanidm group posix show example_group. If you get something similar to the following example:

> kanidm group posix show example_group
Using cached token for name idm_admin
Error -> Http(500, Some(InvalidAccountState("Missing class: account && posixaccount OR group && posixgroup")), 

POSIX-enable the group with kanidm group posix set example_group. You should get a result similar to this when you search for your group name:

> kanidm group posix show example_group
[ spn:, gidnumber: 3443347205 name: example_group, uuid: b71f137e-39f3-4368-9e58-21d26671ae24 ]

Also, ensure the target user is in the group by running:

>  kanidm group list_members example_group

Increase Logging

For the unixd daemon, you can increase the logging with:

systemctl edit kanidm-unixd.service

And add the lines:


Then restart the kanidm-unixd.service.

The same pattern is true for the kanidm-unixd-tasks.service daemon.

To debug the pam module interactions add debug to the module arguments such as:

auth sufficient debug

Check the Socket Permissions

Check that the /var/run/kanidm-unixd/sock has permissions mode 777, and that non-root readers can see it with ls or other tools.

Ensure that /var/run/kanidm-unixd/task_sock has permissions mode 700, and that it is owned by the kanidm unixd process user.

Verify that You Can Access the Kanidm Server

You can check this with the client tools:

kanidm self whoami --name anonymous

Ensure the Libraries are Correct

You should have:


The exact path may change depending on your distribution, should be co-located with Look for it with the find command:

find /usr/ -name ''

For example, on a Debian machine, it's located in /usr/lib/x86_64-linux-gnu/security/.

Increase Connection Timeout

In some high-latency environments, you may need to increase the connection timeout. We set this low to improve response on LANs, but over the internet this may need to be increased. By increasing the conn_timeout, you will be able to operate on higher latency links, but some operations may take longer to complete causing a degree of latency.

By increasing the cache_timeout, you will need to refresh less often, but it may result in an account lockout or group change until cache_timeout takes effect. Note that this has security implications:

# /etc/kanidm/unixd
# Seconds
conn_timeout = 8
# Cache timeout
cache_timeout = 60

Invalidate or Clear the Cache

You can invalidate the kanidm_unixd cache with:


You can clear (wipe) the cache with:


There is an important distinction between these two - invalidated cache items may still be yielded to a client request if the communication to the main Kanidm server is not possible. For example, you may have your laptop in a park without wifi.

Clearing the cache, however, completely wipes all local data about all accounts and groups. If you are relying on this cached (but invalid) data, you may lose access to your accounts until other communication issues have been resolved.


Remote Authentication Dial In User Service (RADIUS) is a network protocol that is commonly used to authenticate Wi-Fi devices or Virtual Private Networks (VPNs). While it should not be a sole point of trust/authentication to an identity, it's still an important control for protecting network resources.

Kanidm has a philosophy that each account can have multiple credentials which are related to their devices, and limited to specific resources. RADIUS is no exception and has a separate credential for each account to use for RADIUS access.


It's worth noting some disclaimers about Kanidm's RADIUS integration.

One Credential - One Account

Kanidm normally attempts to have credentials for each device and application rather than the legacy model of one to one.

The RADIUS protocol is only able to attest a single password based credential in an authentication attempt, which limits us to storing a single RADIUS password credential per account. However, despite this limitation, it still greatly improves the situation by isolating the RADIUS credential from the primary or application credentials of the account. This solves many common security concerns around credential loss or disclosure, and prevents rogue devices from locking out accounts as they attempt to authenticate to Wi-Fi with expired credentials.

Alternatelly, Kanidm supports mapping users with special configuration of certificates allowing some systems to use EAP-TLS for RADIUS authentication. This returns to the "per device" credential model.

Cleartext Credential Storage

RADIUS offers many different types of tunnels and authentication mechanisms. However, most client devices "out of the box" only attempt a single type when a WPA2-Enterprise network is selected: MSCHAPv2 with PEAP. This is a challenge-response protocol that requires clear text or Windows NT LAN Manager (NTLM) credentials.

As MSCHAPv2 with PEAP is the only practical, universal RADIUS-type supported on all devices with minimal configuration, we consider it imperative that it MUST be supported as the default. Esoteric RADIUS types can be used as well, but this is up to administrators to test and configure.

Due to this requirement, we must store the RADIUS material as clear text or NTLM hashes. It would be silly to think that NTLM is secure as it relies on the obsolete and deprecated MD4 cryptographic hash, providing only an illusion of security.

This means Kanidm stores RADIUS credentials in the database as clear text.

We believe this is a reasonable decision and is a low risk to security because:

  • The access controls around RADIUS secrets by default are strong, limited to only self-account read and RADIUS-server read.
  • As RADIUS credentials are separate from the primary account credentials and have no other rights, their disclosure is not going to lead to a full account compromise.
  • Having the credentials in clear text allows a better user experience as clients can view the credentials at any time to enroll further devices.

Service Accounts Do Not Have Radius Access

Due to the design of service accounts, they do not have access to radius for credential assignemnt. If you require RADIUS usage with a service account you may need to use EAP-TLS or some other authentication method.

Account Credential Configuration

For an account to use RADIUS they must first generate a RADIUS secret unique to that account. By default, all accounts can self-create this secret.

kanidm person radius generate_secret --name william william
kanidm person radius show_secret --name william william

Account Group Configuration

In Kanidm, accounts which can authenticate to RADIUS must be a member of an allowed group. This allows you to define which users or groups may use a Wi-Fi or VPN infrastructure, and provides a path for revoking access to the resources through group management. The key point of this is that service accounts should not be part of this group:

kanidm group create --name idm_admin radius_access_allowed
kanidm group add_members --name idm_admin radius_access_allowed william

RADIUS Server Service Account

To read these secrets, the RADIUS server requires an account with the correct privileges. This can be created and assigned through the group "idm_radius_servers", which is provided by default.

First, create the service account and add it to the group:

kanidm service-account create --name admin radius_service_account "Radius Service Account"
kanidm group add_members --name admin idm_radius_servers radius_service_account

Now reset the account password, using the admin account:

kanidm service-account credential generate-pw --name admin radius_service_account

Deploying a RADIUS Container

We provide a RADIUS container that has all the needed integrations. This container requires some cryptographic material, with the following files being in /etc/raddb/certs. (Modifiable in the configuration)

ca.pemThe signing CA of the RADIUS certificate
dh.pemThe output of openssl dhparam -in ca.pem -out ./dh.pem 2048
cert.pemThe certificate for the RADIUS server
key.pemThe signing key for the RADIUS certificate

The configuration file (/data/kanidm) has the following template:

uri = "" # URL to the Kanidm server
verify_hostnames = true     # verify the hostname of the Kanidm server

verify_ca = false           # Strict CA verification
ca = /data/ca.pem           # Path to the kanidm ca

auth_token = "ABC..."       # Auth token for the service account
                            # See: kanidm service-account api-token generate

# Default vlans for groups that don't specify one.
radius_default_vlan = 1

# A list of Kanidm groups which must be a member
# before they can authenticate via RADIUS.
radius_required_groups = [

# A mapping between Kanidm groups and VLANS
radius_groups = [
    { spn = "", vlan = 10 },

# A mapping of clients and their authentication tokens
radius_clients = [
    { name = "test", ipaddr = "", secret  = "testing123" },
    { name = "docker" , ipaddr = "", secret = "testing123" },

# radius_cert_path = "/etc/raddb/certs/cert.pem"
# the signing key for radius TLS
# radius_key_path = "/etc/raddb/certs/key.pem"
# the diffie-hellman output
# radius_dh_path = "/etc/raddb/certs/dh.pem"
# the CA certificate
# radius_ca_path = "/etc/raddb/certs/ca.pem"

A fully configured example

url = ""

# The auth token for the service account
auth_token = "ABC..."

# default vlan for groups that don't specify one.
radius_default_vlan = 99

# if the user is in one of these Kanidm groups,
# then they're allowed to authenticate
radius_required_groups = [

radius_groups = [
    { spn = "", vlan = 10 }

radius_clients = [
    { name = "localhost", ipaddr = "", secret = "testing123" },
    { name = "docker" , ipaddr = "", secret = "testing123" },

Moving to Production

To expose this to a Wi-Fi infrastructure, add your NAS in the configuration:

radius_clients = [
    { name = "access_point", ipaddr = "", secret = "<a_random_value>" }

Then re-create/run your docker instance and expose the ports by adding -p 1812:1812 -p 1812:1812/udp to the command.

If you have any issues, check the logs from the RADIUS output, as they tend to indicate the cause of the problem. To increase the logging level you can re-run your environment with debug enabled:

docker rm radiusd
docker run --name radiusd \
    -e DEBUG=True \
    -p 1812:1812 \
    -p 1812:1812/udp
    --interactive --tty \
    --volume /tmp/kanidm:/etc/raddb/certs \

Note: the RADIUS container is configured to provide Tunnel-Private-Group-ID, so if you wish to use Wi-Fi-assigned VLANs on your infrastructure, you can assign these by groups in the configuration file as shown in the above examples.


While many applications can support systems like Security Assertion Markup Language (SAML), or Open Authorization (OAuth), many do not. Lightweight Directory Access Protocol (LDAP) has been the "lingua franca" of authentication for many years, with almost every application in the world being able to search and bind to LDAP. As many organization still rely on LDAP, Kanidm can host a read-only LDAP interface.

Kani Warning Warning!
The LDAP server in Kanidm is not RFC compliant. This is intentional, as Kanidm wants to cover the common use case, simple bind and search.

What is LDAP

LDAP is a protocol to read data from a directory of information. It is not a server, but a way to communicate to a server. There are many famous LDAP implementations such as Active Directory, 389 Directory Server, DSEE, FreeIPA, and many others. Because it is a standard, applications can use an LDAP client library to authenticate users to LDAP, given "one account" for many applications - an IDM just like Kanidm!

Data Mapping

Kanidm cannot be mapped 100% to LDAP's objects. This is because LDAP types are simple key-values on objects which are all UTF8 strings (or subsets thereof) based on validation (matching) rules. Kanidm internally implements complex data types such as tagging on SSH keys, or multi-value credentials. These can not be represented in LDAP.

Many of the structures in Kanidm do not correlate closely to LDAP. For example Kanidm only has a GID number, where LDAP's schemas define both a UID number and a GID number.

Entries in the database also have a specific name in LDAP, related to their path in the directory tree. Kanidm is a flat model, so we have to emulate some tree-like elements, and ignore others.

For this reason, when you search the LDAP interface, Kanidm will make some mapping decisions.

  • The domain_info object becomes the suffix root.
  • All other entries are direct subordinates of the domain_info for DN purposes.
  • Distinguished Names (DNs) are generated from the attributes naming attributes.
  • Bind DNs can be remapped and rewritten, and may not even be a DN during bind.
  • The Kanidm domain name is used to generate the base DN.
  • The '*' and '+' operators can not be used in conjuction with attribute lists in searches.

These decisions were made to make the path as simple and effective as possible, relying more on the Kanidm query and filter system than attempting to generate a tree-like representation of data. As almost all clients can use filters for entry selection we don't believe this is a limitation for the consuming applications.



StartTLS is not supported due to security risks. LDAPS is the only secure method of communicating to any LDAP server. Kanidm, when configured with certificates, will use them for LDAPS (and will not listen on a plaintext LDAP port).

Access Controls

LDAP only supports password authentication. As LDAP is used heavily in POSIX environments the LDAP bind for any DN will use its configured posix password.

As the POSIX password is not equivalent in strength to the primary credentials of Kanidm (which may be multi-factor authentication, MFA), the LDAP bind does not grant rights to elevated read permissions. All binds have the permissions of "Anonymous" even if the anonymous account is locked.

Server Configuration

To configure Kanidm to provide LDAP, add the argument to the server.toml configuration:

ldapbindaddress = ""

You should configure TLS certificates and keys as usual - LDAP will re-use the Web server TLS material.

Showing LDAP Entries and Attribute Maps

By default Kanidm is limited in what attributes are generated or remapped into LDAP entries. However, the server internally contains a map of extended attribute mappings for application specific requests that must be satisfied.

An example is that some applications expect and require a 'CN' value, even though Kanidm does not provide it. If the application is unable to be configured to accept "name" it may be necessary to use Kanidm's mapping feature. Currently these are compiled into the server, so you may need to open an issue with your requirements.

To show what attribute maps exists for an entry you can use the attribute search term '+'.

# To show Kanidm attributes
ldapsearch ... -x '(name=admin)' '*'
# To show all attribute maps
ldapsearch ... -x '(name=admin)' '+'

Attributes that are in the map can be requested explicitly, and this can be combined with requesting Kanidm native attributes.

ldapsearch ... -x '(name=admin)' cn objectClass displayname memberof


Given a default install with domain "" the configured LDAP DN will be "dc=example,dc=com".

Run your server:

cargo run -- server \
    -D kanidm.db \
    -C ca.pem -c cert.pem \
    -k key.pem \
    -b \

This can be queried with:

LDAPTLS_CACERT=ca.pem ldapsearch \
    -H ldaps:// \
    -b 'dc=example,dc=com' \
    -x '(name=test1)'

objectclass: account
objectclass: memberof
objectclass: object
objectclass: person
displayname: Test User
name: test1
entryuuid: 22a65b6c-80c8-4e1a-9b76-3f3afdff8400

It is recommended that client applications filter accounts that can login with (class=account) and groups with (class=group). If possible, group membership is defined in RFC2307bis or Active Directory style. This means groups are determined from the "memberof" attribute which contains a DN to a group.

LDAP binds can use any unique identifier of the account. The following are all valid bind DNs for the object listed above (if it was a POSIX account, that is).

ldapwhoami ... -x -D 'name=test1'
ldapwhoami ... -x -D ''
ldapwhoami ... -x -D ''
ldapwhoami ... -x -D 'test1'
ldapwhoami ... -x -D '22a65b6c-80c8-4e1a-9b76-3f3afdff8400'
ldapwhoami ... -x -D ',dc=example,dc=com'
ldapwhoami ... -x -D 'name=test1,dc=example,dc=com'

Most LDAP clients are very picky about TLS, and can be very hard to debug or display errors. For example these commands:

ldapsearch -H ldaps:// -b 'dc=example,dc=com' -x '(name=test1)'
ldapsearch -H ldap:// -b 'dc=example,dc=com' -x '(name=test1)'
ldapsearch -H ldap:// -b 'dc=example,dc=com' -x '(name=test1)'

All give the same error:

ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)

This is despite the fact:

  • The first command is a certificate validation error.
  • The second is a missing LDAPS on a TLS port.
  • The third is an incorrect port.

To diagnose errors like this, you may need to add "-d 1" to your LDAP commands or client.

Kubernetes Ingress

Guard your Kubernetes ingress with Kanidm authentication and authorization.


We recommend you have the following before continuing:


  1. Create a Kanidm account and group:

    1. Create a Kanidm account. Please see the section Creating Accounts.
    2. Give the account a password. Please see the section Resetting Account Credentials.
    3. Make the account a person. Please see the section People Accounts.
    4. Create a Kanidm group. Please see the section Creating Accounts.
    5. Add the account you created to the group you create. Please see the section Creating Accounts.
  2. Create a Kanidm OAuth2 resource:

    1. Create the OAuth2 resource for your domain. Please see the section Create the Kanidm Configuration.
    2. Add a scope mapping from the resource you created to the group you create with the openid, profile, and email scopes. Please see the section Create the Kanidm Configuration.
  3. Create a Cookie Secret to for the placeholder <COOKIE_SECRET> in step 4:

    docker run -ti --rm python:3-alpine python -c 'import secrets,base64; print(base64.b64encode(base64.b64encode(secrets.token_bytes(16))).decode("utf-8"));'
  4. Create a file called k8s.kanidm-nginx-auth-example.yaml with the block below. Replace every <string> (drop the <>) with appropriate values:

    1. <FQDN>: The fully qualified domain name with an A record pointing to your k8s ingress.
    2. <KANIDM_FQDN>: The fully qualified domain name of your Kanidm deployment.
    3. <COOKIE_SECRET>: The output from step 3.
    4. <OAUTH2_RS_NAME>: Please see the output from step 2.1 or get the OAuth2 resource you create from that step.
    5. <OAUTH2_RS_BASIC_SECRET>: Please see the output from step 2.1 or get the OAuth2 resource you create from that step.

    This will deploy the following to your cluster:

    apiVersion: v1
    kind: Namespace
      name: kanidm-example
      labels: restricted
    apiVersion: apps/v1
    kind: Deployment
      namespace: kanidm-example
      name: website
        app: website
      revisionHistoryLimit: 1
      replicas: 1
          app: website
            app: website
            - name: website
              image: modem7/docker-starwars
              imagePullPolicy: Always
                - containerPort: 8080
                allowPrivilegeEscalation: false
                  drop: ["ALL"]
            runAsNonRoot: true
              type: RuntimeDefault
    apiVersion: v1
    kind: Service
      namespace: kanidm-example
      name: website
        app: website
        - protocol: TCP
          port: 8080
          targetPort: 8080
    kind: Ingress
      annotations: lets-encrypt-cluster-issuer "https://$host/oauth2/auth" "https://$host/oauth2/start?rd=$escaped_request_uri"
      name: website
      namespace: kanidm-example
      ingressClassName: nginx
        - hosts:
            - <FQDN>
          secretName: <FQDN>-ingress-tls # replace . with - in the hostname
      - host: <FQDN>
          - path: /
            pathType: Prefix
                name: website
                  number: 8080
    apiVersion: apps/v1
    kind: Deployment
        k8s-app: oauth2-proxy
      name: oauth2-proxy
      namespace: kanidm-example
      replicas: 1
          k8s-app: oauth2-proxy
            k8s-app: oauth2-proxy
          - args:
            - --provider=oidc
            - --email-domain=*
            - --upstream=file:///dev/null
            - --http-address=
            - --oidc-issuer-url=https://<KANIDM_FQDN>/oauth2/openid/<OAUTH2_RS_NAME>
            - --code-challenge-method=S256
            - name: OAUTH2_PROXY_CLIENT_ID
              value: <OAUTH2_RS_NAME>
            - name: OAUTH2_PROXY_CLIENT_SECRET
              value: <OAUTH2_RS_BASIC_SECRET>
            - name: OAUTH2_PROXY_COOKIE_SECRET
              value: <COOKIE_SECRET>
            imagePullPolicy: Always
            name: oauth2-proxy
            - containerPort: 4182
              protocol: TCP
              allowPrivilegeEscalation: false
                drop: ["ALL"]
            runAsNonRoot: true
              type: RuntimeDefault
    apiVersion: v1
    kind: Service
        k8s-app: oauth2-proxy
      name: oauth2-proxy
      namespace: kanidm-example
      - name: http
        port: 4182
        protocol: TCP
        targetPort: 4182
        k8s-app: oauth2-proxy
    kind: Ingress
      name: oauth2-proxy
      namespace: kanidm-example
      ingressClassName: nginx
      - host: <FQDN>
          - path: /oauth2
            pathType: Prefix
                name: oauth2-proxy
                  number: 4182
      - hosts:
        - <FQDN>
        secretName: <FQDN>-ingress-tls # replace . with - in the hostname
  5. Apply the configuration by running the following command:

    kubectl apply -f k8s.kanidm-nginx-auth-example.yaml
  6. Check your deployment succeeded by running the following commands:

    kubectl -n kanidm-example get all
    kubectl -n kanidm-example get ingress
    kubectl -n kanidm-example get Certificate

    You may use kubectl's describe and log for troubleshooting. If there are ingress errors see the Ingress NGINX documentation's troubleshooting page. If there are certificate errors see the CertManger documentation's troubleshooting page.

    Once it has finished deploying, you will be able to access it at https://<FQDN> which will prompt you for authentication.

Cleaning Up

  1. Remove the resources create for this example from k8s:

    kubectl delete namespace kanidm-example
  2. Remove the objects created for this example from Kanidm:

    1. Delete the account created in section Instructions step 1.
    2. Delete the group created in section Instructions step 2.
    3. Delete the OAuth2 resource created in section Instructions step 3.


  1. NGINX Ingress Controller: External OAUTH Authentication
  2. OAuth2 Proxy: OpenID Connect Provider


Traefik is a flexible HTTP reverse proxy webserver that can be integrated with Docker to allow dynamic configuration and to automatically use LetsEncrypt to provide valid TLS certificates. We can leverage this in the setup of Kanidm by specifying the configuration of Kanidm and Traefik in the same Docker Compose configuration.

Example setup

Create a new directory and copy the following YAML file into it as docker-compose.yml. Edit the YAML to update the LetsEncrypt account email for your domain and the FQDN where Kanidm will be made available. Ensure you adjust this file or Kanidm's configuration to have a matching HTTPS port; the line sets this on the Traefik side.

NOTE You will need to generate self-signed certificates for Kanidm, and copy the configuration into the kanidm_data volume. Some instructions are available in the "Installing the Server" section of this book.


version: "3.4"
    image: traefik:v2.6
    container_name: traefik
      - "" 
      - ""
      - "--certificatesresolvers.http.acme.tlschallenge=true"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.websecure.http.tls=true"
      - "--entrypoints.websecure.http.tls.certResolver=http"
      - "--log.level=INFO"
      - "--providers.docker=true"
      - "--providers.docker.exposedByDefault=false"
      - "--serverstransport.insecureskipverify=true"
    restart: always
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - "443:443"
    container_name: kanidm
    image: kanidm/server:devel 
    restart: unless-stopped
      - kanidm_data:/data
      - traefik.enable=true
      - traefik.http.routers.kanidm.entrypoints=websecure
      - traefik.http.routers.kanidm.rule=Host(``)
      - traefik.http.routers.kanidm.service=kanidm
      - traefik.http.serversTransports.kanidm.insecureSkipVerify=true
  kanidm_data: {}

Finally you may run docker-compose up to start up both Kanidm and Traefik.

Getting Started (for Developers)


See the designs folder, and compile the private documentation locally:

cargo doc --document-private-items --open --no-deps

Rust Documentation

A list of links to the library documentation is at

Minimum Supported Rust Version

The MSRV is specified in the package Cargo.toml files.

Build Profiles

Setting different developer profiles while building is done by setting the environment variable KANIDM_BUILD_PROFILE to one of the bare filename of the TOML files in /profiles.

For example, this will set the CPU flags to "none" and the location for the Web UI files to /usr/share/kanidm/ui/pkg:

KANIDM_BUILD_PROFILE=release_suse_generic cargo build --release --bin kanidmd



You will need rustup to install a Rust toolchain.


You will need rustup to install a Rust toolchain. If you're using the Tumbleweed release, it's packaged in zypper.

You will also need some system libraries to build this:

libudev-devel sqlite3-devel libopenssl-devel


You need to install the Rust toolchain packages:

rust cargo

You will also need some system libraries to build this:

systemd-devel sqlite-devel openssl-devel pam-devel

Building the Web UI requires additional packages:

perl-FindBin perl-File-Compare rust-std-static-wasm32-unknown-unknown


You need rustup to install a Rust toolchain.

You will also need some system libraries to build this, which can be installed by running:

sudo apt-get install libsqlite3-dev libudev-dev libssl-dev pkg-config libpam0g-dev

Tested with Ubuntu 20.04 and 22.04.


You need rustup to install a Rust toolchain.

An easy way to grab the dependencies is to install vcpkg.

This is how it works in the automated build:

  1. Enable use of installed packages for the user system-wide:
vcpkg integrate install
  1. Install the openssl dependency, which compiles it from source. This downloads all sorts of dependencies, including perl for the build.
vcpkg install openssl:x64-windows-static-md

There's a powershell script in the root directory of the repository which, in concert with openssl will generate a config file and certs for testing.

Get Involved

To get started, you'll need to fork or branch, and we'll merge based on pull requests.

If you are a contributor to the project, simply clone:

git clone

If you are forking, then fork in GitHub and clone with:

git clone
cd kanidm
git remote add myfork<YOUR USERNAME>/kanidm.git

Select an issue (always feel free to reach out to us for advice!), and create a branch to start working:

git branch <feature-branch-name>
git checkout <feature-branch-name>
cargo test

When you are ready for review (even if the feature isn't complete and you just want some advice):

  1. Run the test suite: cargo test --workspace
  2. Ensure rust formatting standards are followed: cargo fmt --check
  3. Try following the suggestions from clippy, after running cargo clippy. This is not a blocker on us accepting your code!
  4. Then commit your changes:
git commit -m 'Commit message' ...
git push <myfork/origin> <feature-branch-name>

If you receive advice or make further changes, just keep commiting to the branch, and pushing to your branch. When we are happy with the code, we'll merge in GitHub, meaning you can now clean up your branch.

git checkout master
git pull
git branch -D <feature-branch-name>


If you are asked to rebase your change, follow these steps:

git checkout master
git pull
git checkout <feature-branch-name>
git rebase master

Then be sure to fix any merge issues or other comments as they arise. If you have issues, you can always stop and reset with:

git rebase --abort

Development Server Quickstart for Interactive Testing

After getting the code, you will need a rust environment. Please investigate rustup for your platform to establish this.

Once you have the source code, you need encryption certificates to use with the server, because without certificates, authentication will fail.

We recommend using Let's Encrypt, but if this is not possible, please use our insecure certificate tool (

NOTE: Windows developers can use insecure_generate_tls.ps1, which puts everything (including a templated confi gfile) in $TEMP\kanidm. Please adjust paths below to suit.

The insecure certificate tool creates /tmp/kanidm and puts some self-signed certificates there.

You can now build and run the server with the commands below. It will use a database in /tmp/kanidm.db.

Create the initial database and generate an admin username:

cargo run --bin kanidmd recover_account -c ./examples/insecure_server.toml admin
Success - password reset to -> Et8QRJgQkMJu3v1AQxcbxRWW44qRUZPpr6BJ9fCGapAB9cT4

Record the password above, then run the server start command:

cd kanidmd/daemon
cargo run --bin kanidmd server -c ../../examples/insecure_server.toml

(The server start command is also a script in kanidmd/daemon/

In a new terminal, you can now build and run the client tools with:

cargo run --bin kanidm -- --help
cargo run --bin kanidm -- login -H https://localhost:8443 -D anonymous -C /tmp/kanidm/ca.pem
cargo run --bin kanidm -- self whoami -H https://localhost:8443 -D anonymous -C /tmp/kanidm/ca.pem

cargo run --bin kanidm -- login -H https://localhost:8443 -D admin -C /tmp/kanidm/ca.pem
cargo run --bin kanidm -- self whoami -H https://localhost:8443 -D admin -C /tmp/kanidm/ca.pem

Raw actions

The server has a low-level stateful API you can use for more complex or advanced tasks on large numbers of entries at once. Some examples are below, but generally we advise you to use the APIs or CLI tools. These are very handy to "unbreak" something if you make a mistake however!

# Create from json (group or account)
kanidm raw create -H https://localhost:8443 -C ../insecure/ca.pem -D admin example.create.account.json
kanidm raw create  -H https://localhost:8443 -C ../insecure/ca.pem -D idm_admin

# Apply a json stateful modification to all entries matching a filter
kanidm raw modify -H https://localhost:8443 -C ../insecure/ca.pem -D admin '{"or": [ {"eq": ["name", "idm_person_account_create_priv"]}, {"eq": ["name", "idm_service_account_create_priv"]}, {"eq": ["name", "idm_account_write_priv"]}, {"eq": ["name", "idm_group_write_priv"]}, {"eq": ["name", "idm_people_write_priv"]}, {"eq": ["name", "idm_group_create_priv"]} ]}' example.modify.idm_admin.json
kanidm raw modify -H https://localhost:8443 -C ../insecure/ca.pem -D idm_admin '{"eq": ["name", "idm_admins"]}' example.modify.idm_admin.json

# Search and show the database representations
kanidm raw search -H https://localhost:8443 -C ../insecure/ca.pem -D admin '{"eq": ["name", "idm_admin"]}'

# Delete all entries matching a filter
kanidm raw delete -H https://localhost:8443 -C ../insecure/ca.pem -D idm_admin '{"eq": ["name", "test_account_delete_me"]}'

Building the Web UI

NOTE: There is a pre-packaged version of the Web UI at /kanidmd_web_ui/pkg/, which can be used directly. This means you don't need to build the Web UI yourself.

The Web UI uses Rust WebAssembly rather than Javascript. To build this you need to set up the environment:

cargo install wasm-pack

Then you are able to build the UI:

cd kanidmd_web_ui/

To build for release, run

The "developer" profile for kanidmd will automatically use the pkg output in this folder.

Build a Kanidm Container

Build a container with the current branch using:

make <TARGET>

Check make help for a list of valid targets.

The following environment variables control the build:

ENV variableDefinitionDefault
IMAGE_BASEBase location of the container image.kanidm
IMAGE_VERSIONDetermines the container's tag.None
CONTAINER_TOOL_ARGSSpecify extra options for the container build tool.None
IMAGE_ARCHPassed to --platforms when the container is built.linux/amd64,linux/arm64
CONTAINER_BUILD_ARGSOverride default ARG settings during the container build.None
CONTAINER_TOOLUse an alternative container build tool.docker
BOOK_VERSIONSets version used when building the documentation book.master

Container Build Examples

Build a kanidm container using podman:

CONTAINER_TOOL=podman make build/kanidmd

Build a kanidm container and use a redis build cache:

CONTAINER_BUILD_ARGS='--build-arg "SCCACHE_REDIS=redis://"' make build/kanidmd

Automatically Built Containers

To speed up testing across platforms, we're leveraging GitHub actions to build containers for test use.

Whenever code is merged with the master branch of Kanidm, containers are automatically built for kanidmd and radius. Sometimes they fail to build, but we'll try to keep them avilable.

To find information on the packages, visit the Kanidm packages page.

An example command for pulling and running the radius container is below. You'll need to authenticate with the GitHub container registry first.

docker pull
docker run --rm -it \
    -v $(pwd)/kanidm:/data/kanidm \

This assumes you have a kanidm client configuration file in the current working directory.

Building the Book

You'll need mdbook to build the book:

cargo install mdbook

To build it:

cd kanidm_book
mdbook build

Or to run a local webserver:

cd kanidm_book
mdbook serve

Access Profiles Rework 2022

Access controls are critical for a project like Kanidm to determine who can access what on other entries. Our access controls have to be dynamic and flexible as administrators will want to define their own access controls. In almost every call in the server, they are consulted to determine if the action can be carried out. We also supply default access controls so that out of the box we are a complete and useful IDM.

The original design of the access control system was intended to satisfy our need for flexibility, but we have begun to discover a number of limitations. The design incorporating filter queries makes them hard to administer as we have not often publicly talked about the filter language and how it internally works. Because of their use of filters it is hard to see on an entry "what" access controls will apply to entries, making it hard to audit without actually calling the ACP subsystem. Currently the access control system has a large impact on performance, accounting for nearly 35% of the time taken in a search operation.

Additionally, the default access controls that we supply have started to run into limits and rough cases due to changes as we have improved features. Some of this was due to limited design with user cases in mind during development.

To resolve this a number of coordinating features need implementation to improve this situation. These features will be documented first, and the use cases second with each use case linking to the features that satisfy it.

Required Features to Satisfy

Refactor of default access controls

The current default privileges will need to be refactored to improve seperation of privilege and improved delegation of finer access rights.

Access profiles target specifiers instead of filters

Access profiles should target a list of groups for who the access profile applies to, and who recieves the access it is granting.

Alternately an access profile could target "self" so that self-update rules can still be expressed.

An access profile could target an oauth2 definition for the purpose of allowing reads to members of a set of scopes that can access the service.

The access profile receiver would be group based only. This allows specifying that "X group of members can write self" meaning that any member of that group can write to themself and only themself.

In the future we could also create different target/receiver specifiers to allow other extended management and delegation scenarioes. This improves the situation making things more flexible from the current filter system. It also may allow filters to be simplified to remove the SELF uuid resolve step in some cases.

Filter based groups

These are groups who's members are dynamicly allocated based on a filter query. This allows a similar level of dynamic group management as we have currently with access profiles, but with the additional ability for them to be used outside of the access control context. This is the "bridge" allowing us to move from filter based access controls to "group" targetted.

A risk of filter based groups is "infinite churn" because of recursion. This can occur if you had a rule such a "and not memberof = self" on a dynamic group. Because of this, filters on dynamic groups may not use "memberof" unless they are internally provided by the kanidm project so that we can vet these rules as correct and without creating infinite recursion scenarioes.

Access rules extracted to ACI entries on targets

The access control profiles are an excellent way to administer access where you can specific whom has access to what, but it makes it harder for the reverse query which is "who has access to this specific entity". Since this is needed for both search and auditing, by specifying our access profiles in the current manner, but using them to generate ACE rules on the target entry will allow the search and audit paths to answer the question of "who has access to this entity" much faster.

Sudo Mode

A flag should exist on a session defining "sudo" mode which requires a special account policy membership OR a re-authentication to enable. This sudo flag is a time window on a session token which can allow/disallow certain behaviours. It would be necessary for all write paths to have access to this value.

Account Policy

Account policy defines rules on accounts and what they can or can't do with regard to properties and authentication. This is required for sudo mode so that a group of accounts can be "always in sudo" mode and this enforces rules on session expiry.

Access Control Use Cases

Default Roles / Seperation of Privilege

By default we attempt to seperate privileges so that "no single account" has complete authority over the system.

Satisfied by:

  • Refactor of default access controls
  • Filter based groups
  • Sudo Mode

System Admin

This role, also called "admins" is responsible to manage Kanidm as a service. It does NOT manage users or accounts.

The "admins" role is responsible to manage:

  • The name of the domain
  • Configuration of the servers and replication
  • Management of external integrations (oauth2)

Service Account Admin

The role would be called "sa_admins" and would be responsible for top level management of service accounts, and delegating authority for service account administration to managing users.

  • Create service accounts
  • Delegate service account management to owners groups
  • Migrate service accounts to persons

The service account admin is capable of migrating service accounts to persons as it is "yielding" control of the entity, rather than an idm admin "taking" the entity which may have security impacts.

Service Desk

This role manages a subset of persons. The helpdesk roles are precluded from modification of "higher privilege" roles like service account, identity and system admins. This is due to potential privilege escalation attacks.

  • Can create credential reset links
  • Can lock and unlock accounts and their expiry.

Idm Admin

This role manages identities, or more specifically person accounts. In addition in is a "high privilege" service desk role and can manage high privilege users as well.

  • Create persons
  • Modify and manage persons
  • All roles of service desk for all persons

Self Write / Write Privilege

Currently write privileges are always available to any account post-authentication. Writes should only be available after an extra "challenge" or "sudo" style extra auth, and only have a limited time window of usage. The write window can be extended during the session. This allows extremely long lived sessions contrast to the current short session life. It also makes it safer to provide higher levels of privilege to persons since these rights are behind a re-authentication event.

Some accounts should always be considered able to write, and these accounts should have limited authentication sessions as a result of this.

Satisfied by:

  • Access profiles target specifiers instead of filters
  • Sudo Mode

Oauth2 Service Read (Nice to Have)

For ux/ui integration, being able to list oauth2 applications that are accessible to the user would be a good feature. To limit "who" can see the oauth2 applications that an account can access a way to "allow read" but by proxy of the related users of the oauth2 service. This will require access controls to be able to interept the oauth2 config and provide rights based on that.

Satisfied by:

  • Access profiles target specifiers instead of filters


Access controls should be easier to manage and administer, and should be group based rather than filter based. This will make it easier for administrators to create and define their own access rules.

  • Refactor of default access controls
  • Access profiles target specifiers instead of filters
  • Filter based groups

Service Account Access

Service accounts should be able to be "delegated" administration, where a group of users can manage a service account. This should not require administrators to create unique access controls for each service account, but a method to allow mapping of the service account to "who manages it".

  • Sudo Mode
  • Account Policy
  • Access profiles target specifiers instead of filters
  • Refactor of default access controls

Auditing of Access

It should be easier to audit whom has access to what by inspecting the entry to view what can access it.

  • Access rules extracted to ACI entries on targets
  • Access profiles target specifiers instead of filters

Access Profiles

Access Profiles (ACPs) are a way of expressing the set of actions which accounts are permitted to perform on database records (object) in the system.

As a result, there are specific requirements to what these can control and how they are expressed.

Access profiles define an action of allow or deny: deny has priority over allow and will override even if applicable. They should only be created by system access profiles because certain changes must be denied.

Access profiles are stored as entries and are dynamically loaded into a structure that is more efficent for use at runtime. Schema and its transactions are a similar implementation.

Search Requirements

A search access profile must be able to limit:

  1. the content of a search request and its scope.
  2. the set of data returned from the objects visible.

An example:

Alice should only be able to search for objects where the class is person and the object is a memberOf the group called "visible".

Alice should only be able to see those the attribute displayName for those users (not their legalName), and their public email.

Worded a bit differently. You need permission over the scope of entries, you need to be able to read the attribute to filter on it, and you need to be able to read the attribute to recieve it in the result entry.

If Alice searches for (&(name=william)(secretdata=x)), we should not allow this to proceed because Alice doesn't have the rights to read secret data, so they should not be allowed to filter on it. How does this work with two overlapping ACPs? For example: one that allows read of name and description to class = group, and one that allows name to user. We don't want to say (&(name=x)(description=foo)) and it to be allowed, because we don't know the target class of the filter. Do we "unmatch" all users because they have no access to the filter components? (Could be done by inverting and putting in an AndNot of the non-matchable overlaps). Or do we just filter our description from the users returned (But that implies they DID match, which is a disclosure).

More concrete:

search {
    action: allow
    targetscope: Eq("class", "group")
    targetattr: name
    targetattr: description

search {
    action: allow
    targetscope: Eq("class", "user")
    targetattr: name

SearchRequest {
    filter: And: {

A potential defense is:

acp class group: Pres(name) and Pres(desc) both in target attr, allow
acp class user: Pres(name) allow, Pres(desc) deny. Invert and Append

So the filter now is:

And: {
    AndNot: {
        Eq("class", "user")
    And: {

This would now only allow access to the name and description of the class group.

If we extend this to a third, this would work. A more complex example:

search {
    action: allow
    targetscope: Eq("class", "group")
    targetattr: name
    targetattr: description

search {
    action: allow
    targetscope: Eq("class", "user")
    targetattr: name

search {
    action: allow
    targetscope: And(Eq("class", "user"), Eq("name", "william"))
    targetattr: description

Now we have a single user where we can read description. So the compiled filter above as:

And: {
    AndNot: {
        Eq("class", "user")
    And: {

This would now be invalid, first, because we would see that class=user and william has no name so that would be excluded also. We also may not even have "class=user" in the second ACP, so we can't use subset filter matching to merge the two.

As a result, I think the only possible valid solution is to perform the initial filter, then determine on the candidates if we could have have valid access to filter on all required attributes. IE this means even with an index look up, we still are required to perform some filter application on the candidates.

I think this will mean on a possible candidate, we have to apply all ACP, then create a union of the resulting targetattrs, and then compared that set into the set of attributes in the filter.

This will be slow on large candidate sets (potentially), but could be sped up with parallelism, caching or other methods. However, in the same step, we can also apply the step of extracting only the allowed read target attrs, so this is a valuable exercise.

Delete Requirements

A delete profile must contain the content and scope of a delete.

An example:

Alice should only be able to delete objects where the memberOf is purgeable, and where they are not marked as protected.

Create Requirements

A create profile defines the following limits to what objects can be created, through the combination of filters and atttributes.

An example:

Alice should only be able to create objects where the class is group, and can only name the group, but they cannot add members to the group.

An example of a content requirement could be something like "the value of an attribute must pass a regular expression filter". This could limit a user to creating a group of any name, except where the group's name contains "admin". This a contrived example which is also possible with filtering, but more complex requirements are possible.

For example, we want to be able to limit the classes that someone could create on an object because classes often are used in security rules.

Modify Requirements

A modify profile defines the following limits:

  • a filter for which objects can be modified,
  • a set of attributes which can be modified.

A modify profile defines a limit on the modlist actions.

For example: you may only be allowed to ensure presence of a value. (Modify allowing purge, not-present, and presence).

Content requirements (see Create Requirements) are out of scope at the moment.

An example:

Alice should only be able to modify a user's password if that user is a member of the students group.

Note: modify does not imply read of the attribute. Care should be taken that we don't disclose the current value in any error messages if the operation fails.

Targeting Requirements

The target of an access profile should be a filter defining the objects that this applies to.

The filter limit for the profiles of what they are acting on requires a single special operation which is the concept of "targeting self".

For example: we could define a rule that says "members of group X are allowed self-write to the mobilePhoneNumber attribute".

An extension to the filter code could allow an extra filter enum of self, that would allow this to operate correctly, and would consume the entry in the event as the target of "Self". This would be best implemented as a compilation of self -> eq(uuid, self.uuid).

Implementation Details

CHANGE: Receiver should be a group, and should be single value/multivalue? Can only be a group.

Example profiles:

search {
    action: allow
    receiver: Eq("memberof", "admins")
    targetscope: Pres("class")
    targetattr: legalName
    targetattr: displayName
    description: Allow admins to read all users names

search {
    action: allow
    receiver: Self
    targetscope: Self
    targetattr: homeAddress
    description: Allow everyone to read only their own homeAddress

delete {
    action: allow
    receiver: Or(Eq("memberof", "admins), Eq("memberof", "servicedesk"))
    targetscope: Eq("memberof", "tempaccount")
    description: Allow admins or servicedesk to delete any member of "temp accounts".

// This difference in targetscope behaviour could be justification to change the keyword here
// to prevent confusion.
create {
    action: allow
    receiver: Eq("name", "alice")
    targetscope: And(Eq("class", "person"), Eq("location", "AU"))
    createattr: location
    createattr: legalName
    createattr: mail
    createclass: person
    createclass: object
    description: Allow alice to make new persons, only with class person+object, and only set
        the attributes mail, location and legalName. The created object must conform to targetscope

modify {
    action: allow
    receiver: Eq("name", "claire")
    targetscope: And(Eq("class", "group"), Eq("name", "admins"))
    presentattr: member
    description: Allow claire to promote people as members of the admins group.

modify {
    action: allow
    receiver: Eq("name", "claire")
    targetscope: And(Eq("class", "person"), Eq("memberof", "students"))
    presentattr: sshkeys
    presentattr: class
    targetclass: unixuser
    description: Allow claire to modify persons in the students group, and to grant them the
        class of unixuser (only this class can be granted!). Subsequently, she may then give
        the sshkeys values as a modification.

modify {
    action: allow
    receiver: Eq("name", "alice")
    targetscope: Eq("memberof", "students")
    removedattr: sshkeys
    description: Allow allice to purge or remove sshkeys from members of the students group,
        but not add new ones

modify {
    action: allow
    receiver: Eq("name", "alice")
    targetscope: Eq("memberof", "students")
    removedattr: sshkeys
    presentattr: sshkeys
    description: Allow alice full control over the ssh keys attribute on members of students.

// This may not be valid: Perhaps if <*>attr: is on modify/create, then targetclass, must
// must be set, else class is considered empty.
// This profile could in fact be an invalid example, because presentattr: class, but not
// targetclass, so nothing could be granted.
modify {
    action: allow
    receiver: Eq("name", "alice")
    targetscope: Eq("memberof", "students")
    presentattr: class
    description: Allow alice to grant any class to members of students.

Formalised Schema

A complete schema would be:


acp_allowsingle valuebool
acp_enablesingle valueboolThis ACP is enabled
acp_receiversingle valuefilter???
acp_targetscopesingle valuefilter???
acp_search_attrmulti valueutf8 case insenseA list of attributes that can be searched.
acp_create_classmulti valueutf8 case insenseObject classes in which an object can be created.
acp_create_attrmulti valueutf8 case insenseAttribute Entries that can be created.
acp_modify_removedattrmulti valueutf8 case insenseModify if removed?
acp_modify_presentattrmulti valueutf8 case insense???
acp_modify_classmulti valueutf8 case insense???


NameMust HaveMay Have
access_control_profile[acp_receiver, acp_targetscope][description, acp_allow]
access_control_modify[acp_modify_removedattr, acp_modify_presentattr, acp_modify_class]
access_control_create[acp_create_class, acp_create_attr]

Important: empty sets really mean empty sets!

The ACP code will assert that both access_control_profile and one of the search/delete/modify/create classes exists on an ACP. An important factor of this design is now the ability to compose multiple ACP's into a single entry allowing a create/delete/modify to exist! However, each one must still list their respective actions to allow proper granularity.

"Search" Application

The set of access controls is checked, and the set where receiver matches the current identified user is collected. These then are added to the users requested search as:

And(<User Search Request>, Or(<Set of Search Profile Filters))

In this manner, the search security is easily applied, as if the targets to conform to one of the required search profile filters, the outer And condition is nullified and no results returned.

Once complete, in the translation of the entry -> proto_entry, each access control and its allowed set of attrs has to be checked to determine what of that entry can be displayed. Consider there are three entries, A, B, C. An ACI that allows read of "name" on A, B exists, and a read of "mail" on B, C. The correct behaviour is then:

A: name
B: name, mail
C: mail

So this means that the entry -> proto entry part is likely the most expensive part of the access control operation, but also one of the most important. It may be possible to compile to some kind of faster method, but initially a simple version is needed.

"Delete" Application

Delete is similar to search, however there is the risk that the user may say something like:


Were we to approach this like search, this would then have "every thing the identified user is allowed to delete, is deleted". A consideration here is that Pres("class") would delete "all" objects in the directory, but with the access control present, it would limit the deletion to the set of allowed deletes.

In a sense this is a correct behaviour - they were allowed to delete everything they asked to delete. However, in another it's not valid: the request was broad and they were not allowed access to delete everything they requested.

The possible abuse vector here is that an attacker could then use delete requests to enumerate the existence of entries in the database that they do not have access to. This requires someone to have the delete privilege which in itself is very high level of access, so this risk may be minimal.

So the choices are:

  1. Treat it like search and allow the user to delete what they are allowed to delete, but ignore other objects
  2. Deny the request because their delete was too broad, and they must specify a valid deletion request.

Option #2 seems more correct because the delete request is an explicit request, not a request where you want partial results. Imagine someone wants to delete users A and B at the same time, but only has access to A. They want this request to fail so they KNOW B was not deleted, rather than it succeed and have B still exist with a partial delete status.

However, a possible issue is that Option #2 means that a delete request of And(Eq(attr, allowed_attribute), Eq(attr, denied)), which is rejected may indicate presence of the denied attribute. So option #1 may help in preventing a security risk of information disclosure.

This is also a concern for modification, where the modification attempt may or may not fail depending on the entries and if you can/can't see them.

IDEA: You can only delete/modify within the read scope you have. If you can't read it (based on the read rules of search), you can't delete it. This is in addition to the filter rules of the delete applying as well. So performing a delete of Pres(class), will only delete in your read scope and will never disclose if you are denied access.

"Create" Application

Create seems like the easiest to apply. Ensure that only the attributes in createattr are in the createevent, ensure the classes only contain the set in createclass, then finally apply filter_no_index to the entry to entry. If all of this passes, the create is allowed.

A key point is that there is no union of create ACI's - the WHOLE ACI must pass, not parts of multiple. This means if a control say "allows creating group with member" and "allows creating user with name", creating a group with name is not allowed - despite your ability to create an entry with name, its classes don't match. This way, the administrator of the service can define create controls with specific intent for how they will be used without the risk of two controls causing unintended effects (users that are also groups, or allowing invalid values.

An important consideration is how to handle overlapping ACI. If two ACI could match the create should we enforce both conditions are upheld? Or only a single upheld ACI allows the create?

In some cases it may not be possible to satisfy both, and that would block creates. The intent of the access profile is that "something like this CAN" be created, so I believe that provided only a single control passes, the create should be allowed.

"Modify" Application

Modify is similar to Create, however we specifically filter on the modlist action of present, removed or purged with the action. The rules of create still apply; provided all requirements of the modify are permitted, then it is allowed once at least one profile allows the change.

A key difference is that if the modify ACP lists multiple presentattr types, the modify request is valid if it is only modifying one attribute. IE we say presentattr: name, email, but we only attempt to modify email.


  • When should access controls be applied? During an operation, we only validate schema after pre* Plugin application, so likely it has to be "at that point", to ensure schema-based validity of the entries that are allowed to be changed.
  • Self filter keyword should compile to eq("uuid", "...."). When do we do this and how?
  • memberof could take name or uuid, we need to be able to resolve this correctly, but this is likely an issue in memberof which needs to be addressed, ie memberof uuid vs memberof attr.
  • Content controls in create and modify will be important to get right to avoid the security issues of LDAP access controls. Given that class has special importance, it's only right to give it extra consideration in these controls.
  • In the future when recyclebin is added, a re-animation access profile should be created allowing revival of entries given certain conditions of the entry we are attempting to revive. A service-desk user should not be able to revive a deleted high-privilege user.

REST Interface

Kani Warning Note!
Here begins some early notes on the REST interface - much better ones are in the repository's designs directory.

There's an endpoint at /<api_version>/routemap (for example, https://localhost/v1/routemap) which is based on the API routes as they get instantiated.

It's very, very, very early work, and should not be considered stable at all.

An example of some elements of the output is below:

  "routelist": [
      "path": "/",
      "method": "GET"
      "path": "/robots.txt",
      "method": "GET"
      "path": "/ui/",
      "method": "GET"
      "path": "/v1/account/:id/_unix/_token",
      "method": "GET"
      "path": "/v1/schema/attributetype/:id",
      "method": "GET"
      "path": "/v1/schema/attributetype/:id",
      "method": "PUT"
      "path": "/v1/schema/attributetype/:id",
      "method": "PATCH"

Kanidm Python Module

So far it includes:

  • asyncio methods for all calls, leveraging aiohttp
  • every class and function is fully python typed (test by running make test/pykanidm/mypy)
  • test coverage for 95% of code, and most of the missing bit is just when you break things
  • loading configuration files into nice models using pydantic
  • basic password authentication
  • pulling RADIUS tokens

TODO: a lot of things.

Setting up your dev environment.

Setting up a dev environment can be a little complex because of the mono-repo.

  1. Install poetry: python -m pip install poetry. This is what we use to manage the packages, and allows you to set up virtual python environments easier.
  2. Build the base environment. From within the pykanidm directory, run: poetry install This'll set up a virtual environment and install all the required packages (and development-related ones)
  3. Start editing!

Most IDEs will be happier if you open the kanidm_rlm_python or pykanidm directories as the base you are working from, rather than the kanidm repository root, so they can auto-load integrations etc.

Building the documentation

To build a static copy of the docs, run:

make docs/pykanidm/build

You can also run a local live server by running:

make docs/pykanidm/serve

This'll expose a web server at http://localhost:8000.

RADIUS Module Development

Setting up a dev environment has some extra complexity due to the mono-repo design.

  1. Install poetry: python -m pip install poetry. This is what we use to manage the packages, and allows you to set up virtual python environments easier.
  2. Build the base environment. From within the kanidm_rlm_python directory, run: poetry install
  3. Install the kanidm python library: poetry run python -m pip install ../pykanidm
  4. Start editing!

Most IDEs will be happier if you open the kanidm_rlm_python or pykanidm directories as the base you are working from, rather than the kanidm repository root, so they can auto-load integrations etc.

Running a test RADIUS container

From the root directory of the Kanidm repository:

  1. Build the container - this'll give you a container image called kanidm/radius with the tag devel:
make build/radiusd
  1. Once the process has completed, check the container exists in your docker environment:
➜ docker image ls kanidm/radius
REPOSITORY      TAG       IMAGE ID       CREATED              SIZE
kanidm/radius   devel     5dabe894134c   About a minute ago   622MB

Note: If you're just looking to play with a pre-built container, images are also automatically built based on the development branch and available at

  1. Generate some self-signed certificates by running the script - just hit enter on all the prompts if you don't want to customise them. This'll put the files in /tmp/kanidm:
  1. Run the container:
cd kanidm_rlm_python && ./

You can pass the following environment variables to to set other options:

  • IMAGE: an alternative image such as
  • CONFIG_FILE: mount your own config file

For example: \
    CONFIG_FILE=~/.config/kanidm \

Testing authentication

Authentication can be tested through the client.localhost Network Access Server (NAS) configuration with:

docker exec -i -t radiusd radtest \
    <username> badpassword \ 10 testing123
docker exec -i -t radiusd radtest \
    <username> <radius show_secret value here> \ 10 testing123


Packages are known to exist for the following distributions:

To ease packaging for your distribution, the Makefile has targets for sets of binary outputs.

release/kanidmKanidm's CLI
release/kanidmdThe server daemon
release/kanidm-sshSSH-related utilities
release/kanidm-unixdUNIX tools, PAM/NSS modules

Debian / Ubuntu Packaging

Building packages

This happens in Docker currently, and here's some instructions for doing it for Ubuntu:

  1. Start in the root directory of the repository.
  2. Run ./platform/debian/ This'll start a container, mounting the repository in ~/kanidm/.
  3. Install the required dependencies by running ./platform/debian/
  4. Building packages uses make, get a list by running make -f ./platform/debian/Makefile help
➜ make -f platform/debian/Makefile help
	 build a .deb for the Kanidm CLI
	 build a .deb for the Kanidm daemon
	 build a .deb for the Kanidm SSH tools
	 build a .deb for the Kanidm UNIX tools (PAM/NSS, unixd and related tools)
	 build all the debs
  1. So if you wanted to build the package for the Kanidm CLI, run make -f ./platform/debian/Makefile debs/kanidm.
  2. The package will be copied into the target directory of the repository on the docker host - not just in the container.

Adding a package

There's a set of default configuration files in packaging/; if you want to add a package definition, add a folder with the package name and then files in there will be copied over the top of the ones from packaging/ on build.

You'll need two custom files at minimum:

  • control - a file containing information about the package.
  • rules - a makefile doing all the build steps.

There's a lot of other files that can go into a .deb, some handy ones are:

FilenameWhat it does
preinstRuns before installation occurs
postrmRuns after removal happens
prermRuns before removal happens - handy to shut down services.
postinstRuns after installation occurs - we're using that to show notes to users