181 lines
7.3 KiB
Markdown
181 lines
7.3 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
|
||
|
||
### 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`
|
||
- Supported commands: `docs/cmds.md`
|
||
- JSON-RPC examples: `docs/rpc_examples.md` |