Skip to content
Development documentation
This describes Keydra as it is being built and is not a released version. What it documents can change before a release.

Security overview

Keydra holds the credentials to every server it manages. What that means for encryption at rest, sessions, outbound requests and the key that protects everything else.

What Keydra protects, and how#

Keydra holds the credentials to every server it manages. That makes it a target, and the measures below are the ones that follow from taking that seriously.

Credentials encrypted at rest

AES-256-GCM under the instance key, and never returned by any API. See What Keydra stores.

Passwords hashed

Argon2id, verified on a worker thread.

A session is a row, not only a cookie

Ending a session takes effect on the next request, and setting a password ends every session that came before it. See Your sessions.

Permissions rebuilt per request

Revoking access does not wait for a sign-out.

Headers the browser enforces

A content security policy, X-Content-Type-Options: nosniff, and the rest are set as headers rather than assumed. The policy is a configuration property, so a deployment that serves assets from elsewhere widens the one directive it needs instead of turning the header off.

Cookies

The session cookie is HttpOnly, so no script can read it, and SameSite=Lax, so no other site can send it. It is Secure in production.

Addresses somebody typed are checked before they are called

Webhooks, issuer discovery and object-store endpoints all go through one guard. Link-local is refused and has no setting — 169.254.169.254 answers with the machine’s cloud credentials, and configuring an alert is an operator’s job rather than an administrator’s. The check runs when the address is stored and again before the request, because a row can predate the check.

Broadcasts name the target they are about

A notification about one target reaches only the sockets whose owner may see that target. A viewer granted one server hears nothing about any other.

Secrets never reach a log

Not a target password, not a token, not a passphrase. The command watcher redacts them before they leave the engine.

The operations that empty a keyspace can be made to wait

A target can refuse to be emptied until its own name is typed, or until somebody other than the person who asked agrees to it. Neither is a permission, so turning one on locks nobody out — see Operations that wait for a second person.

Rotating the instance key#

KEYDRA_SECRET_KEY encrypts every stored credential. A key that cannot be rotated is a key nobody rotates, which is the same as not having one after the first person leaves.

An encrypted value names the key that wrote it, so more than one can be readable at once. That is what makes a rotation something other than an outage.

Prerequisites
  • You hold crypto:rotate. An administrator’s alone: a rotation rewrites every credential in the instance.

Procedure
  1. Generate a new key:

    bash
    openssl rand -base64 32
  2. Deploy with the new key writing and the old one still reading:

    properties
    KEYDRA_SECRET_KEY=<the new key>
    KEYDRA_PREVIOUS_SECRET_KEYS=<the old key>

    This deployment works before anything is re-encrypted. Existing values keep decrypting with the key that wrote them.

  3. In the interface, run the re-encryption. It moves every encrypted column — target passwords, tunnel credentials, provider client secrets, destination credentials — onto the new key and reports how many it moved.

  4. Remove KEYDRA_PREVIOUS_SECRET_KEYS and deploy again.

Verification

The instance starts, every target still connects, and every tunnel still opens. That the next start needed no previous key is the proof the rotation finished.

Important:Important

Do not remove the previous key before the re-encryption has reported that it moved everything. A half-finished rotation with the old key gone is a database nothing can read.

Edit this page