Docs / Security and key custody

Security and key custody

Every backup is encrypted on your host before it is uploaded, and written to a bucket under S3 Object Lock. SafeGrd keeps the key that decrypts it sealed for your enrolled hosts, unless you choose to keep it on a host yourself. This page covers the cryptography, what SafeGrd can hold, and how to harden the host.

The cryptography

LayerWhat is used
EncryptionAge: X25519 key agreement, ChaCha20-Poly1305 authenticated encryption. Standard, auditable, no bespoke scheme.
Compressionzstandard, applied before encryption on the streaming path.
AttestationEd25519 signatures over a SHA-256 hash chain.
ImplementationGo, no wrapped proprietary blobs. A database surface runs its engine's own client tools: pg_dump for a PostgreSQL schema, mysqldump and mysql for MySQL and MariaDB, mongodump and mongorestore for MongoDB. SQLite, files and mail run nothing else.

Encryption is asymmetric on purpose. A host that takes backups needs only the public key, so compromising a backup daemon does not yield the ability to read what it has already written.

The CLI sets no server-side encryption header on an upload: the object is already Age ciphertext. Your bucket's own encryption at rest (SSE-S3, which AWS applies to every new bucket) is your setting, and it adds nothing to the confidentiality of a backup.

Data keys and key-encryption keys

In envelope-encryption terms, every backup has its own data encryption key (DEK), and your age key pair is the key-encryption key (KEK) that wraps it.

KeyWhat it doesWhere it lives
DEK Age makes a random 16-byte file key for each backup. The backup is encrypted with ChaCha20-Poly1305 under a key derived from it. In the backup's header, wrapped by the KEK. Each backup gets a new one, so there is nothing to store or rotate.
KEK Your age key pair (X25519). The public half wraps each backup's DEK. The private half unwraps it for a restore or a drill. The public half is on your hosts and in SafeGrd. Who holds the private half is your key custody choice: a SafeGrd-managed key or a customer-managed key.

With a customer-managed key the private half of the KEK stays on your hosts. With a SafeGrd-managed key SafeGrd stores it in an envelope of its own: the key is encrypted with AES-256-GCM under a fresh key, and that key is wrapped under SafeGrd's root key, which can be rotated without touching your backups. The KEK is the only key you decide about.

In cloud terms, a SafeGrd-managed key works like a key your cloud provider manages for you: kept per organization, released only to your enrolled hosts, and every release audited. A customer-managed key here never reaches SafeGrd at all, which makes it client-side encryption rather than a key in a provider's KMS.

What the control plane never receives

Two kinds of secret, chosen per host

SafeGrd needs two different secrets to protect a surface, and you choose who holds each one separately. Choosing SafeGrd for one does not choose it for the other.

SecretWhat it doesWho holds it unless you choose otherwise
The encryption key The age private key that decrypts a host's backups (the private half of the KEK). Restores and drills need it. Backing up needs only its public half. SafeGrd, sealed. Chosen per host when it enrols: --key-custody local keeps it on the host.
A surface's credential The database URL or mailbox password the host uses to read the surface. Only backups need it. The host. Chosen per surface: the setup wizard asks, and Credential on the surface's row hands it to SafeGrd.

A bucket's own access key belongs to the project, not to a host. SafeGrd holds it when the bucket was set up in the console, and the hosts keep it when the CLI set it up. Hosted storage needs no key of yours.

The encryption key

ChoiceWhat SafeGrd holds
SafeGrd-managed key (--key-custody safegrd) The default for each host. SafeGrd keeps your key sealed and releases it only to your organization's enrolled hosts, for restores and drills. Every release is written to the audit log before it happens. Losing a host never loses your backups: restore from a new host.
Customer-managed key (--key-custody local) Only you can decrypt these backups. SafeGrd holds the public key and nothing else. Keep a copy of the key file somewhere safe.

Each surface in the console says who holds its key. A key you supply yourself is never sent. A database SafeGrd backs up for you has no host to make a key, so it is sealed to one SafeGrd makes and keeps for your organization.

Credentials

A credential SafeGrd holds is sealed and released only to the surface's host when it backs up, or to the machine SafeGrd starts for that backup. Nobody can read it back out through the console or the API. A credential on the host stays in its config, named by an environment variable, a command or a file.

Whichever you pick, the archive is sealed the same way. See Install and enrol for choosing the key at enrollment.

SafeGrd's own database

The control plane's database holds your account, your hosts' enrolment, the record of every backup and drill, and the sealed secrets above. It runs on the same server as the control plane, listens on that server's loopback interface only, and takes a password to connect. The sealed rows are ciphertext without SafeGrd's root key, which is kept in the server's configuration and never in the database or in a copy of it.

If a host, a token or a key file is compromised

Each step names the console action or the command. Do them in this order. The first step stops further access, and the later ones show what was reached.

What happenedWhat to do
A host is lost or compromised
  1. Nodes, the host, Edit, Rotate token. The old node token stops working at once, so the host can no longer report, fetch a held credential or be released the key.
  2. If SafeGrd holds the surface's credential, rotate it at the database or mail provider and save the new one from Credential on the surface's row. If the host held it, rotate it there too: the host's config was readable to whoever had the host.
  3. Storage, Key access record: every release of a held secret to that host, with the time. That is what the host could have decrypted or connected to.
  4. Enrol the replacement with safegrd enroll. With a SafeGrd-managed key it restores at once (runbook). With a customer-managed key, copy the key file to it.
A personal access token leaked
  1. Tokens, the token, Revoke. Every request with it is refused from then on.
  2. A token can enrol a host, read, and ask for a backup or a drill. Check Nodes for a host you did not enrol, and the Key access record for a release to one.
  3. Mint a new token for whatever used the old one, and pass it as env:VAR or file:/path.
A customer-managed key file was copied
  1. Rotate the bucket's access key at your storage provider and save the new one in Storage. Whoever has the key file can read a backup only if they can also fetch it.
  2. On the host, safegrd init --force writes a fresh config and a new key, so the host starts over as a new node: run safegrd enroll, which sends the new public key, and add its surfaces again. Backups from then on are encrypted to the new key. Earlier snapshots stay readable with the old file, so keep that file until they expire, and treat it as the copy did: anyone holding it and a copy of a snapshot can read that snapshot.

Verify the claims yourself

Everything on this page about what leaves your host can be checked in the CLI's source, at github.com/safegrd/cli. It is licensed under the Business Source License 1.1: you can read, modify, build and evaluate it. Production use comes with any SafeGrd account, the free plan included. Listing, verifying, restoring and exporting your backups is allowed without an account, and if SafeGrd stops running its service, all production use is allowed. Each version becomes GPLv3 four years after release (licence).

Hardening the host