give each member a postgres role instead of a docker daemon

A Postgres login role named the same as their Linux account, with CREATEDB, plus a
~/.pgpass so psql never prompts. This is what replaces rootless Docker: the case it
was really there for was "let me run a database to develop against", and a container
per member answered it with a daemon, an image cache and a subuid range each.

Measured on postgres:18-alpine before writing any of it, because three things I
asserted turned out to be wrong:

  - a fresh LOGIN role CAN connect to `officer` (datacl NULL = PUBLIC has CONNECT),
    but CANNOT read any application table — privileges are owner-only, so
    has_table_privilege('users','UPDATE') is false. The capability model was never
    reachable from here.
  - the `trust` line in pg_hba does not cover host connections: Docker's NAT rewrites
    the source, so they fall through to scram-sha-256. Verified with a wrong password.
  - revoking from the ROLE does nothing. Privileges are additive and there is no DENY;
    only revoking from PUBLIC is a lock.

So ensureAppDatabaseClosed revokes CONNECT+TEMPORARY on the platform's own database
from PUBLIC, and it runs inside provisionPostgresRole rather than in the setup script
— an install set up before today, or restored from a dump, then still cannot end up
with a member who can connect to `officer`.

Password is generated per member, 40 chars, rejection-sampled over an alphanumeric
alphabet: CREATE ROLE is a utility statement and cannot take a bind parameter, so the
safety comes from the alphabet rather than from escaping. Not stored anywhere — it
lives in their 600 ~/.pgpass, the same posture as their SSH key, where we keep only
the public half. Only (re)set when .pgpass is missing, so a reprovision does not
rotate a credential they may have pasted into an app config.

KNOWN RESIDUE, not handled: a database one member creates is metadata-readable by
another. datacl is not inherited from the template (measured: closing template1 and
creating from it still produced NULL), and CREATE DATABASE fires no event trigger, so
nothing can close it at creation. A second member can read table and column NAMES from
the catalogue. They cannot read a row and cannot create anything. Closing it needs a
sweep or a pg_hba rule per member; both are decisions, not details.

Verified live against the running cluster: role creation, refusal on `officer`,
creating and using two databases, and the DROP ... WITH (FORCE) teardown. All probe
roles and databases dropped afterwards.

NOT verified: bunx tsgo, still — node_modules is empty in this tree. The drizzle
return shape was checked by reading PostgresJsQueryResultHKT (RowList<T[]>, extends
Array) rather than by running it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 12:14:02 +00:00
co-authored by Claude Opus 5
parent 41cdd1e63a
commit 2273941e71
3 changed files with 312 additions and 6 deletions
+24 -4
View File
@@ -105,11 +105,31 @@ else
%F{blue}❯%f '
fi
# ── Docker ──
# Your own rootless daemon, if Officer provisioned one. Containers you start run as your account in your own
# user namespace — root inside them is you outside them, and you cannot see anyone else's containers.
# ── Postgres ──
# Officer gave your account a Postgres role named the same as your login, and a ~/.pgpass holding its
# password — so psql never prompts you and you never have to know it.
#
# Set from $XDG_RUNTIME_DIR rather than a hard-coded uid so this line is the same in every account's file.
# createdb myapp as many as you like; each one is yours
# psql myapp connect to one
# dropdb myapp
#
# Nobody else can read your data, and you cannot reach Officer's own database.
#
# PGHOST is set because Postgres runs in a container published on loopback: there is no unix socket on
# this machine, and without this psql fails with "No such file or directory", which reads like Postgres
# is not installed rather than like it is one flag away.
#
# Read out of ~/.pgpass rather than hard-coded, for the same reason the PATH lines above are $HOME-relative
# — one fact, in one place, that cannot come to disagree with itself.
if [ -f "$HOME/.pgpass" ]; then
export PGHOST="${$(head -1 "$HOME/.pgpass")%%:*}"
export PGPORT="$(head -1 "$HOME/.pgpass" | cut -d: -f2)"
fi
# ── Docker ──
# Officer no longer provisions a rootless daemon per account (2026-08-13) — a Postgres role covers the
# case it was really there for, at none of the cost. Kept because it costs one stat and does the right
# thing if you install rootless Docker yourself.
if [ -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/docker.sock" ]; then
export DOCKER_HOST="unix://${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/docker.sock"
fi