Authentication
Redis-style AUTH backed by PostgreSQL LOGIN roles.
PostRESP accepts Redis-style AUTH and validates credentials against PostgreSQL
LOGIN roles. Wire auth is off by default (like Redis with no
requirepass).
Warning — Docker defaults: The image listens on
0.0.0.0:6379with wire auth off. Publishing-p 6379:6379exposes an unauthenticated RESP store (same stance as the official Redis image). If you do not setPOSTRESP_PASSWORD, the entrypoint generates a random secret on first start, prints it once to the container log, and persists it on the data volume — use that value when you later enableRequireClientAuth. Standalone / JSON still defaults toPassword: postresp; the gateway warns if wire auth is on with that built-in secret.
Credentials
| Role | Purpose | Used by gateway? | Defaults |
|---|---|---|---|
postresp |
RESP default user | Yes | Username postresp. Docker: random POSTRESP_PASSWORD if unset (see below). JSON / standalone: Password: postresp |
Other LOGIN roles |
Per-app RESP users | Yes — AUTH user pass |
created with pgresp.create_user |
postgres |
SQL admin (psql, migrations) |
No | POSTGRES_USER / POSTGRES_PASSWORD |
The gateway never connects as the SQL superuser.
Docker: generated secret when unset
Priority for the RESP wire secret:
POSTRESP_PASSWORD/VALKEY_PASSWORD/REDIS_PASSWORD*_PASSWORD_FILE- Persisted file
/var/lib/postgresql/.postresp_password(from a previous start) - Generate a random 32-byte secret, write that file (
chmod 600), print it once to stderr, then continue
Wire auth stays off until you enable it; the secret is still used for the
postresp Postgres LOGIN role. Override with an explicit env var anytime (then
pgresp.set_password if the role already exists with a different secret).
Password name map (easy to mix up)
| Name | Layer | Meaning |
|---|---|---|
POSTRESP_PASSWORD |
env (preferred) | RESP default-user wire secret |
VALKEY_PASSWORD / REDIS_PASSWORD |
env fallbacks | Same wire secret if POSTRESP_PASSWORD unset |
Password |
SetupConfiguration.json |
Same wire secret |
Username |
SetupConfiguration.json |
RESP default role (POSTRESP_USERNAME, default postresp) |
POSTGRES_PASSWORD |
official Postgres image | SQL superuser only — not read by the gateway |
On first init, if POSTGRES_PASSWORD is unset, the entrypoint may seed it from
the RESP secret so psql as postgres works. That copy is one-way; the gateway
still only reads POSTRESP_PASSWORD / Password.
Default database is postgres (official image default when POSTGRES_DB is
unset). Keys live in schema pgresp.
AUTH
Redis ACL-style usernames: omit the user, or use the literal name default, to
mean the configured default role (POSTRESP_USERNAME, default postresp).
| Form | Meaning |
|---|---|
AUTH <password> |
Password-only (requirepass); authenticates as the default role |
AUTH default <password> |
Same default role (default is an alias, not a Postgres role name) |
AUTH postresp <password> |
Explicit default role (when POSTRESP_USERNAME is postresp) |
AUTH app <password> |
Extra LOGIN role |
# Prefer env over -a (avoids password in `ps` / shell history)
export REDISCLI_AUTH="$POSTRESP_PASSWORD"
redis-cli -p 6379 PING
redis-cli -p 6379
AUTH "$POSTRESP_PASSWORD"
AUTH default "$POSTRESP_PASSWORD"
AUTH postresp "$POSTRESP_PASSWORD"
# Empty-user URLs work with many clients; some Redis 6+ libs prefer `default`:
REDIS_URL=redis://:${POSTRESP_PASSWORD}@127.0.0.1:6379
REDIS_URL=redis://default:${POSTRESP_PASSWORD}@127.0.0.1:6379
REDIS_URL=redis://postresp:${POSTRESP_PASSWORD}@127.0.0.1:6379
When wire auth is on, unauthenticated commands return
-NOAUTH Authentication required. Wrong credentials return
WRONGPASS invalid username-password pair.
When wire auth is off, clients need not AUTH, but AUTH still validates
credentials (wrong password → WRONGPASS). That differs from classic Redis
“no password configured” rejecting AUTH outright.
Postgres backends for the default role are pooled (deadpool, one LOGIN).
PING and other protocol-only commands do not check out a backend. Extra
AUTH user pass roles still open a dedicated session — a pool is one
Postgres LOGIN, so those users cannot share the default-role pool.
Enabling wire auth
RequireClientAuth defaults to false. Setting a password alone does not
require AUTH. Enable wire auth with either:
JSON (preferred in config files):
{
"RequireClientAuth": true,
"Password": "long-random-secret"
}
POSTRESP_PASSWORD=long-random-secret
Env alone (Redis/Valkey image compat):
ALLOW_EMPTY_PASSWORD=no
POSTRESP_PASSWORD=long-random-secret
ALLOW_EMPTY_PASSWORD=no forces wire auth on at startup even when JSON has
RequireClientAuth: false. Always set a non-default POSTRESP_PASSWORD when
auth is on.
Auth knobs — which wins?
Applied at config load (env overrides JSON), then at runtime via CONFIG SET:
| Source | Effect |
|---|---|
RequireClientAuth in JSON |
Startup “must AUTH” when ALLOW_EMPTY_PASSWORD is unset (false by default) |
ALLOW_EMPTY_PASSWORD=yes |
Forces wire auth off, even if JSON has RequireClientAuth: true |
ALLOW_EMPTY_PASSWORD=no |
Forces wire auth on, even if JSON has RequireClientAuth: false |
CONFIG SET requirepass <secret> |
Wire auth on; updates in-process secret + default role’s Postgres LOGIN |
CONFIG SET requirepass "" |
Wire auth off for this process; LOGIN + memory revert to the startup secret — not “no password” |
CONFIG SET requirepass does not rewrite env / JSON. After restart, startup
config wins again.
Example footgun:
- Cold start with
RequireClientAuth: true,Password: startup-secret. CONFIG SET requirepass rotated-secret— clients must userotated-secret.CONFIG SET requirepass ""— wire auth off; role password isstartup-secretagain (not empty). Anyone who still knowsstartup-secretcan authenticate once auth is turned back on.- Process restart with the same JSON — auth is on again with
startup-secret(the""clear is gone). Persist rotations by updatingPOSTRESP_PASSWORD/Password(andpgresp.set_password) before restart.
Bind address (Docker = Redis image tradeoff)
The Docker image listens on all interfaces (UseLocalHost: false →
0.0.0.0), with wire auth off and no protected mode. Same stance as the
official Redis image: bind wide open inside the container; let Docker
networking be the firewall. Exposure is controlled by whether you publish the
port (-p 6379:6379) or only attach an internal network.
If you do publish to an internet-facing host, set a password and turn on wire auth.
Optional escape hatch: POSTRESP_ALLOW_REMOTE_CONNECTIONS=false binds
loopback only (breaks published-port / cross-container access the same way a
custom bind 127.0.0.1 redis.conf does). The Rust library default is also
loopback-only — for standalone make run, not for the image.
How AUTH checks passwords (pg_hba)
After first init, the image’s pg_hba.conf is effectively:
local all postgres trust
local all all scram-sha-256
host all all 127.0.0.1/32 trust # from official image / initdb
host all all ::1/128 trust
host all all all scram-sha-256 # remote TCP
Init script docker/init-pg-hba-scram.sh (installed as
/docker-entrypoint-initdb.d/00-pg-hba-scram.sh) only rewrites the blanket
local all all trust line. Host / TCP lines are left as the official image
wrote them (loopback trust, everything else scram-sha-256).
The in-process gateway dials Postgres on the unix socket
(PostgresHostName: /var/run/postgresql):
- Default-user
AUTH— compared in-process toPOSTRESP_PASSWORD/Password, then the gateway opens a session as that role. - Extra-user
AUTH— Postgres verifies the LOGIN password over the socket (local … scram-sha-256).
Local SQL admin threat model
Inside the container, local … postgres trust and loopback host … trust mean
any process that can reach the socket or 127.0.0.1:5432 is SQL superuser
without a password. That matches the official image’s localhost behavior; the
socket is not shared to the host unless you bind-mount it. Remote TCP still
requires POSTGRES_PASSWORD (catch-all scram).
Prefer socket admin from inside the container:
docker exec -i postresp psql -U postgres -d postgres
Additional RESP users
docker exec -i postresp psql -U postgres -d postgres -c \
"SELECT pgresp.create_user('app', 'app-secret');"
docker exec -i postresp psql -U postgres -d postgres -c \
"GRANT USAGE ON SCHEMA pgresp TO app;"
pgresp.create_user / pgresp.set_password take the plain wire secret and
encode it for Postgres LOGIN internally — do not pass base64 yourself.
redis-cli -p 6379
AUTH app app-secret
Rotating passwords
Postgres stores role passwords in the catalog. Container env is applied on
process start; POSTGRES_* init vars apply only on first init of an empty
data directory. Changing env alone does not rewrite roles on an existing
volume.
Default RESP role
- Choose a new long random secret.
- Update Postgres (as
postgres):
docker exec -i postresp psql -U postgres -d postgres -c \
"SELECT pgresp.set_password('postresp', 'new-secret');"
- Set
POSTRESP_PASSWORD=new-secret(andPasswordif you manage JSON) and restart the gateway. - Update clients.
Or from an authenticated RESP session: CONFIG SET requirepass new-secret
(still update POSTRESP_PASSWORD for the next cold start).
Extra RESP users
docker exec -i postresp psql -U postgres -d postgres -c \
"SELECT pgresp.set_password('app', 'new-app-secret');"
No gateway restart required; update those clients.
SQL superuser
docker exec -i postresp psql -U postgres -d postgres -c \
"ALTER ROLE postgres PASSWORD 'new-sql-secret';"
The gateway does not use this password. For remote psql -h …, set
PGPASSWORD / .pgpass to match; loopback inside the container remains trust.
Redis passwords are binary-safe; Postgres role passwords are not. For each RESP wire secret, PostRESP sets the role’s LOGIN password string to standard Base64 (RFC 4648 §4:
A–Za–z0–9+/with=padding — not base64url). Postgres then stores a SCRAM verifier of that string — not the base64 plaintext. Clients and env always use the plain wire secret (gateway path supports NULs / non-UTF-8; SQL helperspgresp.create_user/pgresp.set_passwordtake UTF-8 text and encode internally).
Redis requirepass / CONFIG SET map
| Redis | PostRESP |
|---|---|
requirepass secret |
Default role secret + wire auth on |
AUTH secret / -a |
Default role + that secret |
AUTH default secret |
default → POSTRESP_USERNAME (default postresp) |
CONFIG SET requirepass secret |
Updates default role LOGIN + in-process secret; wire auth on |
CONFIG SET requirepass "" |
Wire auth off; reverts LOGIN + memory to the startup secret (not empty) |
RESET
Matches Redis RESET (6.2+): clears subscription state, client name, and
authentication. With wire auth on, the connection must AUTH again.
Not implemented
- Redis ACL command family (
ACL SETUSER, …) - Sentinel / Cluster auth APIs
- TLS termination inside the gateway