Tutorials

Dynamic Database Credentials (JIT) for PostgreSQL and MySQL

Outcome. You'll request a fresh PostgreSQL (or MySQL) user from CoreLink, connect with it, and see it auto-dropped from the database when the session ends. No shared passwords, no long-lived app credentials, nothing to rotate.

Why JIT database access

Shared database passwords are one of the most common credential-rot patterns in any infrastructure. "The prod DB password is in our 1Password vault" — everyone who's ever had prod access has it, nobody revokes it when someone leaves, and rotating it requires a coordinated app restart. JIT flips the model: every access creates an ephemeral user scoped to a single session, which is dropped when the session ends. Nothing to rotate because nothing persists.

Prerequisites

Step 1 — Create a role template on the database

CoreLink will create users on the fly and assign them to an existing role. Create that role in Postgres:

psql -h localhost -U postgres -d postgres

postgres=# CREATE ROLE corelink_readonly;
postgres=# GRANT CONNECT ON DATABASE postgres TO corelink_readonly;
postgres=# GRANT USAGE ON SCHEMA public TO corelink_readonly;
postgres=# GRANT SELECT ON ALL TABLES IN SCHEMA public TO corelink_readonly;
postgres=# ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO corelink_readonly;

This is the ceiling of what CoreLink-provisioned users can do — SELECT-only on the public schema. Separate role templates cover read-write, DDL, and admin profiles.

Step 2 — Grant CoreLink a provisioning user

CoreLink needs a user that can CREATE ROLE and DROP ROLE:

postgres=# CREATE ROLE corelink_provisioner WITH LOGIN CREATEROLE PASSWORD 'replace-with-strong-pw';

Store that password as a CoreLink secret before you do anything else with it — never paste it directly into the DB connection config:

tbcl-cli secrets create --name demo-db-provisioner --value "replace-with-strong-pw"

Step 3 — Register the database connection

UI: Sessions → Database Connections → New Connection. Fill in:

Save. CoreLink attempts a test connection using the provisioning user. You should see Status: Verified within a second or two.

Step 4 — Request JIT credentials

On the connection's detail page, click Request Access:

CoreLink generates a random username (tbcl_a8f3c2d1_readonly), creates the user in Postgres with GRANT corelink_readonly TO ..., returns the credentials to you, and schedules a drop-user job for 15 minutes from now.

Expected output:

Access request granted.
  username: tbcl_a8f3c2d1_readonly
  password: hg83K!f2nZjLp9qWb4vM7eRyUtX0sC5a
  host:     localhost
  port:     5432
  database: postgres
  role:     corelink_readonly
  expires:  2026-04-24T15:42:18Z

You can also request via CLI:

tbcl-cli db-access request --connection demo-postgres --role readonly --duration 15m

Step 5 — Connect with the ephemeral credentials

PGPASSWORD='hg83K!f2nZjLp9qWb4vM7eRyUtX0sC5a' \
  psql -h localhost -U tbcl_a8f3c2d1_readonly -d postgres

postgres=> SELECT current_user;
#  tbcl_a8f3c2d1_readonly

postgres=> SELECT * FROM pg_tables WHERE schemaname = 'public';
# ...

postgres=> CREATE TABLE evil (x int);
# ERROR:  permission denied for schema public

The ephemeral user can read but not write, because the role template only grants SELECT. Anything higher needs a different role template with a different grant set.

Step 6 — Verify automatic revocation

Wait for the 15-minute TTL (or click Revoke Now in the UI to finish early). CoreLink's revocation scheduler runs REASSIGN OWNED BY ... TO corelink_provisioner; DROP OWNED BY ...; DROP ROLE tbcl_a8f3c2d1_readonly;. The user is gone from Postgres and cannot reconnect:

PGPASSWORD='hg83K!f2nZjLp9qWb4vM7eRyUtX0sC5a' \
  psql -h localhost -U tbcl_a8f3c2d1_readonly -d postgres
# psql: error: connection to server at "localhost" failed: FATAL: password authentication failed for user "tbcl_a8f3c2d1_readonly"

Even if the credential had leaked to a log file or a screenshot during the 15 minutes, it's useless now. Rotation isn't needed because revocation happened by design.

What's happening in the audit trail

Four events landed in the audit log for this flow, hash-chained per the tamper-evident audit log design:

That's the compliance evidence: who requested, when, with what privilege, for how long, and the revocation confirmation.

MySQL is the same story

Engine mysql on the connection, role templates map to MySQL roles or direct GRANT statements. Commands are slightly different (CREATE USER ... IDENTIFIED BY, GRANT ... ON db.* TO, DROP USER) but the CoreLink-facing UX is identical. MongoDB is on the roadmap.

Next steps