60 lines
3.8 KiB
Markdown
60 lines
3.8 KiB
Markdown
# Secret Store and encrypted full backup/restore
|
|
|
|
RustProxy keeps application credentials in a central encrypted Secret Store. Provider and ACME secrets are not stored as plaintext configuration values in SQLite.
|
|
|
|
## Secret Store
|
|
|
|
Secret records live in the `secret_store` SQLite table and contain an identifier, provider/type, encryption version, nonce, ciphertext, metadata and timestamps. Values are encrypted with AES-256-GCM using a random local data key stored at:
|
|
|
|
```text
|
|
<data-dir>/secrets/master.key
|
|
```
|
|
|
|
The key file is created with mode `0600`; its directory is private. The application loads it during normal startup, so normal operation does not require an interactive password.
|
|
|
|
Existing ACME DNS credential files are migrated on startup for Cloudflare, OVH and RFC2136. After successful import, the legacy plaintext credential file is removed. Certbot-compatible credential files are recreated only as short-lived private files around the external operation and removed afterwards.
|
|
|
|
Do not copy only `config.sqlite3` as a Secret Store backup: the local key material is also required. Use the encrypted full-backup command below.
|
|
|
|
## Create a full backup
|
|
|
|
`backup create` may run while RustProxy is serving traffic. If the service is stopped, the CLI takes the data-directory lock; if the service is running, it creates a consistent SQLite snapshot and stages durable filesystem state without taking ownership of that lock. The output file must be outside the RustProxy data directory and must not already exist.
|
|
|
|
```bash
|
|
rust-proxy --data-dir /var/lib/rustproxy backup create \
|
|
--output /private/rustproxy-backup.rpb
|
|
```
|
|
|
|
When run from a TTY, RustProxy asks for the backup password without putting it in process arguments or shell history. Press Enter at the password prompt to generate a strong random password. A generated password is printed exactly once and is required for restore.
|
|
|
|
For non-interactive `backup create`, RustProxy generates the password automatically and prints it once to standard output. Protect that output as a secret.
|
|
|
|
The encrypted backup contains the durable application state needed for recovery, including:
|
|
|
|
- a consistent SQLite snapshot,
|
|
- configuration and Secret Store records,
|
|
- backup-wrapped Secret Store key material,
|
|
- certificates and private keys,
|
|
- ACME state and durable provider data,
|
|
- other durable RustProxy data accepted by the backup manifest,
|
|
- validated internal relative symlinks, including Certbot `live/` links into the same profile's `archive/` tree.
|
|
|
|
It excludes rebuildable/runtime data such as cache, logs, temporary files, sockets, PID/lock files and SQLite WAL/SHM sidecars. Symlinks are accepted only when their target is relative and resolves inside the staged RustProxy data tree; absolute, escaping or dangling links fail backup/restore. Restore recreates accepted links as links rather than dereferencing them.
|
|
|
|
The outer container derives a backup key with Argon2id and encrypts authenticated chunks with AES-256-GCM. The backup password is independent from the local Secret Store key.
|
|
|
|
## Restore
|
|
|
|
Restore is CLI-only, requires RustProxy to be stopped, and requires an interactive TTY for the password:
|
|
|
|
```bash
|
|
rust-proxy --data-dir /var/lib/rustproxy backup restore \
|
|
/private/rustproxy-backup.rpb
|
|
```
|
|
|
|
Before replacing the destination, RustProxy verifies the encrypted container, password/authentication tags, manifest, checksums, backup format, RustProxy/schema compatibility, restored SQLite database and decryptability of all Secret Store records.
|
|
|
|
Files are first restored into a private staging directory. Only after validation succeeds is the staged data directory atomically activated; activation failure attempts to roll back to the previous directory. Sensitive restored files use restrictive permissions.
|
|
|
|
A lost backup password cannot be recovered by RustProxy.
|