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.

Architecture

Two programs and one image: a PatternFly interface served by a Quarkus backend that holds your credentials and talks RESP to your servers.

How Keydra is put together#

Keydra is two programs and one image. The browser talks only to the backend; the backend talks to your servers.

text
  Browser
     │  HTTPS + one WebSocket
     ▼
  Keydra backend  ── PostgreSQL   profiles, accounts, grants, schedules, audit
   (Quarkus)      ── Redis/Valkey (optional) shared cache and cross-instance events
     │            ── ClickHouse   (optional) readings that outlive a restart
     │  RESP, optionally through an SSH tunnel
     ▼
  Redis / Valkey targets
Frontend

A React and PatternFly application. It is built into static files and served by the backend, so a deployment is one process and the interface never has to be told where the API is.

Backend

A Quarkus application. Every request is non-blocking from HTTP through persistence to the target, which is what lets one process stream a million-key scan to several browsers at once.

Keydra’s own store

PostgreSQL holds what Keydra knows: connection profiles, accounts and grants, schedules and their runs, alert rules, audit entries. Your keys are never copied into it.

The two API surfaces#

GraphQL at /graphql

What the interface uses for nearly everything it reads and changes.

REST under /api/v1

The complete surface, versioned in its path, and what another tool or a script is written against. The interface still uses it for files, values, signing in and the sockets.

Both call the same services and are guarded by the same permissions.

Both are described in the API reference.

How the interface stays current without polling#

One WebSocket, at /api/v1/notifications, carries every server-side change: a target going up or down, a key changing, a fresh metrics reading, a migration’s progress, a schedule that failed. The interface opens it once and every page listens.

Where several instances of Keydra share a store, each one re-broadcasts what the others published, so a change made on one reaches a browser attached to another.

What Keydra stores, and what it protects#

Keydra holds three kinds of thing, and treats them differently.

Credentials

Target passwords, SSH keys and passphrases, identity-provider client secrets, backup-destination credentials, chat tokens. Encrypted at rest with AES-256-GCM under the instance key, and never returned by any API — an edit form that arrives with an empty password field keeps the stored one rather than clearing it.

Account passwords

Hashed with Argon2id, verified on a worker thread so a deliberately slow hash never occupies an event loop. A hash cannot be turned back into a password, including by Keydra.

Everything else

Names, addresses, notes, the audit log, schedule settings. Stored as written. Column-level encryption was decided against for these deliberately: it would make them unsearchable while protecting them from nobody who can already read the database.

Important:The instance key

KEYDRA_SECRET_KEY is what encrypts every stored credential. Losing it means every one of them has to be entered again. It can be rotated without downtime — see Rotating the instance key.

Your keyspaces are not stored. What a page shows was read from the target when you asked for it. The one exception is a backup, which is by definition a copy — and a backup can be encrypted so that not even the instance that wrote it can read it back.

More than one Keydra#

Two instances behind a load balancer share one PostgreSQL. Work that may happen anywhere — reading a keyspace, running a console command, drawing a dashboard — belongs to whichever instance the request reached. Work that must happen exactly once is claimed:

  • Scheduled jobs run on the instance holding the lease, and only there.

  • Alert rules are evaluated by that instance alone, so a firing rule sends one message.

  • The sweep that marks interrupted migrations runs there too.

The lease is one row in the database, held for a few seconds and renewed, and the clock that decides whether it has expired is the database’s rather than any instance’s. An instance that stops renewing loses it to whoever asks next, which is what makes a crash recover without anybody deciding anything.

The About page names the instance and says whether it currently holds the lease.

Edit this page