Connect PostgreSQL with short-lived client certificates¶
Reach PostgreSQL, CloudNativePG included, with a client certificate from OpenBAO's PKI engine instead of a password. See OpenBAO and the wrapper reference.
Before you start¶
- The PKI's CA can differ from the CA that signed the server's certificate. Name the server's root in
sslrootcertorPGSSLROOTCERT. - PostgreSQL ignores revocation unless
ssl_crl_fileorssl_crl_diris set. A certificate works until it expires, so keepmax_ttlshort. - PostgreSQL 18 native OAuth does not work: the issuer serves no device authorization grant.
1. Run a command with a certificate¶
sluisctl psql --address https://openbao.example:8200 -ns staging -- -h db.example -d orders
sluisctl exchanges your sign-in for the openbao audience (--audience) and logs in to the jwt-roster mount in -ns. To log in at a parent namespace, use --login-ns (logins at a parent namespace). It then generates an ECDSA P-384 key locally and calls pki/sign/<role> (-role, default db-client). The key never leaves your machine.
The common name is your identity unless --common-name overrides it. A cached certificate is reused while it has five minutes of life left. The files client.crt, client.key and client-ca.crt are 0600 under <config>/credentials/<address-hash>/<ns>/<role>/. The command then runs with libpq variables set:
| Variable | Value |
|---|---|
PGSSLCERT, PGSSLKEY |
the certificate and key, always |
PGUSER |
the certificate's common name |
PGSSLMODE |
verify-full |
PGSSLROOTCERT |
client-ca.crt; skipped when the role returned no CA |
sluisctl sets the last three only when you have not: -U, user= or a service file wins. Other arguments pass through.
2. Commit a service file¶
Commit a secret-free pg_service.conf and point PGSERVICEFILE at it from devbox.json or .envrc.
# pg_service.conf, committed
[orders]
host=db.example
port=5432
dbname=orders
sslmode=verify-full
# sslrootcert=/path/to/server-ca.crt # only if the server's CA differs from the PKI's
export PGSERVICEFILE=$PWD/pg_service.conf
sluisctl psql --address https://openbao.example:8200 -ns staging -- service=orders
sluisctl never writes this file; it adds the certificate through the environment.
3. Grant access¶
OpenBAO policy gates access on the groups claim of the exchanged token. One group names one thing that may be minted (OpenBAO policy).
groups:
staging:db:orders:client: { members: [[email protected]] }
clients:
openbao:
kind: exchange
requires: [staging:db:orders:client]
The PKI role's ttl and max_ttl set the lifetime; no flag does. See OpenBAO manager side.
4. Configure the server¶
Trust the role's CA and map the common name to a database role: Configure PostgreSQL.
Verify¶
sluisctl psql --address https://openbao.example:8200 -ns staging -- -h db.example -d orders -c 'select current_user'
The result is the database role you connected as: the common name, or the -U role.
Decided in: ADR 0013.