seekrit
Docs/Temporary access

Temporary access

seekrit can mint short-lived credentials on demand — a temporary Postgres, MySQL/MariaDB, or MongoDB login, a Redis ACL user, an SSH certificate, or an AWS or GCP credential, that exists for an hour then disappears — without giving up the zero-knowledge property that defines the rest of the product. Postgres, MySQL/MariaDB, Redis, SSH, AWS, GCP, and MongoDB are all supported today; the machinery is general.

The problem it solves

A long-lived database password in a .env file is a standing liability: it leaks, it lingers, it is hard to rotate. The Vault-style answer is dynamic secrets — mint a credential per session, scoped and expiring. The catch is that a naïve implementation has the control plane generate and hand out the password, so the service sees your plaintext. seekrit avoids that.

The key trick: ship the verifier, not the password

PostgreSQL stores passwords as SCRAM-SHA-256 verifiers, and CREATE ROLE … PASSWORD '…' stores the string verbatim when it is already in verifier form — it does not re-hash it. So the flow inverts:

  1. The machine that will connect generates a random password locally.
  2. It computes the SCRAM verifier locally (PBKDF2 → HMAC → SHA-256).
  3. It sends seekrit only the verifier, plus a role name and a TTL.
  4. seekrit runs CREATE ROLE … PASSWORD '<verifier>' VALID UNTIL ….
  5. The machine connects to Postgres directly with the password it never shared.

The verifier is not sufficient to authenticate — SCRAM login requires ClientKey, and the verifier only stores SHA256(ClientKey) (preimage- resistant) plus ServerKey. So a dump of pg_authid, the query log, or a backup cannot log in. The plaintext password lives only on the consumer.

note

This is zero-knowledge at both layers: the control plane relays only the verifier, and Postgres-at-rest holds only a verifier that can't log in. The password is high-entropy and machine-generated, so the SCRAM iteration count (which exists to slow dictionary attacks on human passwords) is irrelevant.

MySQL & MariaDB

MySQL and MariaDB get the same trick with a different hash. The mysql_native_password plugin stores an account's secret as *<UPPER(HEX(SHA1(SHA1(password))))>, and CREATE USER … IDENTIFIED WITH mysql_native_password AS '*…' stores that string verbatim — it isn't re-hashed. So the client computes the hash locally and sends only that. The double-SHA1 hash cannot log in: mysql_native_password auth is a challenge-response that verifies knowledge of SHA1(password) (the preimage of the stored inner hash), which a dump of mysql.user doesn't reveal.

Two MySQL-specific differences from Postgres:

  • No account expiry timestamp. MySQL has no VALID UNTIL for an account, so the broker's alarm-driven DROP USER is the only thing that expires a lease (which is exactly what the broker is for).
  • Inline grants, no group role. MySQL roles need activation (SET DEFAULT ROLE), an awkward fit for a plain connection URL — so the read-only / read-write presets apply their GRANTs inline per user at mint, scoped to the target database. There is no one-time setup query.

Redis

Redis (6+) gets the same trick with a third hash. A Redis ACL user is created with ACL SETUSER <name> on #<digest>, where #<digest> is the lowercase-hex SHA-256 of the password, stored verbatim. So the client computes the digest locally and sends only that. The digest cannot log in: AUTH <user> <password> hashes the plaintext it receives with SHA-256 and compares — verifying a client needs the password, and SHA-256 is preimage- resistant for the high-entropy machine-generated password, so a dump of the ACL rules can't authenticate.

Like MySQL, Redis has no per-user expiry (the broker's alarm-driven ACL DELUSER is the only thing that ends a lease) and no role inheritance, so the read-only / read-write presets apply their ACL rules inline per user at mint:

  • Read-only~* +@read +@connection (read every key; the client handshake commands work).
  • Read-write — the above plus +@write.
  • Custom — supply your own ACL SETUSER / ACL DELUSER command templates.
note

Redis provisioning speaks RESP, not SQL — each rendered statement is one Redis command line. The in-DO executor sends it over a redis:///rediss:// socket; the remote executor's provisioner runs it against the admin endpoint it holds. Everything else (verifier-only minting, alarm expiry, the executor dial) is identical to the SQL providers.

Provider tiers

Not every system supports verifier injection, so providers are classified by how much zero-knowledge they can preserve for the credential they mint:

  • Tier 1 — verifier injection. The target accepts a client-computed verifier; plaintext never leaves the consumer. Postgres SCRAM, MySQL mysql_native_password, Redis ACL SHA-256, SSH public keys, mTLS CSR signing, hashed API keys.
  • Tier 2 — client-encrypted delivery. The target mints the secret itself, so the broker wraps it to the consumer's public key before returning it. The control plane relays ciphertext; the broker sees plaintext transiently. AWS STS (see AWS credentials), GCP tokens (see GCP credentials), and MongoDB (see MongoDB credentials) ship today; Azure short-lived tokens are the same shape.
  • Tier 3 — opaque broker. The target mints an opaque token the broker must hold; the best option is to never hand it out and proxy the requests instead.

The broker

Each organization gets its own broker (a Cloudflare Durable Object, one instance per org). It owns the lease lifecycle in its own isolated SQLite, and uses alarms to expire leases — the sweeper that drops the role at its deadline (managed Postgres has no in-database scheduler). It holds two secrets carefully:

  • its own keypair, generated once, private half never leaving the DO;
  • each target's admin credential, stored only as ciphertext wrapped to that public key, decrypted transiently in memory to provision, never written back.

The provisioning credential — the honest caveat

To run CREATE ROLE / CREATE USER, something must authenticate to the database as a privileged role. That admin credential is a second, narrower trust tier — the same trade every dynamic-secrets engine makes. seekrit minimizes it, and lets you choose where it lives via the executor:

  • in-DO executor — the broker connects to the database and runs the SQL itself. Convenient; the admin credential is decrypted transiently inside the DO.
  • remote executor — the broker dispatches a signed command to a small seekrit-provisioner running inside your own network, which holds the admin credential. seekrit orchestrates but never sees it — pure zero-knowledge even for provisioning.

The credential you hand out stays zero-knowledge either way; the executor is the dial for how much the provisioning credential is exposed.

The remote executor ships as a tiny container you run in your own network — the self-hosted provisioner guide walks through minting the shared key, registering a remote target, and running the daemon.

Access levels & permissions

A freshly created role has no privileges — it can connect but read nothing, by design (least privilege). You grant access by picking an access level when you register a target:

  • Read-only — leased credentials can only SELECT.
  • Read-writeSELECT / INSERT / UPDATE / DELETE (plus sequence usage so inserts into serial/identity columns work).
  • Custom — supply your own create/revoke SQL templates.

For the two presets, seekrit shows you a one-time setup query to run as an admin. It creates a shared group role (seekrit_readonly / seekrit_readwrite) with the right grants, including ALTER DEFAULT PRIVILEGES so future tables are covered. It's idempotent — safe to re-run:

DO $$ BEGIN
  IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'seekrit_readonly') THEN
    CREATE ROLE seekrit_readonly NOLOGIN;
  END IF;
END $$;
GRANT USAGE ON SCHEMA "public" TO seekrit_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA "public" TO seekrit_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA "public" GRANT SELECT ON TABLES TO seekrit_readonly;

Every leased credential is then created IN ROLE seekrit_readonly and inherits those grants — so you configure permissions once, and the temp roles stay disposable (they own nothing, so DROP ROLE at expiry is clean). The dashboard badges each target and lease with its access level, so "this credential can write" is always visible.

On MySQL/MariaDB the presets skip the group role: each leased user gets the grant applied inline at mint (e.g. GRANT SELECT ON \app`.* TO 'tmp_…'@'%'), scoped to the target database. There's no setup query to run, and DROP USER` at expiry removes the user and its grants together.

On Redis the presets are likewise inline — the leased user is created with its ACL rules in the same ACL SETUSER command (e.g. ~* +@read +@connection), and ACL DELUSER at expiry removes the user and its rules together. No setup step, no group.

Provider compatibility

The verifier-injection trick relies on standard database behavior — a pre-hashed password is stored verbatim.

Postgres. A pre-hashed SCRAM-SHA-256$… password is stored as-is on Render, AWS RDS, Cloud SQL, Azure, Crunchy Bridge, self-hosted, and most managed Postgres. The in-DO executor authenticates its own admin connection via SCRAM, MD5, or cleartext (over TLS), so it works whether a provider uses the modern SCRAM default or still challenges MD5 for login.

MySQL / MariaDB. CREATE USER … IDENTIFIED WITH mysql_native_password AS '*…' stores the pre-hashed value as-is on standard MySQL 8, MariaDB, AWS RDS / Aurora, Cloud SQL, Azure, and self-hosted. The in-DO executor authenticates its admin connection with mysql_native_password or caching_sha2_password (MySQL 8's default) over TLS, so it works against a default MySQL 8 admin account as well as MariaDB.

Redis. ACL SETUSER … on #<sha256> stores the digest as-is on Redis 6+ (where ACLs were introduced) and Redis-compatible servers — self-hosted, AWS ElastiCache/MemoryDB, Valkey. Use rediss:// for a TLS admin endpoint (the default on managed providers); the admin user needs the +@admin permission (or just @dangerous) to run ACL SETUSER/ACL DELUSER.

caution

Control-plane-proxied databases aren't supported for the zero-knowledge path. Some managed providers intercept role/user creation and demand a plaintext password, which is incompatible with sending only a verifier: Neon (Postgres — "Neon only supports being given plaintext passwords") and PlanetScale (MySQL — user management goes through its own API, not CREATE USER). Use a database whose role/user creation isn't proxied by a control plane.

SSH certificates

The same machinery issues short-lived SSH certificates, and it's an even cleaner zero-knowledge story than Postgres — nothing secret is ever sent to or returned by seekrit.

seekrit acts as an SSH certificate authority. When you register an SSH target, a CA keypair is generated in your browser/CLI; only the CA private key is wrapped to the broker (stored as ciphertext), and the CA public key is printed for you to install on your hosts once:

# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/seekrit_ca.pub

Minting a certificate then works like this:

  1. The machine that will connect generates an ephemeral SSH keypair locally.
  2. It sends seekrit only the public key, plus the login principals and a TTL.
  3. The broker signs a certificate over that public key (valid principals, valid before = now + TTL) using the CA key, decrypted transiently in the DO.
  4. seekrit returns the certificate — a public artifact — and you connect:
ssh -i id_ed25519 -o CertificateFile=id_ed25519-cert.pub deploy@host

The private key never leaves the requesting machine, and a certificate cannot authenticate without it — so, like the Postgres verifier, what seekrit handles is never sufficient to log in. This is tier 1 (verifier injection): the public key is the "verifier". Certificates use Ed25519, and the CA-signing (in_do) executor is the only mode for now.

note

Revocation is by expiry. A certificate is a self-contained bearer artifact, so seekrit v1 relies on short TTLs rather than a revocation list — keep lifetimes tight (minutes to a few hours). Revoking a lease records the event in the ledger and audit trail, but an already-issued certificate stays valid until it expires. A published KRL (RevokedKeys) for early revocation is planned.

AWS credentials

AWS is the first tier-2 provider. Unlike the databases and SSH — where the consumer computes a verifier and the plaintext never leaves — AWS mints the credential itself (an access key + secret + session token). So it can't be verifier-only; instead the credential is encrypted to the consumer before it comes back, which keeps it zero-knowledge at the control plane.

You register one assumable IAM role as a target. seekrit assumes it via STS AssumeRole using a base IAM credential (the wrapped admin secret) whose only required permission is sts:AssumeRole on that role. What the leased credential can do lives entirely in IAM: the role's attached policies define its permissions, optionally narrowed per target by an inline session policy.

Minting works like this:

  1. The machine that will use the credential generates an ephemeral P-256 keypair locally and sends seekrit only the public key, plus a TTL.
  2. The broker calls STS AssumeRole with the base credential, decrypted transiently in the DO, and gets back the temporary credential.
  3. The broker wraps that credential to your public key (the same wd1. envelope used for DEK grants) and returns only the ciphertext.
  4. Your machine unwraps it with the private key it never shared:
eval "$(seekrit aws lease prod-deploy --ttl 1h)"
aws sts get-caller-identity

The control plane only ever relays ciphertext, and only your private key can unwrap it — so, as with the other providers, what seekrit stores or returns is never usable on its own. This is the one place the broker sees plaintext transiently (it does the STS call and the wrap), the scoped exception tier 2 makes — the same trade every dynamic-secrets engine makes for the provisioning credential.

note

Revocation is by expiry. STS session credentials can't be revoked individually, so — like SSH certificates — v1 relies on the short lifetime STS itself enforces (DurationSeconds, 15 minutes to 12 hours; the requested TTL is clamped to the role's MaxSessionDuration). Revoking a lease records the event in the ledger and audit trail, but an issued credential stays valid until it expires. in_do execution only — the remote provisioner speaks SQL, not the AWS API.

GCP credentials

GCP is the second tier-2 provider, and works exactly like AWS against Google's IAM Service Account Credentials API. You register one impersonable service account as a target. seekrit impersonates it with a base service-account key (the wrapped admin secret) for a source service account that holds roles/iam.serviceAccountTokenCreator on the target. What the leased token can do lives entirely in GCP IAM: the target service account's roles define its permissions, narrowed per lease by the OAuth scopes you grant.

Minting works the same as AWS:

  1. The machine that will use the credential generates an ephemeral P-256 keypair locally and sends seekrit only the public key, plus a TTL.
  2. The broker signs a JWT with the source key, exchanges it for the source account's access token, and calls generateAccessToken to impersonate the target account — all with the admin key decrypted transiently in the DO.
  3. The broker wraps the returned OAuth token to your public key (the same wd1. envelope used for DEK grants) and returns only the ciphertext.
  4. Your machine unwraps it with the private key it never shared:
eval "$(seekrit gcp lease prod-deploy --ttl 1h)"
gcloud storage ls

The leased token is a standard OAuth 2.0 access token — the export lines set CLOUDSDK_AUTH_ACCESS_TOKEN (for gcloud) and GOOGLE_OAUTH_ACCESS_TOKEN (for the Google client libraries). As with every other provider, the control plane only ever relays ciphertext, and only your private key can unwrap it.

note

Revocation is by expiry. Short-lived access tokens can't be revoked individually, so — like SSH certificates and AWS — v1 relies on the short lifetime GCP enforces (1 minute to 12 hours; tokens over 1 hour require the constraints/iam.allowServiceAccountCredentialLifetimeExtension org policy). Revoking a lease records the event in the ledger and audit trail, but an issued token stays valid until it expires. in_do execution only — the remote provisioner speaks SQL, not the Google APIs.

MongoDB credentials

MongoDB is a tier-2 provider, for a reason worth stating plainly: unlike Postgres, MySQL, and Redis — where the target stores a client-computed verifier verbatim — MongoDB's createUser hashes the password server-side, and there is no supported way to inject a precomputed SCRAM credential. So the plaintext can't stay on the consumer's machine the way it does for the tier-1 databases. MongoDB takes the same shape as AWS instead: the credential is encrypted to the consumer before it comes back.

You register one cluster as a target, with the database leased users get access to and an access level. seekrit provisions with an admin mongodb:// credential (the wrapped admin secret) that needs userAdmin on that database. Access levels map to MongoDB's built-in roles — read (read-only) or readWrite — on the target database, or you supply an explicit roles array for custom.

Minting works like this:

  1. The machine that will use the credential generates an ephemeral P-256 keypair locally and sends seekrit only the public key, plus a TTL.
  2. The broker generates a random password, connects to MongoDB over its wire protocol (with the admin credential decrypted transiently in the DO), and runs createUser with the preset roles.
  3. The broker wraps the resulting credential to your public key (the same wd1. envelope used for DEK grants) and returns only the ciphertext.
  4. Your machine unwraps it with the private key it never shared:
eval "$(seekrit mongodb lease prod-app --ttl 1h)"
mongosh "$MONGODB_URI"

The control plane only ever relays ciphertext, and only your private key can unwrap it. The broker sees the password transiently (it generates it and runs createUser), the scoped exception tier 2 makes — the same trade the tier-1 providers make for their admin credential.

note

Revocation is real. Unlike AWS/SSH, a MongoDB lease is ended by dropping the user: the broker's alarm runs dropUser at expiry, and revoking a lease drops it immediately. in_do execution only — the remote provisioner speaks SQL, not the MongoDB wire protocol. mongodb+srv:// admin URLs aren't supported (they need a DNS SRV lookup); register an explicit host:port.

See the CLI reference (seekrit pg, seekrit mysql, seekrit redis, seekrit ssh, seekrit aws, seekrit gcp, seekrit mongodb) and the API reference for the endpoints.