Databases
By default, the server runs using a SQLite database, but it can be configured to use a choice of databases: SQLite encrypted, PostgreSQL, MariaDB, MySQL, and Percona XtraDB Cluster, as well as Redis, using the Redis-with-Findex configuration.
Selecting the database
All databases, except SQLite, can be used in a high-availability setup.
The SQLite database can serve high loads and millions of objects, and is very suitable for scenarios that do not demand high availability.
Redis with Findex
Redis with Findex offers the ability to use Redis as a database with application-level encryption: all data is encrypted (using AES 256 GCM) by the KMS servers before being sent to Redis. Findex is an Eviden cryptographic algorithm used to build encrypted indexes on encrypted data, also stored in Redis. This allows the KMS to perform fast encrypted queries on encrypted data. Redis with Findex offers post-quantum resistance on encrypted data and encrypted indexes.
Redis-with-Findex is most useful when:
- KMS servers are run inside a confidential VM or an enclave. In this case, the secret used to encrypt the Redis data and indexes, is protected by the VM or enclave and cannot be recovered at runtime by inspecting the KMS servers' memory.
- KMS servers are run by a trusted party but the Redis backend is managed by an untrusted third party.
Redis-with-Findex is the database selected to run the Eviden KMS in the cloud or any other zero-trust environment.
Configuring the database
The database parameters may be configured either:
- the TOML configuration file
- or the arguments passed to the server on the command line.
SQLite
This is the default configuration. To use SQLite, no additional configuration is needed.
[db]
database_type = "sqlite"
sqlite_path = "./sqlite-data"
PostgreSQL
[db]
database_type = "postgresql"
database_url = "postgres://kms_user:kms_password@pgsql-server:5432/kms"
Before running the server, a dedicated database with a dedicated user should be created on the PostgreSQL instance.
These sample instructions create a database called kms owned by a user kms_user with password kms_password:
-
Connect to psql under user
postgressudo -u postgres psql # or `psql -U postgres` -
Create user
kms_userwith passwordkms_passwordcreate user kms_user with encrypted password 'kms_password'; -
Create database
kmsunder ownerkms_usercreate database kms owner=kms_user;
PostgreSQL High-Availability (multi-host)
The KMS supports multi-host PostgreSQL connection strings for high-availability deployments
(streaming replication, Patroni, pgBouncer clusters, etc.). The database-url value is treated
as a raw string so that host1:port,host2:port syntax — which the standard URL parser cannot
handle — is passed directly to the PostgreSQL driver.
Use the target_session_attrs query parameter to control which node the driver connects to:
| Value | Behaviour |
|---|---|
read-write | Connect only to the primary (default for HA) |
any | Connect to any available node (suitable for read replicas) |
read-only | Connect only to a standby |
The driver tries hosts left to right in the URL. With target_session_attrs=read-write,
standbys are automatically skipped (they return transaction_read_write=off), so the primary
is always found regardless of its position. However, put the expected primary first to
avoid an unnecessary round-trip to the standby on every new connection under normal
conditions.
Example — two-node HA cluster (primary listed first):
[db]
database_type = "postgresql"
database_url = "postgresql://kms_user:kms_password@primary:5432,standby:5432/kms?target_session_attrs=read-write"
The URL must start with postgresql:// or postgres://; any other scheme is rejected at
startup.
Replication and standby writability
In standard PostgreSQL streaming replication, the standby node operates in hot standby
mode: it accepts read queries but rejects all writes. The KMS requires a read-write
connection for every operation, so target_session_attrs=read-write will skip a standby
automatically — but this also means the KMS cannot use the standby as a fallback by
itself. The standby only becomes usable by the KMS after it has been promoted to
primary.
Replication is entirely managed by the PostgreSQL infrastructure — the KMS does not configure, trigger, or monitor it.
Recommended setup: use an HA manager
For automatic failover, deploy an HA manager such as Patroni, pg_auto_failover, or repmgr. These tools:
- Monitor the primary continuously.
- Promote the standby when the primary is unreachable.
- Demote the old primary back to standby automatically when it recovers (Patroni uses
pg_rewindto resync it with the new primary's WAL timeline).
The KMS URL requires no change: with target_session_attrs=read-write the driver
always connects to whichever node is currently the primary.
What happens when the primary goes down then comes back up?
| Phase | State | KMS behaviour |
|---|---|---|
| Primary down, standby still read-only | No writable node exists | KMS retries exhaust (≤ ~3.2 s), requests fail with a database error |
| HA manager promotes the standby | Standby becomes new primary | KMS reconnects on next retry/request, normal operation resumes |
| Old primary recovers | HA manager demotes it to new standby | Transparent; KMS skips it (read-only) and connects to the new primary |
| Old primary recovers without an HA manager | Old primary restarts as a standalone node — data divergence risk | Manual intervention required before reconnecting the KMS |
After a permanent failover without an HA manager, the roles of primary:5432 and
standby:5432 are reversed. The KMS still works (it finds the read-write node), but it
tries the old-primary address first on every connection, adding latency. To avoid this,
front the cluster with a virtual IP (Keepalived, AWS RDS Multi-AZ endpoint, Azure
Flexible Server read-write endpoint, GCP Cloud SQL HA endpoint) and use a single-host
URL — failover then becomes completely transparent to the KMS.
PostgreSQL failover retry
When a PostgreSQL primary fails over, the driver may return transient connection errors before the new primary is ready. The KMS automatically retries failed queries with exponential backoff for the following PostgreSQL SQLSTATE codes:
| SQLSTATE | Meaning |
|---|---|
08001 | sqlclient_unable_to_establish_sqlconnection |
08004 | sqlserver_rejected_establishment_of_sqlconnection |
57P02 | crash_shutdown |
57P03 | cannot_connect_now |
No additional configuration is required; the retry behaviour is enabled automatically for all PostgreSQL connections.
MySQL, MariaDB, or Percona XtraDB Cluster
The KMS supports MySQL-compatible databases including MySQL, MariaDB, and Percona XtraDB Cluster.
All use the same configuration with database-type=mysql.
As of version 5.13.0, the KMS schema includes PRIMARY KEY constraints on all tables, making it fully compatible with:
- Percona XtraDB Cluster (with
pxc_strict_mode=ENFORCING) - MariaDB Galera Cluster
- Any MySQL clustering solution requiring PRIMARY KEYs for replication
[db]
database_type = "mysql"
database_url = "mysql://kms_user:kms_password@mysql-server:3306/kms"
Use a certificate to authenticate to MySQL or MariaDB with the `mysql-user-cert-file` option to
specify the certificate file name.
**Example context**: say the certificate is called `cert.p12`
and is in a directory called `/certificate` on the host disk.
docker run --rm -p 9998:9998 \
--name kms ghcr.io/cosmian/kms:latest \
-v /certificate/cert.p12:/root/cosmian-kms/cert.p12 \
--database-type=mysql \
--database-url=mysql://mysql_server:3306/kms \
--mysql-user-cert-file=cert.p12
Redis with Findex
For Redis with Findex, the --redis-master-password and --redis-findex-label options must also be specified:
- The
redis-master-passwordis the password from which keys will be derived (using Argon 2) to encrypt the Redis data and indexes. - The
redis-findex-labelis a public, arbitrary label that can be changed to rotate the Findex ciphertexts without changing the password/key.
[db]
database_type = "redis-findex"
database_url = "redis://localhost:6379"
redis_master_password = "password"
redis_findex_label = "label"
- Redis (with-Findex), use:
Securing database connections with TLS / mTLS
The KMS supports TLS-encrypted connections and mutual TLS (mTLS) client-certificate authentication
for PostgreSQL and MySQL-compatible databases. All TLS parameters are configured directly in the
database-url as query parameters — no extra CLI flags or TOML keys are needed.
PostgreSQL TLS / mTLS
PostgreSQL TLS is configured using the standard libpq-style query parameters in the connection URL.
| Parameter | Description |
|---|---|
sslmode | TLS mode: disable, prefer (default), require, verify-ca, verify-full |
sslrootcert | Path to the CA certificate (PEM) used to verify the server |
sslcert | Path to the client certificate (PEM) for mTLS |
sslkey | Path to the client private key (PEM) for mTLS |
Server-authenticated TLS only (encrypt the connection and verify the server certificate):
[db]
database_type = "postgresql"
database_url = "postgres://kms:kms@pgsql-server:5432/kms?sslmode=verify-ca&sslrootcert=/path/to/ca.crt"
Mutual TLS (mTLS) (encrypt + verify server certificate + present a client certificate):
[db]
database_type = "postgresql"
database_url = "postgres://kms:kms@pgsql-server:5432/kms?sslmode=verify-full&sslrootcert=/path/to/ca.crt&sslcert=/path/to/client.crt&sslkey=/path/to/client.key"
disable– no TLS at all.prefer(default) /require– TLS is used but the server certificate is not verified.verify-ca– the server certificate is verified against the CA but the hostname is not checked.verify-full– the server certificate is verified against the CA and the hostname must match.
All certificates must be in PEM format.
MySQL / MariaDB TLS / mTLS
MySQL TLS is configured using query parameters in the connection URL.
Both dash (ssl-mode) and underscore (ssl_mode) variants are accepted.
| Parameter | Description |
|---|---|
ssl-mode | TLS mode: DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY |
ssl-ca | Path to the CA certificate (PEM) for server verification |
ssl-client-identity | Path to the client PKCS#12 (.p12) bundle for mTLS |
ssl-client-identity-password | Password protecting the PKCS#12 bundle |
Server-authenticated TLS only (encrypt the connection and verify the server certificate):
[db]
database_type = "mysql"
database_url = "mysql://kms:kms@mysql-server:3306/kms?ssl-mode=VERIFY_CA&ssl-ca=/path/to/ca.crt"
Mutual TLS (mTLS) (encrypt + verify server certificate + present a client certificate):
[db]
database_type = "mysql"
database_url = "mysql://kms:kms@mysql-server:3306/kms?ssl-mode=VERIFY_CA&ssl-ca=/path/to/ca.crt&ssl-client-identity=/path/to/client.p12&ssl-client-identity-password=secret"
DISABLED– no TLS at all.PREFERRED/REQUIRED– TLS is used but the server certificate is not verified.VERIFY_CA– the server certificate is verified against the CA.VERIFY_IDENTITY– the server certificate is verified against the CA and the hostname must match.
MySQL client-certificate authentication requires the certificate and private key bundled as a
PKCS#12 (.p12) file — PEM files are not supported.
You can create the bundle using OpenSSL:
openssl pkcs12 -export \
-in client.crt -inkey client.key \
-out client.p12 -passout pass:secret
PKCS#12 client identity (ssl-client-identity) is not available in FIPS mode.
MySQL mTLS with client certificates requires the non-fips feature.
Clearing the database
The KMS server can be configured to clear the database on restart automatically.
The cleanup operation will delete all objects and keys stored in the database.
[db]
clear_database = true
Database migration
Depending on the KMS database evolution, a migration can happen between 2 versions of the KMS server. It will be clearly written in the CHANGELOG.md. In that case, a generic database upgrade mechanism is run on startup.
At first, the table context is responsible for storing the software run's version and the database's state.
The state can be one of the following:
ready: the database is ready to be usedupgrading: the database is being upgraded
On startup, the server checks if the software version is greater than the last version run:
-
if no, it simply starts;
-
If yes:
- it looks for all upgrades to apply in order from the last version run to this version;
- if there is any to run, it sets an upgrading flag on the db state field in the context table;
- it runs all the upgrades in order.
- it sets the flag from upgrading to ready;
On every call to the database, a check is performed on the db state field to check if the database is upgrading. If yes, calls fail.
Upgrades resist being interrupted in the middle and resumed from the start if that happens.
MySQL schema update (5.13.0)
As of version 5.13.0, the MySQL schema was updated to include PRIMARY KEY constraints on the tags and read_access tables to ensure compatibility with MySQL clustering solutions (e.g., Percona XtraDB Cluster with pxc_strict_mode=ENFORCING, MariaDB Galera).
New installations of 5.13.0+ automatically create the corrected tables.
Existing installations upgrading to 5.13.0 will keep the old table definitions if those tables already exist. If you rely on clustering/replication that requires PRIMARY KEYs, apply the following manual migration before starting the KMS:
-- Fix tags table
ALTER TABLE tags
DROP INDEX id,
MODIFY id VARCHAR(128) NOT NULL,
MODIFY tag VARCHAR(255) NOT NULL,
ADD PRIMARY KEY (id, tag);
-- Fix read_access table
ALTER TABLE read_access
DROP INDEX id,
MODIFY id VARCHAR(128) NOT NULL,
MODIFY userid VARCHAR(255) NOT NULL,
ADD PRIMARY KEY (id, userid);
Notes:
- Run these statements using a privileged MySQL user (e.g.,
root). - Ensure application access is paused during the migration.
- No data loss occurs; this operation converts UNIQUE constraints to PRIMARY KEYs and enforces NOT NULL.
The Unwrapped Objects Cache
For the full technical reference on the KMS in-memory caches — architecture, public API, configuration options, and security trade-offs — see Object Cache and Unwrapped Cache.
The unwrapped cache is a memory cache, and it is not persistent. The unwrapped cache is used to store unwrapped objects that are fetched from the database.
When a wrapped object is fetched from the database, it is unwrapped and stored in the unwrapped cache. Further calls to the same object will use the unwrapped object from the cache until the cache expires.
The time in minutes after which an unused object is evicted from the cache is configurable
using the unwrapped_cache_max_age setting. The default is 15 minutes.
When HSM keys wrap objects, a long expiration time will reduce the number of calls made to HSM to unwrap the object. However, increasing the cache time will increase the memory used by the KMS server and expose the key in clear text in the memory for a longer time.