ubiblk uses a TOML configuration file. The root file is typically named
config.toml and may include additional files for secrets or stripe source
settings.
A config file has these top-level sections:
| Section | Required | Description |
|---|---|---|
[device] |
yes | Device paths and identity |
[tuning] |
no | I/O performance knobs (all fields have defaults) |
[encryption] |
yes* | Encryption key reference |
[danger_zone] |
no | Safety overrides for development |
[stripe_source] |
no | Where to fetch stripes from |
[secrets.*] |
no | Named secret definitions |
* Encryption is required unless danger_zone.allow_unencrypted_disk = true.
The root config can pull in additional TOML files:
include = ["secrets.toml", "stripe_source.toml"]Rules:
- Paths are relative to the directory containing
config.toml. - Append
?to make an include optional (silently skipped if missing):"stripe_source.toml?". - Included files must not declare their own
include(no nesting). - Duplicate key paths across files are rejected.
- Each included file contributes disjoint top-level sections that are merged into the root config.
Core device paths and identity.
[device]
data_path = "/dev/sda"
metadata_path = "/var/lib/ubiblk/meta" # optional
vhost_socket = "/var/run/ubiblk.sock" # optional
rpc_socket = "/var/run/ubiblk-rpc.sock" # optional
device_id = "vm123" # optional, default: "ubiblk"
track_written = false # optional, default: false| Field | Type | Required | Default | Description |
|---|---|---|---|---|
data_path |
path | yes | — | Base block device or file |
metadata_path |
path | no | — | Stripe metadata file |
vhost_socket |
path | no | — | vhost-user socket path (required for vhost-backend) |
rpc_socket |
path | no | — | RPC Unix socket path |
device_id |
string | no | "ubiblk" |
Identifier returned to the guest |
track_written |
boolean | no | false |
Track which stripes have been written |
Performance tuning. All fields are optional with sensible defaults.
[tuning]
num_queues = 4
queue_size = 128
seg_size_max = 65536
seg_count_max = 4
poll_timeout_us = 1000
cpus = [0, 1, 2, 3]
io_engine = "io_uring"
write_through = false| Field | Type | Default | Valid range | Description |
|---|---|---|---|---|
num_queues |
integer | 1 | 1–63 | Number of virtqueues |
queue_size |
integer | 64 | power of 2, max 65536 | Queue depth |
seg_size_max |
integer | 65536 | 1–1048576 | Max I/O segment size (bytes) |
seg_count_max |
integer | 4 | 1–256 | Max segments per I/O |
poll_timeout_us |
integer | 1000 | 0–10000000 | Poll timeout in microseconds |
cpus |
list of integers | none | — | CPU pinning (length must match num_queues) |
io_engine |
string | "io_uring" |
"io_uring", "sync" |
I/O engine |
write_through |
boolean | false | — | Enable write-through mode |
Encryption settings. The xts_key field must reference a named secret using
the ref sub-key.
[encryption]
xts_key.ref = "xts-key"| Field | Type | Required | Description |
|---|---|---|---|
xts_key.ref |
string | yes | Reference to a 64-byte XTS key |
The referenced secret must resolve to exactly 64 bytes (two 32-byte AES keys: data key and tweak key).
Safety overrides for development and testing. The enabled flag must be true
for any individual bypass to take effect.
[danger_zone]
enabled = true
allow_unencrypted_disk = true
allow_inline_plaintext_secrets = true
allow_secret_over_regular_file = true
allow_unencrypted_connection = true
allow_env_secrets = true| Flag | Default | Effect when enabled |
|---|---|---|
enabled |
false | Master switch — all other flags are ignored unless this is true |
allow_unencrypted_disk |
false | Allow omitting the [encryption] section |
allow_inline_plaintext_secrets |
false | Allow source.inline secrets without a KEK |
allow_secret_over_regular_file |
false | Allow reading source.file secrets from regular files (not just pipes) |
allow_unencrypted_connection |
false | Allow remote connections without PSK |
allow_env_secrets |
false | Allow source.env secrets (environment variables persist in /proc/PID/environ) |
Secrets are declared as sub-tables under [secrets]. Each secret has a
source and an optional encrypted_by reference for envelope encryption.
[secrets.config-kek]
source.file = "/run/secrets/kek.pipe"
[secrets.xts-key]
source.inline = "TmVjZXNzYXJ5IGJ5dGVzIGhlcmU..."
encoding = "base64"
encrypted_by.ref = "config-kek"Each source is specified as a sub-key of source:
| Sub-key | Format | Description |
|---|---|---|
source.file |
path | Read secret from a file. Regular files are rejected unless allow_secret_over_regular_file is set; prefer named pipes. |
source.inline |
string | Inline secret data. Without encrypted_by, requires allow_inline_plaintext_secrets. |
source.env |
string | Read secret from an environment variable. Requires allow_env_secrets. |
Secret data must not exceed 8192 bytes after decoding.
Security note on source.env: Environment variables remain in the process
environment for its entire lifetime and are readable via /proc/PID/environ by
the same UID or root. Unlike pipe-based secrets which are consumed and discarded,
env var secrets persist. For this reason, source.env requires
danger_zone.allow_env_secrets to be enabled. For production use, prefer
source.file with a named pipe, which delivers the secret through a
one-time-read channel that leaves no trace in the process environment.
Each secret can set an encoding field (defaults to plaintext when omitted):
| Value | Description |
|---|---|
plaintext |
Use the loaded bytes as-is. |
base64 |
Decode the loaded bytes as base64 to obtain the final secret bytes. |
The encrypted_by field references another secret that holds a 32-byte AES-256-GCM key.
The source data is then treated as encrypted:
[12-byte nonce || ciphertext || 16-byte GCM tag]
The secret's key name (e.g., "xts-key") is used as additional authenticated
data (AAD) during decryption. This binds the ciphertext to the specific secret
name.
Config fields reference secrets using a ref sub-key:
[encryption]
xts_key.ref = "xts-key"References inside [secrets] (the encrypted_by field) are handled through topological
sorting to resolve dependencies in the correct order.
- All secrets are topologically sorted by KEK dependencies.
- Each secret's source bytes are loaded.
- If
encrypted_byis specified, the raw bytes are decrypted using the resolved KEK. - Circular KEK dependencies are detected and rejected.
Configures where to fetch stripes from. Discriminated by the type field.
A local raw disk image:
[stripe_source]
type = "raw"
image_path = "/path/to/image.raw"
autofetch = false # optional, default: false
copy_on_read = false # optional, default: falseAn archive stored on the local filesystem:
[stripe_source]
type = "archive"
storage = "filesystem"
path = "/path/to/archive/root"
archive_kek.ref = "archive-kek"
autofetch = false # optional, default: falseAn archive stored in an S3-compatible object store:
[stripe_source]
type = "archive"
storage = "s3"
bucket = "encrypted-stripes"
prefix = "v1/" # optional
region = "eu-west-1" # optional
access_key_id.ref = "aws-access-key"
secret_access_key.ref = "aws-secret-key"
session_token.ref = "aws-session-token" # optional
archive_kek.ref = "archive-kek"
endpoint = "https://s3.example.com" # optional
connections = 16 # optional, default: 16
autofetch = false # optional, default: false| Field | Type | Required | Default | Description |
|---|---|---|---|---|
bucket |
string | yes | — | S3 bucket name |
prefix |
string | no | — | Key prefix (must not contain . or .. path components) |
region |
string | no | — | AWS region |
access_key_id.ref |
string | yes | — | Reference to AWS access key ID secret |
secret_access_key.ref |
string | yes | — | Reference to AWS secret access key secret |
session_token.ref |
string | no | — | Reference to AWS session token secret (for temporary credentials) |
archive_kek.ref |
string | yes | — | Reference to 32-byte archive KEK secret |
endpoint |
string | no | — | Custom S3 endpoint URL |
connections |
integer | no | 16 | Number of S3 connections (must be > 0) |
autofetch |
boolean | no | false | Fetch stripes in the background |
connect_timeout_ms |
integer | no | 5000 | S3 connection timeout in milliseconds |
operation_attempt_timeout_ms |
integer | no | 20000 | S3 operation attempt timeout in milliseconds |
max_attempts |
integer | no | 3 | Max S3 operation attempts (initial attempt + retries) |
A remote stripe server over TLS-PSK:
[stripe_source]
type = "remote"
address = "1.2.3.4:4555"
autofetch = false # optional, default: false
[stripe_source.psk]
identity = "client1"
secret.ref = "psk-secret"PSK is required unless danger_zone.allow_unencrypted_connection is enabled.
The PSK secret must be at least 16 bytes.
A single-file config for local development:
[device]
data_path = "/tmp/dev-disk.raw"
[danger_zone]
enabled = true
allow_unencrypted_disk = true
allow_inline_plaintext_secrets = true
allow_secret_over_regular_file = trueSplit secrets into a separate file:
config.toml:
include = ["secrets.toml"]
[device]
data_path = "/dev/sda"
metadata_path = "/var/lib/ubiblk/meta"
vhost_socket = "/var/run/ubiblk.sock"
rpc_socket = "/var/run/ubiblk-rpc.sock"
[tuning]
num_queues = 4
queue_size = 128
[encryption]
xts_key.ref = "xts-key"secrets.toml:
[secrets.config-kek]
source.file = "/run/secrets/kek.pipe"
[secrets.xts-key]
source.inline = "<AES-256-GCM encrypted XTS key>"
encoding = "base64"
encrypted_by.ref = "config-kek"config.toml:
include = ["secrets.toml", "stripe_source.toml"]
[device]
data_path = "/dev/nvme0n1"
metadata_path = "/var/lib/ubiblk/meta"
vhost_socket = "/var/run/ubiblk.sock"
rpc_socket = "/var/run/ubiblk-rpc.sock"
[encryption]
xts_key.ref = "xts-key"secrets.toml:
[secrets.config-kek]
source.file = "/run/secrets/kek.pipe"
[secrets.xts-key]
source.inline = "<encrypted XTS key>"
encoding = "base64"
encrypted_by.ref = "config-kek"
[secrets.archive-kek]
source.inline = "<encrypted archive KEK>"
encoding = "base64"
encrypted_by.ref = "config-kek"
[secrets.aws-access-key]
source.env = "AWS_ACCESS_KEY_ID"
[secrets.aws-secret-key]
source.env = "AWS_SECRET_ACCESS_KEY"The S3 credentials above use source.env, so the config must include:
[danger_zone]
enabled = true
allow_env_secrets = truestripe_source.toml:
[stripe_source]
type = "archive"
storage = "s3"
bucket = "encrypted-stripes"
prefix = "v1/"
region = "eu-west-1"
access_key_id.ref = "aws-access-key"
secret_access_key.ref = "aws-secret-key"
session_token.ref = "aws-session-token" # optional
archive_kek.ref = "archive-kek"
autofetch = trueconfig.toml:
include = ["secrets.toml", "stripe_source.toml"]
[device]
data_path = "/dev/sda"
metadata_path = "/var/lib/ubiblk/meta"
vhost_socket = "/var/run/ubiblk.sock"
rpc_socket = "/var/run/ubiblk-rpc.sock"
[encryption]
xts_key.ref = "xts-key"stripe_source.toml:
[stripe_source]
type = "remote"
address = "10.0.0.1:4555"
autofetch = true
[stripe_source.psk]
identity = "client1"
secret.ref = "psk-secret"