## Architecture

Kanidm has a number of components and layers that make it up. As this project is continually
evolving, if you have questions or notice discrepancies with this document please contact William
(Firstyear) at any time.

## Tools

Kanidm Tools are a set of command line clients that are intended to help administrators deploy,
interact with, and support a Kanidm server installation. These tools may also be used for servers or
machines to authenticate and identify users. This is the "human interaction" part of the server from
a CLI perspective.

## Clients

The `kanidm` client is a reference implementation of the client library, that others may consume or
interact with to communicate with a Kanidm server instance. The tools above use this client library
for all of its actions. This library is intended to encapsulate some high level logic as an
abstraction over the REST API.

## Proto

The `kanidm` proto is a set of structures that are used by the REST and raw API's for HTTP
communication. These are intended to be a reference implementation of the on-the-wire protocol, but
importantly these are also how the server represents its communication. This makes this the
authoritative source of protocol layouts with regard to REST or raw communication.

## Kanidmd (main server)

Kanidmd is intended to have minimal (thin) client tools, where the server itself contains most logic
for operations, transformations and routing of requests to their relevant datatypes. As a result,
the `kanidmd` section is the largest component of the project as it implements nearly everything
required for IDM functionality to exist.

# Search

Search is the "hard worker" of the server, intended to be a fast path with minimal overhead so that
clients can acquire data as quickly as possible. The server follows the below pattern.

![Search flow diagram](diagrams/search-flow.png)

(1) All incoming requests are from a client on the left. These are either REST requests, or a
structured protocol request via the raw interface. It's interesting to note the raw request is
almost identical to the queryserver event types - whereas REST requests we have to generate request
messages that can become events.

The frontend uses a webserver with a thread-pool to process and decode network I/O operations
concurrently. This then sends asynchronous messages to a worker (actor) pool for handing.

(2) These search messages in the actors are transformed into "events" - a self-contained structure
containing all relevant data related to the operation at hand. This may be the event origin (a user
or internal), the requested filter (query), and perhaps even a list of attributes requested. These
events are designed to ensure correctness. When a search message is transformed to a search event,
it is checked by the schema to ensure that the request is valid and can be satisfied securely.

As these workers are in a thread pool, it's important that these are concurrent and do not lock or
block - this concurrency is key to high performance and safety. It's also worth noting that this is
the level where read transactions are created and committed - all operations are transactionally
protected from an early stage to guarantee consistency of the operations.

3. When the event is known to be consistent, it is then handed to the queryserver - the query server
   begins a process of steps on the event to apply it and determine the results for the request.
   This process involves further validation of the query, association of metadata to the query for
   the backend, and then submission of the high-level query to the backend.

4. The backend takes the request and begins the low-level processing to actually determine a
   candidate set. The first step in query optimisation, to ensure we apply the query in the most
   efficient manner. Once optimised, we then use the query to query indexes and create a potential
   candidate set of identifiers for matching entries (5.). Once we have this candidate id set, we
   then retrieve the relevant entries as our result candidate set (6.) and return them (7.) to the
   backend.

5. The backend now deserialises the databases candidate entries into a higher level and structured
   (and strongly typed) format that the query server knows how to operate on. These are then sent
   back to the query server.

6. The query server now applies access controls over what you can / can't see. This happens in two
   phases. The first is to determine "which candidate entries you have the rights to query and view"
   and the second is to determine "which attributes of each entry you have the right to perceive".
   This separation exists so that other parts of the server can _impersonate_ users and conduct
   searches on their behalf, but still internally operate on the full entry without access controls
   limiting their scope of attributes we can view.

7. From the entries reduced set (ie access controls applied), we can then transform each entry into
   it's protocol forms - where we transform each strong type into a string representation for
   simpler processing for clients. These protoentries are returned to the front end.

8. Finally, the protoentries are now sent to the client in response to their request.

# Write

The write path is similar to the search path, but has some subtle differences that are worth paying
attention to.

# ![write flow diagram](diagrams/write-flow.png)

(1), (2) Like search, all client operations come from the REST or raw apis, and are transformed or
generated into messages. These messages are sent to a single write worker. There is only a single
write worker due to the use of copy-on-write structures in the server, limiting us to a single
writer, but allowing search transaction to proceed without blocking in parallel.

(3) From the worker, the relevant event is created. This may be a "Create", "Modify" or "Delete"
event. The query server handles these slightly differently. In the create path, we take the set of
entries you wish to create as our candidate set. In modify or delete, we perform an impersonation
search, and use the set of entries within your read bounds to generate the candidate set. This
candidate set will now be used for the remainder of the writing operation.

It is at this point, we assert access controls over the candidate set and the changes you wish to
make. If you are not within rights to perform these operations the event returns an error.

(4) The entries are now sent to the pre-operation plugins for the relevant operation type. This
allows transformation of the candidate entries beyond the scope of your access controls, and to
maintain some elements of data consistency. For example, one plugin prevents creation of system
protected types where another ensures that uuid exists on every entry.

(5) These transformed entries are now returned to the query server.

(6) The backend is sent the list of entries for writing. Indexes are generated (7) as required based
on the new or modified entries, and the entries themselves are written (8) into the core db tables.
This operation returns a result (9) to the backend, which is then filtered up to the query server
(10)

(11) Provided all operations to this point have been successful, we now apply post write plugins
which may enforce or generate different properties in the transaction. This is similar to the pre
plugins, but allows different operations. For example, a post plugin ensures uuid reference types
are consistent and valid across the set of changes in the database. The most critical is memberof,
which generates reverse reference links from entries to their group memberships, enabling fast rbac
operations. These are done as post plugins because at this point internal searches can now yield and
see the modified entries that we have just added to the indexes and datatables, which is important
for consistency (and simplicity) especially when you consider batched operations.

(12) Finally the result is returned up (13) through (14) the layers (15) to the client to inform
them of the success (or failure) of the operation.

# IDM

TBD

## Radius

The radius components are intended to be minimal to support a common set of radius operations in a
container image that is simple to configure. If you require a custom configuration you should use
the python tools here and configure your own radius instance as required.
