Encryption
Configured per backup, performed before upload, with a key you supply.
View as MarkdownYou supply a PGP public key. We encrypt to it and never hold the private half.
Where the encryption happens depends on the kind, and that difference is the whole security story of the product, so it is the first thing on this page rather than a footnote.
| Kind | Encrypted by | Plaintext exists on |
|---|---|---|
local | Your worker, before upload | Your host only |
cloud | Us, after the dump | Our infrastructure |
manual | Us, after your upload | Our infrastructure |
For a local backup, ciphertext is all we ever receive. For the other two, we necessarily handle plaintext: we produced it, or you sent it to us. Encryption there protects the archive at rest, not the pipeline that made it.
Supplying the key
Generate a keypair and give us the public half:
gpg --quick-generate-key "backups@example.com" default default never
gpg --armor --export backups@example.com > ./keys/prod.ascOn the backup definition, for cloud and manual:
encryption:
public_key: ./keys/prod.ascsctl backup configure <backup-id> --encryption-key ./keys/prod.ascOn the worker, for local:
encryption:
public_key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----The key is validated when you save it. A malformed or unreadable key is refused at configure time rather than at 02:00.
Keep the private half somewhere you will still have it after the incident that makes you need it. We hold only the public key. We cannot decrypt your backups, and we cannot recover them if you lose the private key. That is not a policy we could waive; there is no key on our side to waive it with.
Store the private key somewhere that does not share fate with the systems being backed up. A password manager, an HSM, an offline copy in a safe. Not on the database server.
Local backups: put the key on the worker
For a local backup, set the key in the worker's config.yaml. Setting
encryption.public_key on the definition as well makes us encrypt again, on top of the
ciphertext your worker already produced. The result is readable, but you decrypt twice and
the second pass compresses nothing.
If you want belt and braces, that is your call and it works. If you want one encryption, put the key on the worker and leave the definition's key unset.
What "encrypted" means on an artifact
Every artifact records what was actually done to it:
| Field | Meaning |
|---|---|
encrypted | Whether a PGP layer was applied |
key_id | The fingerprint of the public key it was encrypted to |
compressed | Whether a gzip layer was applied, before the PGP one |
key_id is what tells you which key opens a given artifact, which is the field that
matters once you have rotated a key. Listings show it, so you never have to guess.
Plain artifacts
Encryption is optional, and an artifact with no key configured is stored as it came out of the source.
| Kind | What "no key" means |
|---|---|
local | The worker uploads the dump as-is. Your plaintext is in our storage |
cloud | We store the dump as-is |
manual | We store what you uploaded, as-is |
Nothing refuses to run without a key, and nothing warns you at run time. A backup configured
without one quietly produces plain artifacts forever. Check encrypted on an artifact you
care about rather than assuming.
Server-side encryption at the storage layer is not a substitute and we do not offer it as one. It protects against a stolen disk and nothing else, because whoever can read the bucket can read through it.
Changing the key
Set a new public key on the backup, or on the worker for a local backup.
- Future artifacts are encrypted to the new key.
- Existing artifacts are untouched, and still need the old private key.
- Each artifact's
key_idsays which one it needs.
Never destroy an old private key while artifacts encrypted to it still exist. Retention
tells you exactly when that is safe: once the last artifact carrying that key_id has
expired, the key is dead weight.
sctl artifact list --backup $BACKUP_IDA rotation plan that works: add the new key, let the retention window pass, confirm no
artifact still lists the old key_id, then retire the old private key.
Verifying you can actually decrypt
A backup you have never restored is a hypothesis, and an encryption key you have never used is a worse one.
sctl restore file <artifact-id> --path ./restore-test.dumpRestore is client-side: the artifact is downloaded and decrypted on your machine, with your private key. We are not in that path and could not be. If it works on your laptop with your key, it will work during the incident.
Do this the day you configure the key, not the day you need it. See Rehearsal.