Keep role re-assertion within a non-superuser provisioner's rights, and test the second boot

This commit is contained in:
2026-08-19 00:44:49 +02:00
parent e66a8a3e89
commit 5589472e25
12 changed files with 173 additions and 63 deletions
+4 -12
View File
@@ -2,25 +2,20 @@
// alone — the only process holding superuser credentials, which is why the driver stops here.
import postgres from "postgres";
import { derivePassword, NAME_PREFIX, orphanNames, provisionSql, quoteIdentifier, storageName } from "./storage.ts";
import { derivePassword, orphanNames, provisionSql, storageName } from "./storage.ts";
export interface ProvisionOptions {
adminUrl: string; // needs CREATEDB + CREATEROLE, not superuser
connectionLimit: number;
// Databases to keep closed to PUBLIC on every run. init.sql seeds this for the Ory databases, but
// it runs once on an empty data dir — an existing volume would keep the default grant forever.
lockdownDatabases?: string[];
pluginIds: string[];
secret: string;
}
export interface ProvisionResult {
orphans: string[]; // provisioned once, but no installed plugin claims them any more
orphans: string[]; // a plugin_ database no installed plugin claims; reported, never dropped
provisioned: string[];
}
// Idempotent, and it drops nothing: an uninstalled plugin keeps its data until an operator removes
// it deliberately. Orphans are reported rather than removed, so nobody has to guess they exist.
export async function provisionStorage(options: ProvisionOptions): Promise<ProvisionResult> {
const sql = postgres(options.adminUrl, { connect_timeout: 10, max: 1, onnotice: () => {} });
try {
@@ -39,12 +34,9 @@ export async function provisionStorage(options: ProvisionOptions): Promise<Provi
for (const statement of plan) await sql.unsafe(statement); // provisionSql quotes what it interpolates
provisioned.push(name);
}
for (const database of options.lockdownDatabases ?? []) {
await sql.unsafe(`REVOKE CONNECT ON DATABASE ${quoteIdentifier(database)} FROM PUBLIC`);
}
const existing = await sql<{ datname: string }[]>`SELECT datname FROM pg_database WHERE starts_with(datname, ${NAME_PREFIX})`;
const existing = await sql<{ datname: string }[]>`SELECT datname FROM pg_database`;
return { orphans: orphanNames(existing.map((row) => row.datname), provisioned), provisioned };
} finally {
await sql.end();
await sql.end({ timeout: 5 }); // a wedged connection would otherwise hang the boot web waits on
}
}