Files
herodb/docs/admin.md

7.3 KiB
Raw Blame History

Admin Database 0 (0.db)

This page explains what the Admin Database DB 0 is, why HeroDB uses it, and how to work with it as a developer and end-user. Its a practical guide covering how databases are created, listed, secured with access keys, and encrypted using per-database secrets.

What is DB 0?

DB 0 is the control-plane for a HeroDB instance. It stores metadata for all user databases (db_id >= 1) so the server can:

  • Know which databases exist (without scanning the filesystem)
  • Enforce access control (public/private with access keys)
  • Enforce per-database encryption (whether a given database must be opened encrypted and with which write-only key)

DB 0 itself is always encrypted with the admin secret (the process-level secret provided at startup).

How DB 0 is created and secured

  • DB 0 lives at <base_dir>/0.db
  • It is always encrypted using the admin secret provided at process startup (using the --admin-secret <secret> CLI flag)
  • Only clients that provide the correct admin secret can SELECT 0 (see “SELECT + KEY” below)

At startup, the server bootstraps DB 0 (initializes counters and structures) if its missing.

Metadata stored in DB 0

Keys in DB 0 (internal layout, but useful to understand how things work):

  • admin:next_id

    • String counter holding the next id to allocate (initialized to "1")
  • admin:dbs

    • A hash acting as a set of existing database ids
    • field = id (as string), value = "1"
  • meta:db:<id>

    • A hash holding db-level metadata
    • field public = "true" or "false" (defaults to true if missing)
  • meta:db:<id>:keys

    • A hash mapping access-key hashes to the string Permission:created_at_seconds
    • Examples: Read:1713456789 or ReadWrite:1713456789
    • The plaintext access keys are never stored; only their SHA-256 hashes are kept
  • meta:db:<id>:enc

    • A string holding the per-database encryption key used to open <id>.db encrypted
    • This value is write-only from the perspective of the management APIs (its set at creation and never returned)
  • age:key:<name>

    • Base64-encoded X25519 recipient (public encryption key) for named AGE keys
  • age:privkey:<name>

    • Base64-encoded X25519 identity (secret encryption key) for named AGE keys
  • age:signpub:<name>

    • Base64-encoded Ed25519 verify public key for named AGE keys
  • age:signpriv:<name>

    • Base64-encoded Ed25519 signing secret key for named AGE keys

You dont need to manipulate these keys directly; theyre listed to clarify the model. AGE keys are managed via AGE commands.

Database lifecycle

  1. Create a database (via JSON-RPC)
  • The server allocates an id from admin:next_id, registers it in admin:dbs, and defaults the database to public=true
  • If you pass an optional encryption_key during creation, the server persists it in meta:db:<id>:enc. That database will be opened in encrypted mode from then on
  1. Open and use a database
  • Clients select a database over RESP using SELECT
  • Authorization and encryption state are enforced using DB 0 metadata
  1. Delete database files
  • Removing <id>.db removes the physical storage
  • DB 0 remains the source of truth for existence and may be updated by future management methods as the system evolves

Access control model

  • Public database (default)
    • Anyone can SELECT <id> with no key, and will get ReadWrite permission
  • Private database
    • You must provide an access key when selecting the database
    • The server hashes the provided key with SHA-256 and checks membership in meta:db:<id>:keys
    • Permissions are Read or ReadWrite depending on how the key was added
  • Admin DB 0
    • Requires the exact admin secret as the KEY argument to SELECT 0
    • Permission is ReadWrite when the secret matches

How to select databases with optional KEY

  • Public DB (no key required)

    • SELECT <id>
  • Private DB (access key required)

    • SELECT <id> KEY <plaintext_key>
  • Admin DB 0 (admin secret required)

    • SELECT 0 KEY <admin_secret>

Examples (using redis-cli):

# Public database
redis-cli -p $PORT SELECT 1
# → OK

# Private database
redis-cli -p $PORT SELECT 2 KEY my-db2-access-key
# → OK

# Admin DB 0
redis-cli -p $PORT SELECT 0 KEY my-admin-secret
# → OK

Per-database encryption

  • At database creation, you can provide an optional per-db encryption key
  • If provided, the server persists that key in DB 0 as meta:db:<id>:enc
  • When you later open the database, the engine checks whether meta:db:<id>:enc exists to decide if it must open <id>.db in encrypted mode
  • The per-db key is not returned by RPC—it is considered write-only configuration data

Operationally:

  • Create with encryption: pass a non-null encryption_key to the createDatabase RPC
  • Open later: simply SELECT the database; encryption is transparent to clients

Management via JSON-RPC

You can manage databases using the management RPC (namespaced herodb.*). Typical operations:

  • createDatabase(backend, config, encryption_key?)
    • Allocates a new id, sets optional encryption key
  • listDatabases()
    • Lists database ids and info (including whether storage is currently encrypted)
  • getDatabaseInfo(db_id)
    • Returns details: backend, encrypted flag, size on disk, key_count, timestamps, etc.
  • addAccessKey(db_id, key, permissions)
    • Adds a Read or ReadWrite access key (permissions = "read" | "readwrite")
  • listAccessKeys(db_id)
    • Returns hashes and permissions; you can use these hashes to delete keys
  • deleteAccessKey(db_id, key_hash)
    • Removes a key by its hash
  • setDatabasePublic(db_id, public)
    • Toggles public/private

Copyable JSON examples are provided in the RPC examples documentation.

Typical flows

  1. Public, unencrypted database
  • Create a new database without an encryption key
  • Clients can immediately SELECT <id> without a key
  • You can later make it private and add keys if needed
  1. Private, encrypted database
  • Create passing an encryption_key
  • Mark it private (setDatabasePublic false) and add access keys
  • Clients must use SELECT <id> KEY <plaintext_access_key>
  • Storage opens in encrypted mode automatically

Security notes

  • Only SHA-256 hashes of access keys are stored in DB 0; keep plaintext keys safe on the client side
  • The per-db encryption key is never exposed via the API after it is set
  • The admin secret must be kept secure; anyone with it can SELECT 0 and perform administrative actions

Troubleshooting

  • ERR invalid access key when selecting a private db

    • Ensure you passed the KEY argument: SELECT <id> KEY <plaintext_key>
    • If you recently added the key, confirm the permissions and that you used the exact plaintext (hash must match)
  • Database X not found

    • The id isnt registered in DB 0 (admin:dbs). Use the management APIs to create or list databases
  • Cannot SELECT 0

    • The KEY must be the exact admin secret passed at server startup

Reference

  • Admin metadata lives in DB 0 (0.db) and controls:
    • Existence: admin:dbs
    • Access: meta:db:<id>.public and meta:db:<id>:keys
    • Encryption: meta:db:<id>:enc

For command examples and management payloads:

  • RESP command basics: docs/basics.md
  • Supported commands: docs/cmds.md
  • JSON-RPC examples: docs/rpc_examples.md