## File: README.md

<a href="https://kurrent.io">
  
    
    
    
  
</a>

- [What is Kurrent](#what-is-kurrent)
- [What is KurrentDB](#what-is-kurrentdb)
- [What is Kurrent Cloud](#what-is-kurrent-cloud)
- [Licensing](#licensing)
- [Documentation](#docs)
- [Getting started with KurrentDB](#getting-started-with-kurrentdb)
- [Getting started with Kurrent Cloud](#getting-started-with-kurrent-cloud)
- [Client libraries](#client-libraries)
- [Deployment](#deployment)
- [Communities](#communities)
- [Contributing](#contributing)
- [Building KurrentDB](#building-kurrentdb)
- [More resources](#more-resources)

## What is Kurrent

Event Store – the company and the product – are rebranding as Kurrent.

- The flagship product will be referred to as “the Kurrent event-native data platform” or “the Kurrent platform” or simply “Kurrent"
- EventStoreDB will be referred to as KurrentDB
- Event Store Cloud will now be called Kurrent Cloud

Read more about the rebrand in the [rebrand FAQ](https://www.kurrent.io/blog/kurrent-re-brand-faq).

## What is KurrentDB

KurrentDB is a database that's engineered for modern software applications and event-driven architectures. Its event-native design simplifies data modeling and preserves data integrity while the integrated streaming engine solves distributed messaging challenges and ensures data consistency.

Download the [latest version](https://kurrent.io/downloads).
For more product information visit [the website](https://kurrent.io/kurrent).

## What is Kurrent Cloud?

 Kurrent Cloud is a fully managed cloud offering that's designed to make it easy for developers to build and run highly available and secure applications that incorporate KurrentDB without having to worry about managing the underlying infrastructure. You can provision KurrentDB clusters in AWS, Azure, and GCP, and connect these services securely to your own cloud resources.

For more details visit [the website](https://kurrent.io/kurrent-cloud).

## Licensing

View [KurrentDB's licensing information](https://github.com/kurrent-io/KurrentDB/blob/master/LICENSE.md).

## Docs

For guidance on installation, development, deployment, and administration, see the [User Documentation](https://docs.kurrent.io/).

## Getting started with KurrentDB

Follow the [getting started guide](https://docs.kurrent.io/latest.html).

## Getting started with Kurrent Cloud

Kurrent can manage KurrentDB for you, so you don't have to run your own clusters.
See the online documentation: [Getting started with Kurrent Cloud](https://docs.kurrent.io/cloud/).

## Client libraries

[This guide](https://docs.kurrent.io/clients/grpc/getting-started.html) shows you how to get started with KurrentDB by setting up an instance or cluster and configuring it.
KurrentDB supports the gRPC protocol.

### KurrentDB supported clients

- Python: [pyeventsourcing/kurrentdbclient](https://pypi.org/project/kurrentdbclient/)
- Node.js (javascript/typescript): [kurrent-io/KurrentDB-Client-NodeJS](https://github.com/kurrent-io/KurrentDB-Client-NodeJS)
- Java: [(kurrent-io/KurrentDB-Client-Java](https://github.com/kurrent-io/KurrentDB-Client-Java)
- .NET: [kurrent-io/EventStore-Client-Dotnet](https://github.com/kurrent-io/EventStore-Client-Dotnet)
- Go: [kurrent-io/KurrentDB-Client-Go](https://github.com/kurrent-io/KurrentDB-Client-Go)
- Rust: [kurrent-io/KurrentDB-Client-Rust](https://github.com/kurrent-io/KurrentDB-Client-Rust)
- Read more in the [gRPC clients documentation](https://docs.kurrent.io/clients/grpc)

### Community supported clients

- Elixir: [NFIBrokerage/spear](https://github.com/NFIBrokerage/spear)
- Ruby: [yousty/event_store_client](https://github.com/yousty/event_store_client)

Read more in the [documentation](https://docs.kurrent.io/server/latest/quick-start/#protocols-clients-and-sdks).

### Legacy clients (support ends with EventStoreDB v23.10 LTS)

- .NET: [EventStoreDB-Client-Dotnet-Legacy](https://github.com/kurrent-io/EventStoreDB-Client-Dotnet-Legacy)

## Deployment

- Kurrent Cloud - [steps to get started in Kurrent Cloud](https://docs.kurrent.io/cloud/).
- Self-managed - [steps to host KurrentDB yourself](https://docs.kurrent.io/latest/quick-start/installation).

## Communities

[Join our global community](https://www.kurrent.io/community) of developers.

- [Discuss](https://discuss.kurrent.io/)
- [Discord (Kurrent)](https://discord.gg/Phn9pmCw3t)
- [Discord (ddd-cqrs-es)](https://discord.com/invite/sEZGSHNNbH)

## Contributing

Development is done on the `master` branch.
We attempt to do our best to ensure that the history remains clean and to do so, commits are automatically squashed into a single logical commit when pull requests are merged.

If you want to switch to a particular release, you can check out the release branch for that particular release. For example:
`git checkout release/v25.0`

- [Create an issue](https://github.com/kurrent-io/KurrentDB/issues)
- [Documentation](https://docs.kurrent.io/)
- [Contributing guide](CONTRIBUTING.md)

## Building KurrentDB

KurrentDB is written in a mixture of C# and JavaScript. It can run on Windows, Linux and macOS (using Docker) using the .NET Core runtime.

**Prerequisites**

- [.NET SDK 10.0](https://dotnet.microsoft.com/download/dotnet/10.0)

Once you've installed the prerequisites for your system, you can launch a `Release` build of KurrentDB as follows:

```
dotnet build -c Release
```

To start a single node, you can then run:

```
dotnet run --project ./src/KurrentDB/KurrentDB.csproj -c Release -e ASPNETCORE_ENVIRONMENT=Development -- --dev --db ./tmp/data --index ./tmp/index --log ./tmp/log
```

Alternatively, you can run the `KurrentDB - Dev DB` profile pre-configured in `launchSettings.json`:

```
dotnet run --project ./src/KurrentDB/KurrentDB.csproj -c Release -lp "KurrentDB - Dev DB"
```

### Running the tests

You can launch the tests as follows:

```
dotnet test --solution KurrentDB.slnx
```

### Build KurrentDB Docker image

You can also build a Docker image by running the command:

```
docker build --tag mykurrentdb . \
--build-arg CONTAINER_RUNTIME={container-runtime}
--build-arg RUNTIME={runtime}
```

For instance:

```
docker build --tag mykurrentdb . \
--build-arg CONTAINER_RUNTIME=noble \
--build-arg RUNTIME=linux-x64
```

**_Note:_** Because of the [Docker issue](https://github.com/moby/buildkit/issues/1900), if you're building a Docker image on Windows, you may need to set the `DOCKER_BUILDKIT=0` environment variable. For instance, running in PowerShell:

```
$env:DOCKER_BUILDKIT=0; docker build --tag mykurrentdb . `
--build-arg CONTAINER_RUNTIME=noble `
--build-arg RUNTIME=linux-x64
```

Currently, we support the following configurations:

1. Noble:

- `CONTAINER_RUNTIME=noble`
- `RUNTIME=linux-x64`

You can verify the built image by running:

```
docker run --rm mykurrentdb --insecure --what-if
```

## More resources

- [Release notes](https://docs.kurrent.io/server/latest/release-schedule/release-notes.html)
- [Beginners Guide to Event Sourcing](https://kurrent.io/event-sourcing)
- [Articles](https://kurrent.io/blog)
- [Webinars](https://kurrent.io/webinars)
- [Contact us](https://kurrent.io/contact)

---

## File: docs/server/security/protocol-security.md

---
title: Protocol security
order: 2
---

# Protocol security

KurrentDB supports gRPC and the proprietary TCP protocol for high-throughput real-time communication. It
also has some HTTP endpoints for the management operations like scavenging, creating projections and so on.
KurrentDB also uses HTTP for the gossip seed endpoint, both for the cluster gossip, and
for clients that connect to the cluster using discovery mode.

All those protocols support encryption with TLS and SSL. You can only use one set of certificates for both TLS and HTTPS.

The protocol security configuration depends a lot on the deployment topology and platform. We have created an
interactive [configuration tool](https://configurator.eventstore.com), which also has instructions on how to generate and install
the certificates and configure KurrentDB nodes to use them.

::: note
For non-production use, two run modes skip most of the configuration described on this page: [`--insecure`](./security-options.md#running-without-security) disables TLS entirely, and [`--dev`](./security-options.md#development-mode) generates a self-signed certificate for local use.
:::

## Certificates configuration

In this section, you can find settings related to configuring the certificates for protocol security (HTTPS and TLS).

### Certificate common name

SSL certificates can be created with a common name (CN), which is an arbitrary string. Usually it contains the DNS name for which the certificate is issued.

When cluster nodes connect to each other, they need to ensure that they indeed talk to another node and not something that pretends to be a node. To achieve that, KurrentDB authenticates a connecting node by ensuring that it supplies a trusted client certificate having a CN that matches exactly with the CN in its own certificate. This essentially means that the CN must be the same across all node certificates by default. For example, when using the [certificate generator](#certificate-generation-tool), the CN is set to `eventstoredb-node` in all node certificates.

::: note
KurrentDB will automatically read the CN from the node's certificate and use it as the `CertificateReservedNodeCommonName` by default. However, if you still choose to specify the `CertificateReservedNodeCommonName` in your configuration, it will take precedence.
:::

In practice, it's not always possible to obtain certificates where the CN is the same across all nodes. For instance, when using a public CA, single-domain certificates are very common and these certificates cannot be used on multiple nodes as they are valid for exactly one host. In this case, the `CertificateReservedNodeCommonName` setting can be configured with a wildcard as per the following example:

If the domains are `node1.kurrentdb.mycompany.org`, `node2.kurrentdb.mycompany.org` and `node3.kurrentdb.mycompany.org`, then `CertificateReservedNodeCommonName` must be set to `*.kurrentdb.mycompany.org`.

| Format               | Syntax                                             |
|:---------------------|:---------------------------------------------------|
| Command line         | `--certificate-reserved-node-common-name`          |
| YAML                 | `CertificateReservedNodeCommonName`                |
| Environment variable | `KURRENTDB_CERTIFICATE_RESERVED_NODE_COMMON_NAME`  |

::: warning
Server certificates **must** have the internal and external IP addresses (`ReplicationIp` and `NodeIp` respectively) or DNS names as subject alternative names.
:::

### Disable client authentication EKU validation

A KurrentDB node currently uses a single TLS certificate for
1. Authenticating as a server when a client application or another node connects to it.
2. Authenticating as a client when it connects to another node.

When a node is authenticating another node that is connecting to it, it will by default comply with [RFC 5280 section-4.2.1.12](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12) and require that the connecting node's certificate has the `clientAuth` Extended Key Usage (EKU), or that the certificate does not use EKUs at all.

You can disable the `clientAuth` EKU requirement by setting `DisableClientAuthEkuValidation` to `true`. The authentication will still verify the chain of trust, expiration, and common name of the certificate. Please consider whether this is appropriate for your deployment.

This setting allows KurrentDB to operate using certificates signed by publicly trusted certificate authorities, which, largely driven by changes to the [Chrome Root Program policy](https://www.chromium.org/Home/chromium-security/root-ca-policy/), are moving away from issuing certificates with the `clientAuth` EKU. 

This setting does not affect authentication of users via user certificates, which will still require the `clientAuth` EKU.

| Format               | Syntax                                         |
|:---------------------|:-----------------------------------------------|
| Command line         | `--disable-client-auth-eku-validation`         |
| YAML                 | `DisableClientAuthEkuValidation`               |
| Environment variable | `KURRENTDB_DISABLE_CLIENT_AUTH_EKU_VALIDATION` |

**Default**: `false`

### Trusted root certificates

When getting an incoming connection, the server needs to ensure if the certificate used for the connection can be trusted. For this to work, the server needs to know where trusted root certificates are located.

KurrentDB will use the default trusted root certificates location of `/etc/ssl/certs` when running on Linux only. So if you are running on Windows or a platform with a different default certificates location, you'd need to explicitly tell the node to use the OS default root certificate store. For certificates signed by a private CA, you just provide the path to the CA certificate file (but not the filename).

If you are running on Windows, you can also load the trusted root certificate from the Windows Certificate Store. The available options for configuring this are described [below](#certificate-store-windows).

| Format               | Syntax                                      |
|:---------------------|:--------------------------------------------|
| Command line         | `--trusted-root-certificates-paths`         |
| YAML                 | `TrustedRootCertificatesPath`               |
| Environment variable | `KURRENTDB_TRUSTED_ROOT_CERTIFICATES_PATH`  |

**Default**: n/a on Windows, `/etc/ssl/certs` on Linux

### Certificate file

The `CertificateFile` setting needs to point to the certificate file, which will be used by the cluster node.

| Format               | Syntax                        |
|:---------------------|:------------------------------|
| Command line         | `--certificate-file`          |
| YAML                 | `CertificateFile`             |
| Environment variable | `KURRENTDB_CERTIFICATE_FILE`  |

If the certificate file is protected by password, you'd need to set the `CertificatePassword` value accordingly, so the server can load the certificate.

| Format               | Syntax                            |
|:---------------------|:----------------------------------|
| Command line         | `--certificate-password`          |
| YAML                 | `CertificatePassword`             |
| Environment variable | `KURRENTDB_CERTIFICATE_PASSWORD`  |

If the certificate file doesn't contain the certificate private key, you need to tell the node where to find the key file using the `CertificatePrivateKeyFile` setting. The private key can be in RSA, or PKCS8 format.

| Format               | Syntax                                    |
|:---------------------|:------------------------------------------|
| Command line         | `--certificate-private-key-file`          |
| YAML                 | `CertificatePrivateKeyFile`               |
| Environment variable | `KURRENTDB_CERTIFICATE_PRIVATE_KEY_FILE`  |

If the private key file is an encrypted PKCS #8 file, then you need to provide the password with the `CertificatePrivateKeyPassword` option.

| Format               | Syntax                                        |
|:---------------------|:----------------------------------------------|
| Command line         | `--certificate-private-key-password`          |
| YAML                 | `CertificatePrivateKeyPassword`               |
| Environment variable | `KURRENTDB_CERTIFICATE_PRIVATE_KEY_PASSWORD`  |


### Certificate store (Windows)

The certificate store location is the location of the Windows certificate store, for example `CurrentUser`.

| Format               | Syntax                                  |
|:---------------------|:----------------------------------------|
| Command line         | `--certificate-store-location`          |
| YAML                 | `CertificateStoreLocation`              |
| Environment variable | `KURRENTDB_CERTIFICATE_STORE_LOCATION`  |

The certificate store name is the name of the Windows certificate store, for example `My`.

| Format               | Syntax                              |
|:---------------------|:------------------------------------|
| Command line         | `--certificate-store-name`          |
| YAML                 | `CertificateStoreName`              |
| Environment variable | `KURRENTDB_CERTIFICATE_STORE_NAME`  |

You can load a certificate using either its thumbprint or its subject name.
If using the thumbprint, the server expects to only find one certificate file matching that thumbprint in the cert store.

| Format               | Syntax                              |
|:---------------------|:------------------------------------|
| Command line         | `--certificate-thumbprint`          |
| YAML                 | `CertificateThumbprint`             |
| Environment variable | `KURRENTDB_CERTIFICATE_THUMBPRINT`  |

The subject name matches any certificate that contains the specified name. This means that multiple matching certificates could be found.
To match any certificate made by the `es-gencert-cli` tool, you can set the subject name to `eventstoredb-node`.

If multiple matching certificates are found, then the certificate with the latest expiry date will be selected.

| Format               | Syntax                                |
|:---------------------|:--------------------------------------|
| Command line         | `--certificate-subject-name`          |
| YAML                 | `CertificateSubjectName`              |
| Environment variable | `KURRENTDB_CERTIFICATE_SUBJECT_NAME`  |

When you are loading your node certificates from the Windows cert store, you are likely to want to load the trusted root certificate from the cert store as well.
The options to configure this are similar to the ones for node certificates.

The trusted root certificate store location is the location of the Windows certificate store in which the trusted root certificate is installed, for example `CurrentUser`.

| Format               | Syntax                                               |
|:---------------------|:-----------------------------------------------------|
| Command line         | `--trusted-root-certificate-store-location`          |
| YAML                 | `TrustedRootCertificateStoreLocation`                |
| Environment variable | `KURRENTDB_TRUSTED_ROOT_CERTIFICATE_STORE_LOCATION`  |

The trusted root certificate store name is the name of the Windows certificate store in which the trusted root certificate is installed, for example `Root`.

| Format               | Syntax                                           |
|:---------------------|:-------------------------------------------------|
| Command line         | `--trusted-root-certificate-store-name`          |
| YAML                 | `TrustedRootCertificateStoreName`                |
| Environment variable | `KURRENTDB_TRUSTED_ROOT_CERTIFICATE_STORE_NAME`  |

Trusted root certificates can also be loaded using either its thumbprint or its subject name.
If using the thumbprint, the server expects to only find one trusted root certificate file matching that thumbprint in the cert store.

| Format               | Syntax                                           |
|:---------------------|:-------------------------------------------------|
| Command line         | `--trusted-root-certificate-thumbprint`          |
| YAML                 | `TrustedRootCertificateThumbprint`               |
| Environment variable | `KURRENTDB_TRUSTED_ROOT_CERTIFICATE_THUMBPRINT`  |

The subject name matches any certificate that contains the specified name. This means that multiple matching certificates could be found.
To match any root certificate made through the `es-gencert-cli` tool, you can set the Subject Name to `EventStoreDB CA`.

If multiple matching root certificates are found, then the root certificate with the latest expiry date will be selected.

| Format               | Syntax                                             |
|:---------------------|:---------------------------------------------------|
| Command line         | `--trusted-root-certificate-subject-name`          |
| YAML                 | `TrustedRootCertificateSubjectName`                |
| Environment variable | `KURRENTDB_TRUSTED_ROOT_CERTIFICATE_SUBJECT_NAME`  |

## Certificate generation tool

Kurrent provides the interactive Certificate Generation CLI, which creates certificates signed by a private, auto-generated CA for KurrentDB. You can use the [configuration wizard](https://configurator.eventstore.com), that will provide you exact CLI commands that you need to run to generate certificates matching your configuration.

### Getting started

The CLI is available from its [GitHub Repository](https://github.com/EventStore/es-gencert-cli). The latest release can be found under the [GitHub releases page.](https://github.com/EventStore/es-gencert-cli/releases)

We're releasing binaries for Windows, Linux and macOS. We also publish the tool as a Docker image.

Basic usage for the Certificate Generation CLI:

```bash
./es-gencert-cli [options] <command> [args]
```

Getting help for a specific command:

```bash
./es-gencert-cli -help <command>
```

::: warning
If you are running KurrentDB on Linux, remember that all certificate files should have restrictive rights, otherwise the OS won't allow using them.
Usually, you'd need to change rights for each certificate file to prevent the "permissions are too open" error.

You can do it by running the following command:

```bash
chmod 600 [file]
```
:::

### Generating the CA certificate

As the first step CA certificate needs to be generated. It'll need to be trusted for each of the nodes and client environment.

By default, the tool will create the `ca` directory in the `certs` directory you created. Two keys will be generated:
- `ca.crt` - public file that need to be used also for the nodes and client configuration,
- `ca.key` - private key file that should be used only in the node configuration. **Do not copy it to client environment**.

CA certificate will be generated with pre-defined CN `eventstoredb-node`.

To generate CA certificate run:

```bash
./es-gencert-cli create-ca
```

You can customise generated cert by providing following params:

| Param   | Description                                                       |
|:--------|:------------------------------------------------------------------|
| `-days` | The validity period of the certificate in days (default: 5 years) |
| `-out`  | The output directory (default: ./ca)                              |

Example:

```bash
./es-gencert-cli create-ca -out ./kurrentdb-ca
```

### Generating the Node certificate

You need to generate certificates signed by the CA for each node. They should be installed only on the specific node machine.

By default, the tool will create the `ca` directory in the `certs` directory you created. Two keys will be generated:
- `node.crt` - the public file that needs to be also used for the nodes and client configuration,
- `node.key` - the private key file that should be used only in the node's configuration. **Do not copy it to client environment**.

To generate node certificate run command:

```bash
./es-gencert-cli -help create_node
```

You can customise generated cert by providing following params:

| Param             | Description                                                                   |
|:------------------|:------------------------------------------------------------------------------|
| `-ca-certificate` | The path to the CA certificate file (default: `./ca/ca.crt`)                  |
| `-ca-key`         | The path to the CA key file (default: `./ca/ca.key`)                          |
| `-days`           | The output directory (default: `./nodeX` where X is an auto-generated number) |
| `-out`            | The output directory (default: `./ca`)                                        |
| `-ip-addresses`   | Comma-separated list of IP addresses of the node                              |
| `-dns-names`      | Comma-separated list of DNS names of the node                                 |

::: warning
While generating the certificate, you need to remember to pass internal end external:
- IP addresses to `-ip-addresses`: e.g. `127.0.0.1,172.20.240.1` and/or
- DNS names to `-dns-names`: e.g. `localhost,node1.kurrentdb`
  that will match the URLs that you will be accessing KurrentDB nodes.
  :::

Sample:

```
./es-gencert-cli-cli create-node \
    -ca-certificate ./kurrentdb-ca/ca.crt \
    -ca-key ./kurrentdb-ca/ca.key \
    -out ./node1 \
    -ip-addresses 127.0.0.1,172.20.240.1 \
    -dns-names localhost,node1.kurrentdb
```

### Running with Docker

You could also run the tool using Docker interactive container:

```bash
docker run --rm -i eventstore/es-gencert-cli <command> <options>
```

One useful scenario is to use the Docker Compose file tool to generate all the necessary certificates before starting cluster nodes.

Sample:

```yaml
version: "3.5"

services:
  setup:
    image: eventstore/es-gencert-cli:1.0.2
    entrypoint: bash
    user: "1000:1000"
    command: >
      -c "mkdir -p ./certs && cd /certs
      && es-gencert-cli create-ca
      && es-gencert-cli create-node -out ./node1 -ip-addresses 127.0.0.1,172.20.240.1 -dns-names localhost,node1.kurrentdb
      && es-gencert-cli create-node -out ./node1 -ip-addresses 127.0.0.1,172.20.240.2 -dns-names localhost,node2.kurrentdb
      && es-gencert-cli create-node -out ./node1 -ip-addresses 127.0.0.1,172.20.240.3 -dns-names localhost,node3.kurrentdb
      && find . -type f -print0 | xargs -0 chmod 666"
    container_name: setup
    volumes:
      - ./certs:/certs
```

See more in the [complete sample of docker-compose secured cluster configuration.](../quick-start/installation.md#use-docker-compose)

## Certificate installation on a client environment

To connect to KurrentDB, you need to install the auto-generated CA certificate file on the client machine (e.g. machine where the client is hosted, or your dev environment).

::: tabs#os
@tab Linux

1. Copy auto-generated CA file to dir `/usr/local/share/ca-certificates/`, e.g. using command:
  ```bash
  sudo cp ca.crt /usr/local/share/ca-certificates/event_store_ca.crt
  ```
2. Update the CA store:
  ```bash
  sudo update-ca-certificates
  ```

@tab Windows

1. You can manually import it to the local CA cert store through `Certificates Local Machine Management Console`. To do that select **Run** from the **Start** menu, and then enter `certmgr.msc`. Then import certificate to `Trusted Root Certification`.
2. You can also run the PowerShell script instead:

```powershell
Import-Certificate -FilePath ".\certs\ca\ca.crt" -CertStoreLocation Cert:\CurrentUser\Root
```

@tab macOS

1. In the Keychain Access app on your Mac, select either the login or System keychain. Drag the certificate file onto the Keychain Access app. If you're asked to provide a name and password, type the name and password for an administrator user on this computer.
2. You can also run the bash script:

```bash
sudo security add-certificates -k /Library/Keychains/System.keychain ca.crt 
```
:::

## Intermediate CA certificates

Intermediate CA certificates are supported by loading them from a [PEM](https://datatracker.ietf.org/doc/html/rfc1422) or [PKCS #12](https://datatracker.ietf.org/doc/html/rfc7292) bundle specified by the [`CertificateFile` configuration parameter](#certificate-file). To make sure that the configuration is correct, the certificate chain is validated on startup with the node's own certificate.

If you've used the [certificate generation tool](#certificate-generation-tool) with the default settings to generate your CA and node certificates, then you're not using intermediate CA certificates.

However, if you're using a public certificate authority (e.g [Let's Encrypt](https://letsencrypt.org/)) to generate your node certificates there is a chance that you're using intermediate CA certificates without knowing. This is due to the [Authority Information Access (AIA)](https://datatracker.ietf.org/doc/html/rfc4325#section-2) extension which allows intermediate certificates to be fetched from a remote server.

To verify if your certificate is using the AIA extension, you need to verify if there is a section named: `Authority Information Access` in the certificate.

::: tabs#os
@tab Linux

Use `openssl` to find the section in the certificate file:

```bash
openssl x509 -in /path/to/node.crt -text | grep 'Authority Information Access' -A 1
```
@tab Windows
Open the certificate, go to the `Details` tab and look for the `Authority Information Access` field.

If the extension is present, you can manually download the intermediate certificate from the URL present under the `CA Issuers` entry.
Note that you will usually need to convert the downloaded certificate from the `DER` to the `PEM` format.

This can be done with the following `openssl` command:

```powershell
openssl x509 -inform der -in /path/to/cert.der > /path/to/cert.pem
```

or with [an online service](https://www.sslshopper.com/ssl-converter.html) if you don't have openssl installed.
:::

It's possible that there are more than one intermediate CA certificates in the chain - so you need to verify if the certificate you've just downloaded also uses the AIA extension. If yes, you need to download the next intermediate CA certificate in the chain by repeating the same process above until you eventually reach a publicly trusted root certificate (i.e. the `Subject` and `Issuer` fields will match). In practice, there'll usually be at most two intermediate certificates in the chain.

### Bundling the intermediate certificates

The node's certificate should be first in the bundle, followed by the intermediates. Intermediates can be in any order but it would be good to keep it from leaf to root, as per the usual convention. The root certificate should not be bundled.

In the examples below, intermediate certificates are numbered from 1 to N starting from the leaf and going up.

#### PEM format

If your node's certificate and the intermediate CA certificates are both PEM formatted, that is they begin with `-----BEGIN CERTIFICATE-----` and end with `-----END CERTIFICATE-----` then you can simply append the contents of the intermediate certificate files to the end of the node's certificate file to create the bundle.

::: tabs#os
@tab Linux

```bash
cat /path/to/intermediate1.crt >> /path/to/node.crt
...
cat /path/to/intermediateN.crt >> /path/to/node.crt
```

@tab Windows

```powershell
type C:\path\to\intermediate1.crt >> C:\path\to\node.crt
...
type C:\path\to\intermediateN.crt >> C:\path\to\node.crt
```
:::

#### PKCS #12 format

If you want to generate a PKCS #12 bundle from PEM formatted certificate files, please follow the steps below.

```bash
cat /path/to/intermediate1.crt >> ./ca_bundle.crt
...
cat /path/to/intermediateN.crt >> ./ca_bundle.crt

openssl pkcs12 -export -in /path/to/node.crt -inkey /path/to/node.key -certfile ./ca_bundle.crt -out /path/to/node.p12 -passout pass:
```

### Adding intermediate certificates to the certificate store

Intermediate certificates also need to be added to the current user's certificate store.

This is required for two reasons:  
i)  For the full certificate chain to be sent when TLS connections are established  
ii) To improve performance by preventing certificate downloads if your certificate uses the AIA extension  

::: tabs#os
@tab Linux
The following script assumes KurrentDB is running under the `kurrent` account.

```bash
sudo su kurrent --shell /bin/bash
dotnet tool install --global dotnet-certificate-tool
 ~/.dotnet/tools/certificate-tool add -s CertificateAuthority -l CurrentUser --file /path/to/intermediate.crt
```

@tab Windows
To import the intermediate certificate in the `Intermediate Certification Authorities` certificate store, run the following PowerShell command under the same account as KurrentDB is running:

```powershell
Import-Certificate -FilePath .\path\to\intermediate.crt -CertStoreLocation Cert:\CurrentUser\CA
```

Optionally, to import the intermediate certificate in the `Local Computer` store, run the following as `Administrator`:

```powershell
Import-Certificate -FilePath .\ca.crt -CertStoreLocation Cert:\LocalMachine\CA
```
:::

---

## File: docs/server/security/security-options.md

---
title: Security options
order: 1
---

# Security

For production use, it is important to configure KurrentDB security features to prevent unauthorised access
to your data.

Security features of KurrentDB include:

- [Encryption in-flight](./protocol-security.md) using HTTPS and TLS
- [User authentication](./user-authentication.md) for allowing users with different roles to access the database
- [User authorization](./user-authorization.md) to restrict access to specific event streams
- [Encryption-at-rest](#encryption-at-rest) to protect data on disk
- [FIPS compatibility](#fips-140-2) to allow running KurrentDB on FIPS-enabled operating systems

## Configuration options

Below you can find more details about each of the available security options.

### Running without security

KurrentDB is secure by default. It means that you have to supply valid certificates and configuration for the database node to work.

TLS, authentication, and authorization can all be disabled by setting the `Insecure` option:

| Format               | Syntax                |
|:---------------------|:----------------------|
| Command line         | `--insecure`          |
| YAML                 | `Insecure`            |
| Environment variable | `KURRENTDB_INSECURE`  |

**Default**: `false`

::: warning
When running with protocol security disabled, everything is sent unencrypted over the wire. KurrentDB **does not use any authentication or authorization** when running insecure.
:::

### Running without TLS

If you want to disable transport security but keep authentication and authorization active, for example to run a dev/test cluster without generating certificates, or to deploy on an internal trusted network, use the `DisableTls` option.

Unlike `Insecure`, `DisableTls` does not disable authentication or authorization. Users and nodes still need valid credentials, and authorization policies still apply.

| Format               | Syntax                     |
|:---------------------|:---------------------------|
| Command line         | `--disable-tls`            |
| YAML                 | `DisableTls`               |
| Environment variable | `KURRENTDB_DISABLE_TLS`    |

**Default**: `false`

::: warning
When TLS is disabled, all traffic, including authentication credentials, is transmitted in cleartext. Do not enable `DisableTls` on networks where untrusted parties can observe traffic.
:::

#### Inter-node authentication: `ClusterSecret`

Nodes normally authenticate inter-node traffic (gossip, elections, replication, and internal administrative calls) using their TLS certificates. When TLS is disabled there are no client certificates to inspect, so you must configure a shared cluster secret that nodes can use instead.

| Format               | Syntax                             |
|:---------------------|:-----------------------------------|
| Command line         | `--cluster-secret <value>`         |
| YAML                 | `ClusterSecret: <value>`           |
| Environment variable | `KURRENTDB_CLUSTER_SECRET`         |

**Default**: `""` (empty)

In a cluster, the same value must be set on every node. KurrentDB will refuse to start with `DisableTls` enabled if `ClusterSecret` is empty.

`ClusterSecret` has no effect when TLS is enabled: nodes authenticate by mutual TLS in that case.

### Development mode

For development purposes, the `Dev` option starts a single node with TLS, authentication and authorization, but without needing to be provided with certificates. KurrentDB generates a self-signed certificate valid for one month and (on Windows) adds the certificate to the trusted root store automatically.

Dev mode also enables AtomPub over HTTP, and enables system projections.

| Format               | Syntax           |
|:---------------------|:-----------------|
| Command line         | `--dev`          |
| YAML                 | `Dev`            |
| Environment variable | `KURRENTDB_DEV`  |

**Default**: `false`

```bash
kurrentdb --dev
```

::: warning
Dev mode is not for production use.
:::

#### Persisting the dev certificate

By default the dev certificate is stored in the user certificate store. In containers or hardened environments where the home directory is not writable, KurrentDB falls back to an ephemeral in-memory key — a fresh certificate is generated on every startup, which breaks any client that has pinned the previous certificate.

Use the `DevCertPath` option to persist the certificate to a file:

| Format               | Syntax                     |
|:---------------------|:---------------------------|
| Command line         | `--dev-cert-path`          |
| YAML                 | `DevCertPath`              |
| Environment variable | `KURRENTDB_DEV_CERT_PATH`  |

```bash
kurrentdb --dev --dev-cert-path /var/lib/kurrentdb/certs/dev.pfx
```

On first start, KurrentDB generates the certificate, writes it as a PFX at the given path, and writes a PEM-encoded public certificate alongside it with a `.crt` extension (in the example above, `/var/lib/kurrentdb/certs/dev.crt`). Distribute the `.crt` file to clients for trust configuration. On subsequent starts, the existing certificate is loaded from the PFX; if it is expired or invalid, a new one is generated and saved over it.

When running in a container, mount a volume at the certificate directory so the certificate survives container restarts:

::: tabs
@tab kurrent-latest
```bash
docker run --name kurrentdb-dev -it -p 2113:2113 \
    -v $(pwd)/dev-certs:/var/lib/kurrentdb/certs \
    docker.kurrent.io/kurrent-latest/kurrentdb:latest \
    --dev --dev-cert-path /var/lib/kurrentdb/certs/dev.pfx
```
@tab kurrent-lts
```bash
docker run --name kurrentdb-dev -it -p 2113:2113 \
    -v $(pwd)/dev-certs:/var/lib/kurrentdb/certs \
    docker.kurrent.io/kurrent-lts/kurrentdb:latest \
    --dev --dev-cert-path /var/lib/kurrentdb/certs/dev.pfx
```
:::

#### Removing dev certificates

To remove dev certificates KurrentDB has installed in the user certificate store without starting the server:

| Format               | Syntax                       |
|:---------------------|:-----------------------------|
| Command line         | `--remove-dev-certs`         |
| YAML                 | `RemoveDevCerts`             |
| Environment variable | `KURRENTDB_REMOVE_DEV_CERTS` |

```bash
kurrentdb --remove-dev-certs
```

This does not delete certificates written to a `--dev-cert-path` file - remove those manually.

### Set initial passwords

You can set the admin and ops passwords on the first run of the database. This will not impact the existing credentials, the user can log into their accounts with existing passwords. Additionally, this will only take effect if you are using [basic authentication](./user-authentication.md#basic-authentication).

Set the password for the `admin` user with the `DefaultAdminPassword` option:

| Format               | Syntax                              |
|:---------------------|:------------------------------------|
| Environment variable | `KURRENTDB_DEFAULT_ADMIN_PASSWORD`  |

**Default**: `changeit`

And set the password for the `ops` user with the `DefaultOpsPassword` option:

| Format               | Syntax                              |
|:---------------------|:------------------------------------|
| Environment variable | `KURRENTDB_DEFAULT_OPS_PASSWORD`    |

**Default**: `changeit`

::: warning
Due to security reasons the `DefaultAdminPassword` and `DefaultOpsPassword` options can only be set through environment variables. The user will receive the error message if they try to pass the options using command line or config file.
:::

### Anonymous access to streams

Historically, anonymous users with network access have been allowed to read/write streams that do not have access control lists.

This is now disabled by default but can be enabled by setting `AllowAnonymousStreamAccess` to `true`.

| Format               | Syntax                                     |
|:---------------------|:-------------------------------------------|
| Command line         | `--allow-anonymous-stream-access`          |
| YAML                 | `AllowAnonymousStreamAccess`               |
| Environment variable | `KURRENTDB_ALLOW_ANONYMOUS_STREAM_ACCESS`  |

**Default**: `false`

### Anonymous access to endpoints

Similarly to streams above, anonymous access has historically been available to some http endpoints.

Anonymous access to `/gossip`, `/stats` and the `HTTP OPTIONS` method can now be configured with the following two options. By default `/gossip` is still accessible anonymously but the others are not. Some clients currently rely on anonymous access to `/gossip`. This will likely change in the future.

The `AllowAnonymousEndpointAccess` option controls anonymous access to these endpoints. Setting `OverrideAnonymousEndpointAccessForGossip` to `true` allows anonymous access to `/gossip` specifically, overriding the other option.

| Format               | Syntax                                       |
|:---------------------|:---------------------------------------------|
| Command line         | `--allow-anonymous-endpoint-access`          |
| YAML                 | `AllowAnonymousEndpointAccess`               |
| Environment variable | `KURRENTDB_ALLOW_ANONYMOUS_ENDPOINT_ACCESS`  |

**Default**: `false`

| Format               | Syntax                                                     |
|:---------------------|:-----------------------------------------------------------|
| Command line         | `--override-anonymous-endpoint-access-for-gossip`          |
| YAML                 | `OverrideAnonymousEndpointAccessForGossip`                 |
| Environment variable | `KURRENTDB_OVERRIDE_ANONYMOUS_ENDPOINT_ACCESS_FOR_GOSSIP`  |

**Default**: `true`

::: tip
Anonymous access is still always granted to `/ping`, `/info`, the static content of the UI, and http redirects.
:::

## FIPS 140-2 

KurrentDB runs on FIPS 140-2 enabled operating systems.

The Federal Information Processing Standards (FIPS) of the United States are a set of publicly announced standards that the National Institute of Standards and Technology (NIST) has developed for use in computer systems of non-military United States government agencies and contractors.

The 140 series of FIPS (FIPS 140) are U.S. government computer security standards that specify requirements for cryptography modules.

To run KurrentDB on FIPS 140-2 enabled operating systems, the following is required:

1. The node's certificates & keys must be FIPS 140-2 compatible
    - Our certificate generation tool, [es-gencert-cli](https://github.com/EventStore/es-gencert-cli/releases), generates FIPS 140-2 compatible certificates & keys as from version 1.2.0 (only the linux build).
    - If you want to manually generate your certificates or keys with openssl, you must use openssl 3 or later.
    - If you want to use PKCS #12 bundles (.p12/.pfx extension), you must use a FIPS 140-2 compatible encryption algorithm (e.g AES-256) & hash algorithm (e.g SHA-256) to encrypt the bundle's contents.

Note that KurrentDB will also likely run properly on FIPS 140-3 compliant operating systems with the above steps but this cannot reliably be confirmed at the moment as FIPS 140-3 certification is still ongoing for the operating systems themselves.

## Encryption-At-Rest



The Encryption-At-Rest feature allows users to encrypt their KurrentDB database. Currently, only chunk files are encrypted - the indexes are not. The primary objective is to protect against an attacker who obtains access to the physical disk. In contrast to volume or filesystem encryption, file level encryption provides some degree of protection for attacks against the live system or remote exploits as the plaintext data is not directly readable. Protecting against memory-dump based attacks is out of the scope of this feature.

You require a [license key](../quick-start/installation.md#license-keys) to use this feature.

### Encryption Algorithm

Data is encrypted using a symmetric-key encryption algorithm.


The feature is designed to support different encryption algorithms. Currently, the following encryption algorithms are implemented:
- `AesGcm` - Advanced Encryption Standard (AES) Galois Counter Mode (GCM)
  - The default key size is 256 bits, but 128-bit and 192-bit keys are also supported.

### Master Key

The `master` key is the topmost key in the key hierarchy.

The size of the master key is not defined but it needs to be long enough (e.g at least 128 bits) to provide reasonable security.

Using a key-derivation function (HKDF in our case), the master key is used to derive multiple `data` keys. Each `data` key is used to encrypt a single chunk file. If the `master` key is compromised, the whole database can be decrypted. However, if a `data` key is compromised, only one chunk file can be decrypted.

Usually, only one `master` key should be necessary. However, to cater for scenarios where a `master` key is compromised, the feature supports loading multiple `master` keys. The latest `master` key is actively used to encrypt/decrypt new chunks, while old `master` keys are used only to decrypt old chunks - thus the new data is secure.

### Master Key Source

The function of a master key source is to load master keys.

Master keys are loaded from the source only once at process startup. They are then kept in memory throughout the lifetime of the process.

The feature is designed to support different master key sources. Currently, the following master key sources are implemented:
- `File` - loads master keys from a directory
  - The master key can be generated using [es-cli](https://cloudsmith.io/~eventstore/repos/eventstore/packages/?q=es-cli) with the command: `es-cli encryption generate-master-key`
  - For proper security, the master key files must be located on a drive other than where the database files are.
  - This master key source is not recommended to be used in production as it provides minimal protection.

You can contact Kurrent if you want us to support a particular master key source.

### Configuration

Refer to the [configuration guide](../configuration/README.md) for configuration mechanisms other than YAML.

Sample configuration:

```yaml
Transform: aes-gcm

EncryptionAtRest:
  Enabled: true
  MasterKey:
    File:
      KeyPath: /path/to/keys/
  Encryption:
    AesGcm:
      Enabled: true
      KeySize: 256 # optional. supported key sizes: 128, 192, 256 (default)
```

When using the `File` master key source, a master key must be generated using the es-genkey-cli tool and placed in the configured `KeyPath` directory. The master key having the highest ID will be chosen as the active one.

Once the feature is enabled, the server should log messages similar to the following:

```
...
[141828, 1,11:42:45.325,INF] Encryption-At-Rest: Loaded master key source: "File"
[141828, 1,11:42:45.340,INF] Encryption-At-Rest: (File) Loaded master key: 2 (256 bits)
[141828, 1,11:42:45.340,INF] Encryption-At-Rest: (File) Loaded master key: 1 (256 bits)
[141828, 1,11:42:45.345,INF] Encryption-At-Rest: Active master key ID: 2
[141828, 1,11:42:45.345,INF] Encryption-At-Rest: Loaded encryption algorithm: "AesGcm"
[141828, 1,11:42:45.347,INF] Encryption-At-Rest: (AesGcm) Using key size: 256 bits
[141828, 1,11:42:45.401,INF] Loaded the following transforms: Identity, Encryption_AesGcm
[141828, 1,11:42:45.402,INF] Active transform set to: Encryption_AesGcm
...
```

New and scavenged chunks will be encrypted.

### Compatibility

When encryption is enabled, it's no longer possible to revert back to an unencrypted database if:
- at least one new chunk file has been created or
- at least one chunk has been scavenged

The encrypted chunks would first need to be decrypted before a user can revert to the `identity` transform. Special tooling or endpoints are not currently available to do this.

### Tutorial

[Learn how to set up and use Encryption-At-Rest in KurrentDB through a tutorial.](/tutorials/Encryption-At-Rest.md)

---

## File: docs/server/security/user-authentication.md

---
title: User authentication
order: 3
---

# Authentication

KurrentDB provides the following means to authenticate users connecting to the database:

- [Basic authentication](#basic-authentication) is enabled by default, and allows you to authenticate users with a username and password. Users are created and managed in KurrentDB.
- [User X.509 certificates](#user-x509-certificates) to authenticate users with an X.509 certificate. This is in addition to basic authentication.
- [LDAP authentication](#ldap-authentication) to authenticate users against an LDAP server such as OpenLDAP or Acive Directory.
- [OAuth authentication](#oauth-authentication) to authenticate users against an identity provider such as Auth0 or Identity Server

Once a user has been authenticated, they must be [authorized](./user-authorization.md) before they can perform actions on the database.

## Basic authentication

KurrentDB supports authentication based on usernames and passwords out of the box.

These users are created and managed within KurrentDB. So if you back up and restore a database with configured users, the same users will exist on the restored node.

### Default users

KurrentDB provides two default users, `$ops` and `$admin`.

`$admin` has full access to everything in KurrentDB. It can read and write to protected streams, which is any stream that starts with `$`, such as `$projections-$all` or `$users`. Protected streams are usually system streams, for example, `$projections-$all` manages which projections exist in KurrentDB.

The `$admin` user can also run operational commands, such as scavenges and shutdowns on KurrentDB.

An `$ops` user can do everything that an `$admin` can do except manage users and read from system streams.

### New users

New users can be created through the following means:

- The [HTTP API](../http-api/security.md#creating-users)
- The [embedded UI](../features/admin-ui.md#users)


Users are created with a username, password, and optional set of groups or roles. These roles are used for [authorization](./user-authorization.md#roles).

::: tip
Users managed in KurrentDB are always granted a role that matches their username.
:::

### Externalised authentication

You can also use the trusted intermediary header for externalized authentication that allows you to integrate
almost any authentication system with KurrentDB.

The trusted intermediary header helps KurrentDB to support a common security architecture. There are
thousands of possible methods for handling authentication and it is impossible for us to support them all. The
header allows you to configure a trusted intermediary to handle the authentication instead of KurrentDB.

A sample configuration is to enable OAuth2 with the following steps:

- Configure KurrentDB to run on the local loopback.
- Configure nginx to handle OAuth2 authentication.
- After authenticating the user, nginx rewrites the request and forwards it to the loopback to KurrentDB
  that serves the request.

The header has the form of `{user}; group, group1` and the KurrentDB ACLs use the information to handle
security.

```http:no-line-numbers
Kurrent-TrustedAuth: "root; admin, other"
```

Use the following option to enable this feature:

| Format               | Syntax                           |
|:---------------------|:---------------------------------|
| Command line         | `--enable-trusted-auth`          |
| YAML                 | `EnableTrustedAuth`              |
| Environment variable | `KURRENTDB_ENABLE_TRUSTED_AUTH`  |

## User X.509 Certificates 



The User Certificates feature allows authentication through an X.509 user certificate in addition to username and password. User certificates work across any cluster that shares a trusted root Certificate Authority (CA) with the user's certificate. This means that you can have a single user certificate that is valid across multiple clusters.

### Configuration steps

You require a [license key](../quick-start/installation.md#license-keys) to use this feature.

Refer to the [configuration guide](../configuration/README.md) for configuration mechanisms other than YAML.

Sample configuration:

```yaml
UserCertificates:
  Enabled: true
```

If the feature is enabled, the server will log the following on startup:

```
UserCertificatesPlugin: user X.509 certificate authentication is enabled
```

### Connecting with a user certificate

Use the following command as an example to connect using `curl`: 

```bash
curl -i https://localhost:2113/streams/%24all --cert user-admin.crt --key user-admin.key
```

For using X.509 user certificate with KurrentDB client from an application, refer to the client's documentation.

### Creating a user certificate

The user certificate must adhere to the following requirements:

- The certificate has a root CA in common with the node certificate.
- The root CA that they have in common is trusted by the node.
- The certificate has the ClientAuth EKU, and not the ServerAuth EKU.
- The certificate must be in date.
- The CN is the username of a user that exists in the database.

::: tip
Certificates can also authenticate `admin` and `ops` users.
:::

You can generate a user certificate with the [es-gencert-cli](https://github.com/EventStore/es-gencert-cli/):

For example, to generate a user certificate with the username `ouro`:

```bash
./es-gencert-cli.exe create-user -username ouro -ca-certificate ca.crt -ca-key ca.key -days 10
```

::: details Click to view a sample user certificate

```
/* Detailed source-code truncated for AI context efficiency. */
```

:::

### Limitations

1. Requests authenticated with user certificates cannot be forwarded between nodes. This affects Writes, Persistent Subscription operations, and Projections operations. These requests will need to be performed on the Leader node only.

2. The X.509 authentication cannot be used in conjunction with any of the other authentication plugins, such as the LDAP plugin.

### Troubleshooting

| Error                                     | Solution                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
|:------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Feature not enabled                        | The feature has to be enabled in order to authenticate user requests. The following log indicates that the feature was not enabled: `UserCertificatesPlugin is not enabled`.                                                                                                                                                                                                                                                                                                                             |
| Feature enabled and user not authenticated | If the feature has been enabled but there are still access denied errors, check the following: <li>The user exists and is enabled in the KurrentDB database. Can you log in with the username and password?</li><li>The user certificate is valid, and has a valid chain up to a trusted root CA.</li><li>The user certificate and node certificate share a common root CA.</li><li>Use 'requires leader' (which is the default) in your client configuration to rule out issues with forwarding requests.</li> |

## LDAP authentication 



The LDAP Authentication feature enables KurrentDB to use LDAP-based directory services for authentication.

### Configuration

You require a [license key](../quick-start/installation.md#license-keys) to use this feature.

Refer to the [configuration guide](../configuration/README.md) for configuration mechanisms other than YAML.

1. Change the authentication type to `ldaps`.
2. Add LDAP-specific settings in a section named `LdapsAuth`, either inline or in a separate file.

:::tabs
@tab kurrentdb.conf
```yaml
AuthenticationType: ldaps
LogFailedAuthenticationAttempts: true # for testing
LdapsAuth:
  Host: 13.88.9.49
  Port: 636 #to use plaintext protocol, set Port to 389 and UseSSL to false
  UseSSL: true
  ValidateServerCertificate: false #set this to true to validate the certificate chain
  AnonymousBind: false
  BindUser: cn=binduser,dc=mycompany,dc=local
  BindPassword: p@ssw0rd!
  BaseDn: ou=Lab,dc=mycompany,dc=local
  ObjectClass: organizationalPerson
  Filter: sAMAccountName
  RequireGroupMembership: false #set this to true to allow authentication only if the user is a member of the group specified by RequiredGroupDn
  GroupMembershipAttribute: memberOf
  RequiredGroupDn: cn=Kurrent-Users,dc=mycompany,dc=local
  PrincipalCacheDurationSec: 60
  LdapGroupRoles:
      'cn=Kurrent-Accounting,ou=Staff,dc=mycompany,dc=local': accounting
      'cn=Kurrent-Operations,ou=Staff,dc=mycompany,dc=local': it
      'cn=Kurrent-Admins,ou=Staff,dc=mycompany,dc=local': '$admins'
```
:::

Alternatively you can provide a path to a separate config file with the `AuthenticationConfig` option. For example:

:::tabs
@tab kurrentdb.conf
```yaml
AuthenticationType: ldaps
LogFailedAuthenticationAttempts: true # for testing
AuthenticationConfig: ./kurrentdb-ldaps.conf
```

@tab kurrentdb-ldaps.conf
```yaml
LdapsAuth:
  Host: 13.88.9.49
  Port: 636 #to use plaintext protocol, set Port to 389 and UseSSL to false
  UseSSL: true
  ValidateServerCertificate: false #set this to true to validate the certificate chain
  AnonymousBind: false
  BindUser: cn=binduser,dc=mycompany,dc=local
  BindPassword: p@ssw0rd!
  BaseDn: ou=Lab,dc=mycompany,dc=local
  ObjectClass: organizationalPerson
  Filter: sAMAccountName
  RequireGroupMembership: false #set this to true to allow authentication only if the user is a member of the group specified by RequiredGroupDn
  GroupMembershipAttribute: memberOf
  RequiredGroupDn: cn=Kurrent-Users,dc=mycompany,dc=local
  PrincipalCacheDurationSec: 60
  LdapGroupRoles:
      'cn=Kurrent-Accounting,ou=Staff,dc=mycompany,dc=local': accounting
      'cn=Kurrent-Operations,ou=Staff,dc=mycompany,dc=local': it
      'cn=Kurrent-Admins,ou=Staff,dc=mycompany,dc=local': '$admins'
```
:::

Upon successful LDAP authentication, users are assigned [roles](./user-authorization.md#roles) based on their domain group memberships, as specified in the `LdapGroupRoles` section.

### Troubleshooting 

If you encounter issues, check the server's log. Common problems include: 

| Error                                                                                       | Solution                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|:--------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Invalid Bind Credentials Specified                                                          | Confirm the `BindUser` and `BindPassword`.                                                                                                                                                                                                                                                                                                                                                                                                      |
| Exception During Search - 'No such Object' or 'The object does not exist'                   | Verify the `BaseDn`.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| 'Server Certificate Error' or 'Connect Error - The authentication or decryption has failed' | Verify that the server certificate is valid. If it is a self-signed certificate, set `ValidateServerCertificate` to `false`.                                                                                                                                                                                                                                                                                                                    |
| LDAP Server Unavailable                                                                     | Verify connectivity to the LDAP server from a KurrentDB node (e.g. using `netcat` or `telnet`). Verify the `Host` and `Port` parameters.                                                                                                                                                                                                                                                                                                    |
| Error Authenticating with LDAP server                                                       | Verify the `ObjectClass` and `Filter` parameters. If you have set `RequireGroupMembership` to `true`, verify that the user is part of the group specified by `RequiredGroupDn` and that the LDAP record has `GroupMembershipAttribute` set to `memberOf`.                                                                                                                                                                                       |
| No Errors in Server Logs But Cannot Login                                                   | <li>Verify that the user is part of the group specified by `RequiredGroupDn` and that the LDAP record has `GroupMembershipAttribute` set to `memberOf`.</li><li>Verify the `ObjectClass` and `Filter` parameters.</li><li>If you have set `RequireGroupMembership` to `true`, verify that the user is part of the group specified by `RequiredGroupDn` and that the LDAP record has `GroupMembershipAttribute` set to `memberOf`.</li> |

## OAuth Authentication



The OAuth feature allows KurrentDB to connect to an identity server and authenticate users based on a JWT rather than username and password.

Access tokens can contain a "role" claim, which will be used for [authorization](./user-authorization.md).

::: tip
With the default basic authentication, KurrentDB treats the username as a "role" in addition to the groups they are in. If you want to have the same behaviour with OAuth authentication, you will need to add the user's username as an additional role claim.
:::

### Configuration

You require a [license key](../quick-start/installation.md#license-keys) to use this feature.

Refer to the [configuration guide](../configuration/README.md) for configuration mechanisms other than YAML.

You can enable the OAuth plugin by setting the following options in your KurrentDB config file:
- Set the `AuthenticationType` to `oauth`
- Provide oauth configuration in a section named `OAuth`

For example:

:::tabs
@tab kurrentdb.conf
```yaml
AuthenticationType: oauth
LogFailedAuthenticationAttempts: true # for testing
OAuth:
  Audience: {audience}
  Issuer: {identity_server_endpoint}
  ClientId: {client_id}
  ClientSecret: {client_secret}
```
:::

Alternatively you can provide a path to a separate config file with the `AuthenticationConfig` option. For example:

:::tabs
@tab kurrentdb.conf
```yaml
AuthenticationType: oauth
LogFailedAuthenticationAttempts: true # for testing
AuthenticationConfig: ./kurrentdb-oauth.conf
```

@tab kurrentdb-oauth.conf
```yaml
OAuth:
  Audience: {audience}
  Issuer: {identity_server_endpoint}
  ClientId: {client_id}
  ClientSecret: {client_secret}
```
:::

The configuration options are:

| Name 			              | Type	  | Required?	| Default 	|Description |
|-------------------------|---------|-----------|-----------|------------|
| Audience 			          | string 	| Y 		| - 		| The audience used in your identity provider. |
| Issuer 			            | string 	| Y 		| - 		| The issuer endpoint for your identity provider. |
| ClientId			          | string 	| N 		| "" 		| The id of the client configured in the identity provider. |
| ClientSecret 		        | string 	| N 	  | "" 		| The client secret configured in the identity provider. |
| DisableIssuerValidation | bool 	  | N 		| false 	| Disable issuer validation for testing purposes. |
| DisableCodeChallengeMethodsSupportedValidation | bool | N | false | Disable validation of the `code_challenge_methods_supported` field in the identity provider's discovery document. Enable this when using an identity provider such as Microsoft Entra that supports PKCE but does not advertise it in the discovery document. |
| Insecure 			          | bool 	  | N 		| false 	| Whether to validate the certificates for the identity provider. This is not related to `Insecure` in the KurrentDB configuration. |

### Testing with a local identity server

You can try out the OAuth feature locally with this [Identity Server 4 docker container](https://github.com/EventStore/idsrv4). This container is not intended to be used in production.

You will first configure and run the identity server, and then configure and run KurrentDB.

1. Create a `users.conf.json` file to configure the users for your test.

::: details users.conf.json
```json
[
  {
    "subjectId": "1",
    "username": "admin",
    "password": "password",
    "claims": [{
        "type": "role",
        "value": "$admins"
      }, {
        "type": "role",
        "value": "$ops"
      }]
  }, {
    "subjectId": "2",
    "username": "operator",
    "password": "password",
    "claims": [{
      "type": "role",
      "value": "$ops"
    }]
  }, {
    "subjectId": "3",
    "username": "ouro",
    "password": "password",
    "claims": [{
      "type": "role",
      "value": "custom"
    }]
  }, {
    "subjectId": "4",
    "username": "user",
    "password": "password",
    "claims": []
  }
]
```
:::

This creates the following users:

| Username 		  | Password 		| Roles |
|---------------|-------------|-------|
| `admin`		    | `password` 	| `$admins`, `$ops` |
| `operator` 	  | `password` 	| `$ops` |
| `ouro`		    | `password`	| `custom` |
| `user`		    | `password`	| None |

2. Create an identity server config file, `idsrv4.conf.json`.

You need to configure the following:

- A role claim (`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`) must be added to the token.
- The grant types of `password` and `authorization_code` must be allowed.
- A redirect uri of `/signin-oidc` must be allowed for the embedded web UI to function.

::: details idsrv4.conf.json
```json
{
	"IdentityResources": [
		{
			"Name": "openid",
			"DisplayName": "Your user identifier",
			"Required": true,
			"UserClaims": [
				"sub",
				"role"
			]
		},
		{
			"Name": "profile",
			"DisplayName": "User profile",
			"Description": "Your user profile information (first name, last name, etc.)",
			"Emphasize": true,
			"UserClaims": [
				"name",
				"given_name",
				"middle_name",
			]
		}
	],
	"ApiResources": [
		{
			"Name": "kurrentdb-client",
			"Scopes": [
				"streams",
				"openid",
				"profile"
			]
		}
	],
	"ApiScopes": [
		{
			"Name": "streams",
			"UserClaims": [
				"http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
				"role"
			]
		}
	],
	"Clients": [
		{
			"ClientId": "kurrentdb-client",
			"AllowedGrantTypes": [
				"password",
				"authorization_code",
                "client_credentials"
			],
			"ClientSecrets": [
				{
					"Value": "{client_secret}"
				}
			],
			"AllowedScopes": [
				"streams",
				"openid",
				"profile"
			],
			"RedirectUris": ["https://localhost:2113/signin-oidc","https://127.0.0.1:2113/signin-oidc","https://localhost:2113/oauth/callback","https://127.0.0.1:2113/oauth/callback"],
			"AlwaysIncludeUserClaimsInIdToken": true,
			"RequireConsent": false,
			"AlwaysSendClientClaims": true,
			"AllowOfflineAccess": true,
			"RequireClientSecret": false,
			"AllowAccessTokensViaBrowser": true
		}
	]
}
```
:::

::: tip
In this example we set the `RedirectUris` to both `https://localhost:2113` and `https://127.0.0.1:2113` so that you can log into the UI from either address.
:::

3. Pull and run the `kurrent-io/idsrv4` docker container:

```bash
docker pull ghcr.io/kurrent-io/idsrv4/idsrv4

docker run \
    --rm -it \
    -p 5000:5000 \                                            # HTTP port
    -p 5001:5001 \                                            # HTTPS port
    --volume $PWD/users.conf.json:/etc/idsrv4/users.conf \    # mount users file; required
    --volume $PWD/idsrv4.conf.json:/etc/idsrv4/idsrv4.conf \  # mount configuration file
    ghcr.io/kurrent-io/idsrv4/idsrv4
```

You should see the identity server start up and start listening on the ports `5000` and `5001`:

```
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://[::]:5000
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://[::]:5001
```

4. Add the following configuration to your KurrentDB config file `kurrentdb.conf`:

:::tabs
@tab kurrentdb.conf
```yaml
IntIp: 127.0.0.1
ExtIp: 127.0.0.1

# Licensing and certificates configuration omitted

LogFailedAuthenticationAttempts: true # for testing
AuthenticationType: oauth
OAuth:
  Audience: kurrentdb-client
  Issuer: https://localhost:5001/
  ClientId: kurrentdb-client
  ClientSecret: {client_secret}
  # For testing
  DisableIssuerValidation: true
  Insecure: true
```
:::

5. Start KurrentDB. You should see the following logs indicating that OAuth is in use:

```
[INF] OAuthAuthenticationProvider    OAuthAuthentication 25.0.0.1673 plugin enabled.
...
[INF] OAuthAuthenticationPlugin      Obtaining auth token signing key from https://localhost:5001/
...
[INF] OAuthAuthenticationPlugin      Issuer signing keys have been retrieved. Key IDs: ["{key_id}"]
```

You should now be able to log into the UI with the configured user names and passwords.

---

## File: docs/server/security/user-authorization.md

---
title: User authorization
order: 4
---

# Authorization

Authorization governs what an authenticated user can access and what actions they can perform based on that user's roles.

## Roles

Users are granted roles by the [authentication mechanism](./user-authentication.md). These roles determine what operations, endpoints, and streams a user can access.

KurrentDB provides the `$admins` and `$ops` roles by default, granted respectively to the `admin` and `ops` default users. Custom roles can also be created.

### Admins role

The `$admins` role is granted to the `admin` user by default and authorizes the user to do the following:

- Read all streams, including `$all` and system streams.
- Manage users and reset their passwords.
- Perform all administrative operations on a node or cluster, such as scavenges, restarts, and triggering leader elections.
- Manage projections and persistent subscriptions.
- View statistics for the node, subscriptions, or projections

You can create additional admin users by assigning the `$admins` group to a user.

::: note
The `$admins` role bypasses all stream access checks such as [ACLs](#access-control-lists) or [stream policies](#stream-policy-authorization).
:::

### Ops role

The `$ops` role is granted to the `ops` user by default. This role grants similar permissions to the `$admins` role, but without the user management and stream access.

The `$ops` role authorizes the user to do the following:

- Perform maintenance operations on a node or cluster, such as scavenges, restarts, and triggering leader elections.
- Manage projections and persistent subscriptions.
- View statistics for the node, subscriptions, or projections
- Stream access is determined by the [authorization mechanism used](#stream-access)

You can create additional ops users by assigning the `$ops` group to a user.

### Custom roles

Custom roles are any role assigned to an authenticated user outside of the `$admins` and `$ops` roles.
These can be groups assigned to the user, their username, or a mapping from the authentication mechanism used. These roles can be anything, but typically describe the group or tenant a user is part of. Examples of custom roles are `accounting`, `sales`, or `tenant-123`.

Users with custom (or no) roles are authorized to do the following:

- View statistics for the node, subscriptions, or projections
- Stream access is determined by the [authorization mechanism used](#stream-access)

::: tip
Users managed in KurrentDB are always granted a role that matches their username.
:::

## Stream access

Stream access covers what actions a user can perform on a stream within KurrentDB. These actions are:

| Name              | Key   | Description |
|-------------------|-------|-------------|
| Read              | `$r`  | The permission to read from a stream. |
| Write             | `$w`  | The permission to write to a stream. |
| Delete            | `$d`  | The permission to delete a stream. |
| Metadata read     | `$mr` | The permission to read the metadata associated with a stream. |
| Metadata write    | `$mw` | The permission to write the metadata associated with a stream. |

KurrentDB supports these methods of authorizing user access to streams:
- [Access control lists](#access-control-lists) to restrict user access to streams based on metadata in each stream.
- [Stream policies](#stream-policy-authorization) to restrict user access to streams based on configured policies.

### Access control lists

By default, authenticated users can access all non-system streams in the KurrentDB database. You can use Access Control Lists (ACLs) to set up more granular access control. In fact, the default access
level is also controlled by a special ACL called the [default ACL](#default-acl).

#### Stream ACL

KurrentDB keeps the ACL of a stream in the stream metadata as JSON with the below definition:

```json
{
  "$acl": {
    "$w": "$admins",
    "$r": "$all",
    "$d": "$admins",
    "$mw": "$admins",
    "$mr": "$admins"
  }
}
```

These fields represent the following:

- `$w` The permission to write to this stream.
- `$r` The permission to read from this stream.
- `$d` The permission to delete this stream.
- `$mw` The permission to write the metadata associated with this stream.
- `$mr` The permission to read the metadata associated with this stream.

You can update these fields with either a single string or an array of strings representing users or
groups (`$admins`, `$all`, or custom groups). It's possible to put an empty array into one of these fields,
and this removes all users from that permission.

::: tip 
We recommend you don't give people access to `$mw` as then they can change the ACL.
:::

#### Default ACL

The `$settings` stream uses a special ACL as the default ACL. This stream controls the default ACL for
streams without an ACL and who can create streams in the system. The default state of these is
shown below:

```json
{
  "$userStreamAcl": {
    "$r": "$all",
    "$w": "$all",
    "$d": "$all",
    "$mr": "$all",
    "$mw": "$all"
  },
  "$systemStreamAcl": {
    "$r": "$admins",
    "$w": "$admins",
    "$d": "$admins",
    "$mr": "$admins",
    "$mw": "$admins"
  }
}
```

You can rewrite these to the `$settings` stream with the following request:

@[code{curl}](@samples/server/default-settings.sh)

The `$userStreamAcl` controls the default ACL for user streams, while all system streams use
the `$systemStreamAcl` as the default.

::: tip 
Members of `$admins` always have access to everything. You cannot remove this permission.
:::

When you set a permission on a stream, it overrides the default values. However, it's not necessary to specify
all permissions on a stream. It's only required to specify those which differ from the default.

Here is an example of the default ACL that has been changed:

```json
{
  "$userStreamAcl": {
    "$r": "$all",
    "$w": "ouro",
    "$d": "ouro",
    "$mr": "ouro",
    "$mw": "ouro"
  },
  "$systemStreamAcl": {
    "$r": "$admins",
    "$w": "$admins",
    "$d": "$admins",
    "$mr": "$admins",
    "$mw": "$admins"
  }
}
```

This default ACL gives `ouro` and `$admins` create and write permissions on all streams while everyone else
can read from them. Be careful when allowing default access to system streams to non-admins, as they would also have
access to `$settings` unless you specifically override it.

Refer to the documentation of the HTTP API or SDK of your choice for more information about changing ACLs
programmatically.

### Stream policy authorization



This allows administrators to define stream access policies for KurrentDB based on stream prefixes.

#### Enabling

Using this feature requires a [license key](../quick-start/installation.md#license-keys).

The last event in the `$authorization-policy-settings` stream configures the stream access policy.

Enable stream policies by appending an event of type `$authorization-policy-changed` to the `$authorization-policy-settings` stream:

::: tabs
@tab HTTP
```http
POST https://localhost:2113/streams/$authorization-policy-settings
Authorization: Basic admin changeit
Content-Type: application/json
Kurrent-EventId: 11887e82-9fb4-4112-b937-aea895b32a4a
Kurrent-EventType: $authorization-policy-changed

{
    "streamAccessPolicyType": "streampolicy"
}
```

@tab Powershell
```powershell
$JSON = @"
{
    "streamAccessPolicyType": "streampolicy"
}
"@ `

curl.exe -X POST `
  -H "Content-Type: application/json" `
  -H "Kurrent-EventId: 11887e82-9fb4-4112-b937-aea895b32a4b" `
  -H "Kurrent-EventType: `$authorization-policy-changed" `
  -u "admin:changeit" `
  -d $JSON `
  https://localhost:2113/streams/%24authorization-policy-settings
```

@tab Bash
```bash
JSON='{
    "streamAccessPolicyType": "streampolicy"
}'

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Kurrent-EventId: 814f6d67-515c-4bd6-a6d6-8a0235ec719a" \
  -H "Kurrent-EventType: \$authorization-policy-changed" \
  -u "admin:changeit" \
  -d "$JSON" \
  https://localhost:2113/streams/%24authorization-policy-settings
```
:::

And disable stream policies by setting the stream access policy type back to `acl`:

::: tabs
@tab HTTP

```http
### Configure cluster to use acls
POST https://localhost:2113/streams/$authorization-policy-settings
Authorization: Basic admin changeit
Content-Type: application/json
Kurrent-EventId: d07a6637-d9d0-43b7-8f48-6a9a8800af12
Kurrent-EventType: $authorization-policy-changed

{
    "streamAccessPolicyType": "acl"
}
```

@tab Powershell
```Powershell
$JSON = @"
{
    "streamAccessPolicyType": "acl"
}
"@ `

curl.exe -X POST `
  -H "Content-Type: application/json" `
  -H "Kurrent-EventId: 3762c89e-d12e-4a01-83bb-781459c93ebc" `
  -H "Kurrent-EventType: `$authorization-policy-changed" `
  -u "admin:changeit" `
  -d $JSON `
  https://localhost:2113/streams/%24authorization-policy-settings
```
@tab Bash
```bash
JSON='{
    "streamAccessPolicyType": "acl"
}'

curl -X POST \
  -H "Content-Type: application/json" \
  -H "Kurrent-EventId: d6b3a316-2493-4bcd-9019-e02b5ae4eec3" \
  -H "Kurrent-EventType: \$authorization-policy-changed" \
  -u "admin:changeit" \
  -d "$JSON" \
  https://localhost:2113/streams/%24authorization-policy-settings
```

:::

When stream policies are enabled, you should see the following logs:

You can check that the feature is enabled by searching for the following log at startup:

```
[22860,28,17:30:42.247,INF] StreamBasedAuthorizationPolicyRegistry New Authorization Policy Settings event received
[22860,28,17:30:42.249,INF] StreamBasedAuthorizationPolicyRegistry Starting factory streampolicy
...
[22860,28,17:30:42.264,INF] StreamBasedPolicySelector      Existing policy found in $policies stream (0)
[22860,28,17:30:42.269,INF] StreamBasedPolicySelector      Successfully applied policy
[22860,28,17:30:42.271,INF] StreamBasedPolicySelector      Subscribing to $policies at 0
```

Stream policies will log the following error and not start if you do not have a valid license. KurrentDB will continue to run with the current policy type:

```
[22928, 1,17:31:56.879,INF] StreamPolicySelector           Stream Policies plugin is not licensed, stream policy authorization may not be enabled.
...
[22928,28,17:31:57.030,ERR] StreamPolicySelector           Stream Policies plugin is not licensed, cannot enable Stream policies
[22928,28,17:31:57.030,ERR] StreamBasedAuthorizationPolicyRegistry Failed to enable policy selector plugin streampolicy. Authorization settings will not be applied
```

::: note
If you enable the Stream Policy feature, KurrentDB will not enforce [stream ACLs](#access-control-lists).
:::

#### Overriding the default

If the `$authorization-policy-settings` stream is empty and there is no configuration, KurrentDB will default to using ACLs.

If you would rather default to stream policies, you can set the `Authorization:DefaultPolicyType` option to `streampolicy`.

::: note
Any events in the `$authorization-policy-settings` stream take precedence over the `Authorization:DefaultPolicyType` setting.
:::

You can do this by adding the following to the server configuration file:

```yaml
Authorization:
  DefaultPolicyType: streampolicy
```

Refer to the [configuration guide](../configuration/README.md) for configuration mechanisms other than YAML.

::: warning
If the policy type configured in `Authorization:DefaultPolicyType` is not present at startup, KurrentDB will not start.
:::

#### Fallback stream access policy

If none of the events in the `$authorization-policy-settings` stream are valid, or if the stream policy plugin cannot be started (for example, due to it being unlicensed), KurrentDB will fall back to restricted access.

This locks down all stream access to admins only to prevent KurrentDB from returning to a more permissive policy such as ACLs.

To recover from this, either fix the issue preventing the plugin from loading or set the stream access policy type to a different policy such as ACL:

```json
{
    "streamAccessPolicyType": "acl"
}
```

#### Stream policies and rules

Stream policies and Stream Rules are configured by events written to the `$policies` stream.

##### Default stream policy

KurrentDB creates default stream policies by adding a default policy event in the `$policies` stream when the feature is enabled for the first time. The default policies specified in that event include:

- Restricts [system streams](../features/streams.md#system-events-and-streams) to the `$admins` group.
- Grants users outside of the `$ops` group access to user streams.
- Grants users outside of the `$ops` group read access to the [default projection streams](../features/streams.md#projections-events-and-streams) (`$ce`, `$et`, `$bc`, `$category`, `$streams`) and their metadata.

KurrentDB will log when the default policy is created:

```
[31124,16,11:18:15.099,DBG] StreamBasedPolicySelector Successfully wrote default policy to stream $policies.
```

::: details Click here to see the default policy

```json
{
  "streamPolicies": {
    "publicDefault": {
      "$r": ["$all"],
      "$w": ["$all"],
      "$d": ["$all"],
      "$mr": ["$all"],
      "$mw": ["$all"]
    },
    "adminsDefault": {
      "$r": ["$admins"],
      "$w": ["$admins"],
      "$d": ["$admins"],
      "$mr": ["$admins"],
      "$mw": ["$admins"]
    },
    "projectionsDefault": {
      "$r": ["$all"],
      "$w": ["$admins"],
      "$d": ["$admins"],
      "$mr": ["$all"],
      "$mw": ["$admins"]
    }
  },
  "streamRules": [
    {
      "startsWith": "$et-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$ce-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$bc-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$category-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$streams",
      "policy": "projectionsDefault"
    }
  ],
  "defaultStreamRules": {
    "userStreams": "publicDefault",
    "systemStreams": "adminsDefault"
  }
}
```

:::

::: note
Operations users in the `$ops` group are excluded from the `$all` group and do not have access to user streams by default.
:::

##### Custom stream policies

You can create a custom stream policy by writing an event with event type `$policy-updated` to the `$policies` stream.

We recommend that you start with the default policy shown above and add your own policies and stream rules to it.

Define policies by adding one or more entries to the `streamPolicies` JSON object, with the key being your policy name and the value being an [access policy](#accesspolicy):

```json
"streamPolicies": {
  "{your_policy_name}": {
    "$r": ["{roles_with_read_access}"],
    "$w": ["{roles_with_write_access}"],
    "$d": ["{roles_with_delete_access}"],
    "$mr": ["{roles_with_metadata_read_access}"],
    "$mw": ["{roles_with_metadata_write_access}"],
  },
  // Default policies truncated
}
```

For example, this policy, named `customPolicy`, grants read, write, delete, metadata read, and metadata write access to users with the `ouro` role, and only grants read access to users in the `readers` role:

```json
"streamPolicies": {
    "customPolicy": {
        "$r": ["ouro", "readers"],
        "$w": ["ouro"],
        "$d": ["ouro"],
        "$mr": ["ouro"],
        "$mw": ["ouro"]
    },
    // Default policies truncated (full version shown below)
}
```

Define which policies apply to which stream prefix by adding a [stream rule](#streamrule) entry to the `streamRules` object:

```json
"streamRules": [{
  "startsWith": "{stream_prefix}",
  "policy": "{your_policy_name}"
},
// Default stream rules truncated
]
```

For example, these stream rules apply the `customPolicy` defined above to streams starting with `account` and `customer`:

```json
"streamRules": [{
    "startsWith": "account",
    "policy": "customPolicy"
}, {
    "startsWith": "customer",
    "policy": "customPolicy"
}
// Default stream rules truncated (full version shown below)
]
```

::: tip
You can apply the same policy to multiple streams by defining multiple stream rules.
:::

Define which policies apply to user and system streams by default by updating the `defaultStreamRules` object:

```json
"defaultStreamRules": {
  "userStreams": "{default_users_policy_name}",
  "systemStreams": "{default_system_policy_name}"
}
```

For example, to keep using the system defaults:

```json
"defaultStreamRules": {
  "userStreams": "publicDefault",
  "systemStreams": "adminsDefault"
}
```

::: tip
Ensure the default policies are included in the `streamPolicies` object.
:::

To update the policies, append an event containing the JSON created in the above steps to the `$policies` stream with the `$policy-updated` event type:

::: tabs
@tab HTTP
```http
POST https://localhost:2113/streams/$policies
Authorization: Basic admin changeit
Content-Type: application/json
Kurrent-EventId: b04a64bf-4ac2-4f36-a856-6da9b63e64b7
Kurrent-EventType: $policy-updated

< ./customPolicy.json
```

@tab Powershell
```powershell
curl.exe -X POST `
  -H "Content-Type: application/json" `
  -H "Kurrent-EventId: 8bc7c581-0f07-4987-bfcb-ab8f9404ca34" `
  -H "Kurrent-EventType: `$policy-updated" `
  -u "admin:changeit" `
  -d @customPolicy.json `
  https://localhost:2113/streams/%24policies
```

@tab Bash
```bash
curl -X POST \
  -H "Content-Type: application/json" \
  -H "Kurrent-EventId: cb6a722d-c775-4831-929b-86870560c68e" \
  -H "Kurrent-EventType: \$policy-updated" \
  -u "admin:changeit" \
  -d @customPolicy.json \
  https://localhost:2113/streams/%24policies

```

@tab customPolicy.json
```json
{
  "streamPolicies": {
    "customPolicy": {
      "$r": ["ouro", "readers"],
      "$w": ["ouro"],
      "$d": ["ouro"],
      "$mr": ["ouro"],
      "$mw": ["ouro"]
    },
    "publicDefault": {
      "$r": ["$all"],
      "$w": ["$all"],
      "$d": ["$all"],
      "$mr": ["$all"],
      "$mw": ["$all"]
    },
    "adminsDefault": {
      "$r": ["$admins"],
      "$w": ["$admins"],
      "$d": ["$admins"],
      "$mr": ["$admins"],
      "$mw": ["$admins"]
    },
    "projectionsDefault": {
      "$r": ["$all"],
      "$w": ["$admins"],
      "$d": ["$admins"],
      "$mr": ["$all"],
      "$mw": ["$admins"]
    }
  },
  "streamRules": [
    {
      "startsWith": "account",
      "policy": "customPolicy"
    },
    {
      "startsWith": "customer",
      "policy": "customPolicy"
    },
    {
      "startsWith": "$et-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$ce-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$bc-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$category-",
      "policy": "projectionsDefault"
    },
    {
      "startsWith": "$streams",
      "policy": "projectionsDefault"
    }
  ],
  "defaultStreamRules": {
    "userStreams": "publicDefault",
    "systemStreams": "adminsDefault"
  }
}
```

:::

::: note
If a policy update is invalid, it will not be applied, and an error will be logged. KurrentDB will continue running with the previous valid policy in place.
:::

##### Stream policy schema

###### Policy

| Key                   | Type                                  | Description | Required |
|-----------------------|---------------------------------------|-------------|----------|
| `streamPolicies`      | `Dictionary<string, AccessPolicy>`    | The named policies which can be applied to streams. | Yes |
| `streamRules`         | `StreamRule[]`                        | An array of rules to apply for stream access.  The ordering is important, and the first match wins. | Yes |
| `defaultStreamRules`  | `DefaultStreamRules`                  | The default policies to apply if no other policies are defined for a stream | Yes |

###### AccessPolicy

| Key   | Type	    | Description	| Required |
|-------|-----------|---------------|----------|
| `$r`  | string[]  | User and group names that are granted stream read access. | Yes |
| `$w`  | string[]  | User and group names that are granted stream write access. | Yes |
| `$d`  | string[]  | User and group names that are granted stream delete access. | Yes |
| `$mr` | string[]  | User and group names that are granted metadata stream read access. | Yes |
| `$mw` | string[]  | User and group names that are granted metadata stream write access. | Yes |

::: note
Having metadata read or metadata write access to a stream does not grant read or write access to that stream.
:::

###### DefaultStreamRules

|Key                | Type           | Description   | Required |
|-------------------|----------------|---------------|----------|
| `userStreams`     | `AccessPolicy` | The default policy to apply for non-system streams. | Yes |
| `systemStreams`   | `AccessPolicy` | The default policy to apply for system streams. | Yes |

###### StreamRule

| Key           | Type     | Description   | Required |
|---------------|----------|---------------|----------|
| `startsWith`  | `string` | The stream prefix to which the rule is applied. | Yes |
| `policy`      | `string` | The name of the policy to enforce for streams that match this prefix. | Yes |


#### Tutorial

[Learn how to set up and use Stream Policy Authorization in KurrentDB through a tutorial.](/tutorials/Stream_Policy_Authorization.md)


#### Troubleshooting

##### License is invalid or not found

This feature requires a license to use. If a license is not found or the provided license is invalid, you will see the following log:

```
[INF] StreamPolicySelector           Stream Policies plugin is not licensed, stream policy authorization cannot be enabled.
```

Trying to enable the feature will give you the following errors, and the previous or default policy authorization settings will be used:

```
[ERR] StreamPolicySelector           Stream Policies plugin is not licensed, cannot enable Stream policies
[ERR] StreamBasedAuthorizationPolicyRegistry Failed to enable policy selector plugin streampolicy. Authorization settings will not be applied.
```

If the `DefaultPolicyType` is set to `streampolicy`, the [fallback policy](#fallback-stream-access-policy) will be used, and stream access will be restricted to admins only:

```
[WRN] StreamBasedAuthorizationPolicyRegistry Could not load authorization policy settings. Restricting access to admins only.
```

#### The `$authorization-policy-settings` stream has been deleted

If the `$authorization-policy-settings` stream has been deleted (including hard deleted), the default policy type will be used, and KurrentDB will log the following:

```
[WRN] StreamBasedAuthorizationPolicyRegistry Authorization policy settings stream $authorization-policy-settings has been deleted.
[INF] KurrentDB                     No existing authorization policy settings were found in $authorization-policy-settings. Using the default
```

#### New authorization settings could not be applied

If an invalid update is made to the `$authorization-policy-changed` stream, you will see the following log and the settings will not be updated.

```
[WRN] StreamBasedAuthorizationPolicyRegistry New authorization settings could not be applied. Settings were not updated.
```

This can happen because:

1. The event type is not `$authorization-policy-changed`:

```
[ERR] StreamBasedAuthorizationPolicyRegistry Invalid authorization policy settings event. Expected event type $authorization-policy-changed but got {invalid_event_type}
```

2. The event is not valid JSON:

```
[ERR] StreamBasedAuthorizationPolicyRegistry Could not parse authorization policy settings
```

3. The stream access policy type was not found. Ensure that the policy type is set to `acl` or `streampolicy`:

```
[ERR] StreamBasedAuthorizationPolicyRegistry Could not find policy not-found in registered authorization policy plugins.
```

#### Could not parse policy

If an invalid update is made to the `$policies` stream, you will see the following log and the policies will not be updated.

```
[ERR] StreamBasedPolicySelector      Could not parse policy
```

This can happen because:

1. A stream rule references a policy that does not exist. Ensure that the policy is defined in the [streamPolicies](#custom-stream-policies) json object:

```
[ERR] StreamPolicySelector           Stream rule for prefix: {stream_prefix} refers to an undefined policy: {not_found_policy}
```

2. An [access policy](#accesspolicy) is missing a key. Ensure that each access key (`$r`, `$w`, `$d`, `$mr`, `$md`) is defined:

```
[ERR] StreamPolicySelector           Error while deserializing new policy
System.ArgumentNullException: Value cannot be null. (Parameter 'Deleters cannot be null')
```

3. A [stream rule](#streamrule) has an empty prefix:

```
[ERR] StreamPolicySelector           Error while deserializing new policy
System.ArgumentNullException: Value cannot be null. (Parameter 'StartsWith cannot be null or empty')
```

4. The event type is not `$policy-updated`:

```
[ERR] StreamPolicySelector           Expected event type: $policy-updated but was {invalid_event_type}
```

---

## File: docs/server/release-schedule/previous-versions.md

---
order: 99
---

# Previous versions

| Version | Release Date     | End of Support  | Status         | Release Notes                                                                                      |
|:--------|:-----------------|:----------------|:---------------|:---------------------------------------------------------------------------------------------------|
| 26.0    | 16 January  2026 | > January 2028  | LTS, Latest    | [Release Notes](https://docs.kurrent.io/server/v26.0/release-schedule/release-notes)               |
| 25.1    | 15 October  2025 |   January 2026  | Out of support | [Release Notes](https://docs.kurrent.io/server/v25.1/release-schedule/release-notes)               |
| 25.0    | 25 March    2025 |   October 2025  | Out of support | [Release Notes](https://docs.kurrent.io/server/v25.0/release-schedule/release-notes)               |
| 24.10   | 20 November 2024 | > November 2026 | LTS            | [Release Notes](https://docs.kurrent.io/server/v24.10/release-schedule/release-notes)              |
| 24.6    | 26 June     2024 |   October 2024  | Out of support | [Release Notes](https://docs.kurrent.io/server/v24.10/release-schedule/release-notes.html#_24-6-0) |
| 24.2    | 29 February 2024 |   June    2024  | Out of support | [Release Notes](https://docs.kurrent.io/server/v24.10/release-schedule/release-notes.html#_24-2-0) |
| 23.10   | 13 October  2023 |   October 2025  | Out of support | [Release Notes](https://docs.kurrent.io/server/v23.10/release-schedule/release-notes)              |
| 23.6    | 11 August   2023 |   October 2023  | Out of support | [Release Notes](https://docs.kurrent.io/server/v23.10/release-schedule/release-notes.html#_23-6-0) |
| 22.10   | 14 November 2022 |   October 2024  | Out of support | [Release Notes](https://docs.kurrent.io/server/v22.10/release-notes.html)                          |
| 22.6    | 10 August   2022 |   October 2022  | Out of support | [Release Notes](https://docs.kurrent.io/server/v22.10/release-notes.html#_22-6-0)                  |
| 21.10   | 03 November 2021 |   October 2023  | Out of support | [Release Notes](https://docs.kurrent.io/server/v23.10/release-schedule/release-notes)              |
| 21.6    | 29 June     2021 |   October 2021  | Out of support | [Release Notes](https://github.com/EventStore/EventStore/releases/tag/oss-v21.6.0)                 |
| 21.2    | 25 February 2021 |   June    2021  | Out of support | [Release Notes](https://github.com/EventStore/EventStore/releases/tag/oss-v21.2.0)                 |
| 20.10   | 16 December 2020 |   October 2022  | Out of support | [Release Notes](https://github.com/EventStore/EventStore/releases/tag/oss-v20.10.0)                |
| 20.6    | 09 June     2020 |   October 2020  | Out of support | [Release Notes](https://github.com/EventStore/EventStore/releases/tag/oss-v20.6.0)                 |

---

## File: docs/server/release-schedule/README.md

---
dir:
  text: "Release schedule"
  order: 7
---

# Release schedule

KurrentDB production release version numbers are of the form `Major.Minor.Patch`.

* `Major`
    * Corresponds to the year in which a release was released.
* `Minor`
    * Indicates whether the release is an [LTS](#long-term-support-releases) or an [STS](#short-term-support-releases).
    * Starting with `26.0`, LTS releases have minor number `0`. Previously they had minor number `10` (e.g. `23.10`, `24.10`).
* `Patch`
    * Is incremented for bug fixes and occasionally small features.

Note that the version scheme is not `SemVer`.

Examples:

| Version  | Type | Description              |
|----------|------|--------------------------|
| `26.0.0` | LTS  | 2026 LTS release.        |
| `26.0.1` | LTS  | A patch to `26.0.0`.     |
| `26.1.0` | STS  | First 2026 STS release.  |
| `26.2.0` | STS  | Second 2026 STS release. |
| `26.2.1` | STS  | A patch to `26.2.0`.     |
| `27.0.0` | LTS  | 2027 LTS release.        |

## Long term support releases

There will be one LTS release of KurrentDB per year, released in or around January.

These versions will be supported for a minimum of two years, with a two month grace period for organizing upgrades when the LTS goes out of support.

## Short term support releases

STS releases will be published as new features are added to KurrentDB.

These versions will be supported until the next STS or LTS release of KurrentDB.

There are typically one or two STS releases per year.

---

## File: docs/server/release-schedule/release-notes.md

---
order: 1
---

# Release notes

This page contains the release notes for KurrentDB v26.2.

## [26.2.0](https://github.com/kurrent-io/KurrentDB/releases/tag/v26.2.0)

Day Month 2026

### What's new

Find out [what's new](../quick-start/whatsnew.md) in this release.

---

## File: docs/server/quick-start/default-directories.md

---
order: 4
---

# Default directories

The default directories used by KurrentDB vary by platform to fit with the common practices each platform.

## Linux

When you install KurrentDB on Linux, the following locations apply:

- **Application:** `/usr/bin`
- **Content:** `/usr/share/kurrentdb`
- **Configuration:** `/etc/kurrentdb/`
- **Data:** `/var/lib/kurrentdb`
- **Server logs:** `/var/log/kurrentdb`
- **Test client logs:** `./testclientlog`
- **Web content:** `./clusternode-web` then `{Content}/clusternode-web`
- **Projections:** `./projections` then `{Content}/projections`
- **Prelude:** `./Prelude` then `{Content}/Prelude`

## Windows

- **Configuration:** `./`
- **Data:** `./data`
- **Server logs:** `./logs`
- **Test client log:** `./testclientlogs`
- **Web content:** `./clusternode-web`
- **Projections:** `./projections`
- **Prelude:** `./Prelude`

## Local binaries

When running KurrentDB using local binaries, either downloaded or built from source, the server will use its location and place the necessary files inside it:

- **Configuration:** `./`
- **Data:** `./data`
- **Server logs:** `./logs`
- **Test client log:** `./testclientlogs`
- **Web content:** `./clusternode-web`
- **Projections:** `./projections`
- **Prelude:** `./Prelude`

Depending on the platform and installation type, the location of KurrentDB executables, configuration and other necessary files vary.