KDB knowledge

Keeping kdb serve safe

Keep the server on 127.0.0.1, use users or a token, put HTTPS in front, and pick strong passwords.

kdb serve gives full access to your data over the network: the HTTP API, the web console and the PostgreSQL port. This page says how to keep strangers out.

The short rules:

  1. Keep the default --bind 127.0.0.1. Then only programs on the same computer can connect.
  2. Create users with long passwords (see Users and roles). Then every request needs a login.
  3. For access from other computers, turn on TLS (--tls-cert: https and sslmode=require), or put a web server with HTTPS in front, or use an SSH tunnel.
  4. Keep the data folder and the backups private: whoever can read them can read everything, without a login.
ServerLogin needed?
--bind 127.0.0.1 (default), no --token, no usersNo. Only programs on this computer can connect.
the data folder has usersYes, for every request except /health, POST /api/login and the console page itself
started with --token <secret>Yes. The token works like an admin. With users too, both work.
--bind 0.0.0.0 (or any address that is not 127.x) with no token and no usersRefused: kdb serve does not start

HTTP: Is a login needed on this server?

GET /health

Response:

HTTP 200 (application/json)

{
  "status": "ok",
  "version": "0.2.0-dev",
  "server_time_ms": 1792647852082,
  "auth_required": false,
  "login": false,
  "tls": false,
  "ready": true,
  "standby": false
}

--bind

Syntax:

kdb serve [--bind 127.0.0.1 | --bind <address>] ...

The network address the server listens on. It is used for the HTTP port and the PostgreSQL port.

ValueMeaning
127.0.0.1the default. Only programs on the same computer can connect.
0.0.0.0every network card: other computers can connect too
one address, like 192.168.1.20only that network card

Command line: 0.0.0.0 without users and without a token is refused (shows an error)

kdb serve --bind 0.0.0.0

Output:

error: --bind 0.0.0.0 without --token and without users would let anyone who can reach this computer
read, change and delete every database. Do one of these first:
  1. create an admin user:   kdb user add <name> --role admin
  2. or start with a secret:  --token <long random text>
  3. or keep --bind 127.0.0.1 and put a web server with HTTPS in front of KDB
See docs/tutorial/16-users-passwords-security.md

exit code 1

HTTP: A request from another web site is refused

GET /exec?query=SELECT%201
Sec-Fetch-Site: cross-site

Response:

HTTP 403 (application/json)

{"error": "cross-site requests are not allowed"}

HTTP: A request sent by a page of another web site is refused

GET /exec?query=SELECT%201
Origin: http://evil.example.com

Response:

HTTP 403 (application/json)

{"error": "cross-origin requests are not allowed"}

--token

Syntax:

kdb serve --token <long random text>

A secret that every request must send, as Authorization: Bearer <token>. It gives full rights (like an admin on every database).

ArgumentTypeMeaning
<token>textthe secret. Use 32 or more random characters, for example from SELECT random_token().

Example: Make a strong token or password

SELECT random_token() AS token_for_kdb_serve, random_token(24) AS password_for_a_program;

Result:

token_for_kdb_serve (STRING)password_for_a_program (STRING)
F85M2XAbSxmwGbrF0DZvRFrpep6LTAHSEvtQ8qv98Awi-EQRZ2g5L08iU7_tw48SAwLjA4FAy55

(1 row)

Command line: A server with a token

kdb serve --token 3kD9vQx7LmN2pR8sT4wY6zA1bC5eF0gH

Output:

KDB 0.2.0-dev
  console   http://127.0.0.1:9900
  data      ./kdb-data (from KDB_DATA_DIR; one folder per database)
  default   default (used when a request names no database)
  backups   ./kdb-data/_backups/<database name> (from default)
  access    token required
  capacity  32 HTTP threads (default), 500 PostgreSQL connections (default), lock wait 5 s (default)
  lanes     reads using 100 ms of CPU (three times in a row) wait for one of 16 heavy slots, at most 16 HTTP in line (--lanes on)
  reads     32 reads run at once (the CPUs, at least 2; follows the CPU limit), the rest wait in line (--read-slots)
  commits   synced to disk before they are confirmed (--sync-commits always)
  time zone Asia/Katmandu (the computer's); also timers and backup schedules
  readers   read while a long trigger or CALL writes (--reader-yield on); idle open transaction rolled back after 30 s (default)
  memory    26.95 GB for the whole process (85% of the machine's memory); table data 512.0 MB of it (--memory)
  cpu       32 CPUs (the machine's); 16 query threads (half of the CPUs, follows the limit), limits checked every 10 s
  limits    statement timeout 1 min, result memory 1.00 GB, 5000000 groups, 32 statements per user
  safety    writes allowed; DROP TABLE kept 7 days (UNDROP TABLE); audit log on (SHOW AUDIT)
  logs      slow queries >= 1000 ms to ./kdb-data/_logs/slow_queries.jsonl; pg_stat_statements on; metrics at /metrics
  recovery  the default database is created when it is first used

Press Ctrl+C to stop.

Users and logins

Syntax:

HTTP Basic:   Authorization: Basic base64(user:password)
Log in once:  POST /api/login  {"user": "...", "password": "..."}   ->  token
Then:         Authorization: Bearer <token>     (or the kdb_login cookie in a browser)
Log out:      POST /api/logout

When the data folder has users, every request needs a login. Create users with CREATE USER or kdb user add. The details of each route are in HTTP login.

WayUsed byNotes
HTTP Basic (user and password on each request)scripts, curl -uKDB remembers a good password for 10 minutes, so it is not slow
POST /api/login, then Authorization: Bearer <token>appsthe token is random (256 bits); KDB keeps only its SHA-256, in memory
Cookie kdb_loginthe web consoleHttpOnly (page scripts can not read it) and SameSite=Strict (other sites can not use it). A change also needs the X-KDB-CSRF header (CSRF).
SCRAM-SHA-256PostgreSQL drivers on port 8832the password never crosses the network

Command line: With a user, the server asks for a login

kdb user add admin --role admin
> Long-Admin-Passw0rd
> Long-Admin-Passw0rd
kdb serve

Output:

> kdb user add admin --role admin

user 'admin' added with role admin in ./kdb-data

> kdb serve

KDB 0.2.0-dev
  console   http://127.0.0.1:9900
  data      ./kdb-data (from KDB_DATA_DIR; one folder per database)
  default   default (used when a request names no database)
  backups   ./kdb-data/_backups/<database name> (from default)
  access    login required (1 user)
  capacity  32 HTTP threads (default), 500 PostgreSQL connections (default), lock wait 5 s (default)
  lanes     reads using 100 ms of CPU (three times in a row) wait for one of 16 heavy slots, at most 16 HTTP in line (--lanes on)
  reads     32 reads run at once (the CPUs, at least 2; follows the CPU limit), the rest wait in line (--read-slots)
  commits   synced to disk before they are confirmed (--sync-commits always)
  time zone Asia/Katmandu (the computer's); also timers and backup schedules
  readers   read while a long trigger or CALL writes (--reader-yield on); idle open transaction rolled back after 30 s (default)
  memory    26.95 GB for the whole process (85% of the machine's memory); table data 512.0 MB of it (--memory)
  cpu       32 CPUs (the machine's); 16 query threads (half of the CPUs, follows the limit), limits checked every 10 s
  limits    statement timeout 1 min, result memory 1.00 GB, 5000000 groups, 32 statements per user
  safety    writes allowed; DROP TABLE kept 7 days (UNDROP TABLE); audit log on (SHOW AUDIT)
  logs      slow queries >= 1000 ms to ./kdb-data/_logs/slow_queries.jsonl; pg_stat_statements on; metrics at /metrics
  recovery  the default database is created when it is first used

Press Ctrl+C to stop.

HTTP: A server without users has nothing to log in to

POST /api/login
Content-Type: application/json

{"user": "admin", "password": "Long-Admin-Passw0rd"}

Response:

HTTP 400 (application/json)

{
  "error": "this server has no users yet; create one with CREATE USER <name> WITH PASSWORD '...' ROLE admin"
}

HTTPS in front

Syntax:

browser  --HTTPS-->  Caddy or nginx (port 443)  --HTTP-->  kdb serve on 127.0.0.1:9900

Over plain HTTP, anyone on the network path can read passwords and tokens. When people or programs on other computers must reach KDB, either turn on KDB's own TLS, or put a web server with HTTPS (a reverse proxy) in front and keep KDB on 127.0.0.1. A proxy also gets free certificates by itself and renews them.

Caddy, file /etc/caddy/Caddyfile:

kdb.example.com {
    reverse_proxy 127.0.0.1:9900
}

nginx, inside the server { listen 443 ssl; ... } block:

location / {
    proxy_pass http://127.0.0.1:9900;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Password rules

Syntax:

at least 12 characters, not the user name, at most 1024 bytes;  long and random is best

What KDB needs from the password of a database user, and what is wise.

RuleDetail
KDB refusesfewer than 12 characters, the user name itself, or more than 1024 bytes
Wise for people16 or more characters; a few random words are easy to remember. Admins: also two-factor login
Wise for programs20 or more random characters from a password manager or random_token(24); programs keep them in their settings
Neverthe same password as your email or bank; a password on the command line; a password in a web page's JavaScript

Example: A too short password is refused (shows an error)

CREATE USER admin WITH PASSWORD 'ktm2026' ROLE admin;

Result:

Error: the password is too short: use at least 12 characters (a password manager can make a long random one)

Checklist

Syntax:

bind 127.0.0.1  +  users  +  HTTPS or SSH tunnel  +  firewall  +  private data and backups

A small Linux VPS with your app and KDB on the same machine. Chapter 16 shows each step with commands.

CheckWhy
KDB runs as its own system user; data and backups are chmod 700a file copy needs no login
--bind 127.0.0.1 (the default); ports 9900 and 8832 closed in the firewallstrangers can not even connect
an admin user with a long random password; each app has its own user with the smallest roleone leaked password does not open everything
TLS, HTTPS (Caddy or nginx) or an SSH tunnel for the console and the PostgreSQL portpasswords are not sent in plain text
every kdb program updated before the first user was createdold versions do not know users and would serve without a login
app passwords stored with password_hash; session tokens random and stored as sha256a stolen table does not give the passwords
nightly backup copied to another place, restore testedbackups hold the password hashes: keep them private too
server output checked for login failed now and thensee who tries to guess