182 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			182 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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. It’s 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 it’s 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 (it’s 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 don’t need to manipulate these keys directly; they’re 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
 | ||
| 
 | ||
| 2) Open and use a database
 | ||
| - Clients select a database over RESP using `SELECT`
 | ||
| - Authorization and encryption state are enforced using `DB 0` metadata
 | ||
| 
 | ||
| 3) 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
 | ||
| 
 | ||
| Connections start with no database selected. Any command that requires storage (GET, SET, H*, L*, SCAN, etc.) will return an error until you issue a SELECT to choose a database. Admin DB 0 is never accessible without authenticating via SELECT 0 KEY <admin_secret>.
 | ||
| ### 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`):
 | ||
| ```bash
 | ||
| # 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](./rpc_examples.md).
 | ||
| 
 | ||
| ## 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
 | ||
| 
 | ||
| 2) 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 isn’t 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](./basics.md)
 | ||
| - Supported commands: [docs/cmds.md](./cmds.md)
 | ||
| - JSON-RPC examples: [docs/rpc_examples.md](./rpc_examples.md) |