Skip to content

Database and Keys

StorageConfigurationSuitable for
SQLiteDNSE_DB=./dnseditor.db (default)Single instance, desktop, testing
PostgreSQLDNSE_DB_DSN=postgres://…Production use, backups via pg_dump
YugabyteDBDNSE_DB_DSN=… and DNSE_DB_YB=onMultiple nodes, distributed operation

YugabyteDB speaks the PostgreSQL protocol but needs its own driver. That is why the DSN alone is not enough — without DNSE_DB_YB=on the connection is treated as ordinary PostgreSQL.

The schema is migrated automatically at startup. When multiple nodes run, the first one to start performs the migration; the others wait until the schema matches.

DNSE_ENCRYPTION_KEY protects everything that must not be stored in plaintext: provider credentials, private certificate keys, the SMTP credentials (username and password), 2FA secrets, the password hashes of the user accounts — and the personal data of the accounts themselves: username, e-mail address, display name and the ACME account addresses. The database contains only encrypted forms of these; API, agent and DynDNS tokens are additionally stored only as SHA-256 hashes, never in plaintext.

To keep logins working despite the encryption, the DNS-Editor maintains a blind index for username and e-mail: a deterministic HMAC over the normalized value (lower-cased, trimmed) whose key is derived from DNSE_ENCRYPTION_KEY. The database therefore sees neither names nor addresses — yet equality lookups and account uniqueness work unchanged, and logins remain case-insensitive as before.

Three rules follow:

  1. The key does not belong in the same backup as the database. Whoever captures both together has everything.
  2. A backup without the key is worthless. Back it up before the first production start and document where it is stored.
  3. All nodes need the same key. Otherwise node 2 cannot decrypt what node 1 has written.

For PostgreSQL, the usual approach is enough:

Terminal window
pg_dump -Fc dnseditor > dnseditor-$(date +%F).dump

For SQLite, back up the file while the service is stopped, or via sqlite3 dnseditor.db ".backup dnseditor-backup.db".

For restore drills, the same applies as everywhere: a backup that has never been restored is not a backup.

With DNSE_BACKUP_DIR set, the service also writes a nightly logical export of the organisation (Administration → Backup). That export decrypts the database fields so it can be restored — and is therefore sealed again as it is written: AES-256-GCM with the same DNSE_ENCRYPTION_KEY. New points are named …jsonl.enc, older ones …jsonl; both stay restorable, and a download delivers cleartext.

So the same rule applies to these files as to the database: without the key they cannot be restored. Back the key up separately, and actually back it up.

Older backups still lying in the clear are sealed by a one-off run — no database, no running service, repeatable:

Terminal window
DNSE_BACKUP_DIR=… DNSE_ENCRYPTION_KEY=… dnseditor backup-seal

Whether the backups are actually readable is answered by:

Terminal window
DNSE_BACKUP_DIR=… DNSE_ENCRYPTION_KEY=… dnseditor backup-verify

The run opens every point and reads it in full — a damaged frame or a truncated file surfaces there and not first during a restore.