// What holds the store's read-only query to being read-only (novox/hq issue 193): a caller's // statement runs as the reader login and never as the admin, is sent as given with no transaction // wrapped around it as text, and comes back as rows keyed by their columns. The reader is made once, // as the admin, with every attribute stated; without its password the statement is refused. // // psql is a fake on PATH that records each call's user, options and statement, and answers in CSV // the way the real one does with -q. That the reader cannot write is the server's to enforce and is // proven against a real server, not here; this holds the module to asking for it. // Run against the compiled module (npm test builds first), the way the runtime loads it. import { test, before, after } from "node:test"; import assert from "node:assert/strict"; import { chmod, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { PostgresClient, READER } from "../dist/client.js"; let dir: string; let log: string; const originalPath = process.env.PATH; before(async () => { dir = await mkdtemp(join(tmpdir(), "postgres-reader-")); log = join(dir, "calls.jsonl"); // Records argv, the user it connected as and the options it was given; answers a role lookup // with no rows and anything else with a two-column result. await writeFile(join(dir, "psql"), `#!/usr/bin/env node const fs = require("node:fs"); const args = process.argv.slice(2); const at = (flag) => args[args.indexOf(flag) + 1]; fs.appendFileSync(${JSON.stringify(log)}, JSON.stringify({ user: at("-U"), database: at("-d"), sql: at("-c"), quiet: args.includes("-q"), password: process.env.PGPASSWORD, options: process.env.PGOPTIONS ?? "", }) + "\\n"); const sql = at("-c"); if (/FROM pg_roles/.test(sql)) process.stdout.write("?column?\\n"); else if (/^(CREATE|ALTER|GRANT)/.test(sql)) process.stdout.write(""); else process.stdout.write("name,n\\nalpha,1\\n\\"b,eta\\",2\\n"); `); await chmod(join(dir, "psql"), 0o755); process.env.PATH = `${dir}:${originalPath}`; }); after(async () => { process.env.PATH = originalPath; await rm(dir, { recursive: true, force: true }); }); async function calls(): Promise[]> { const text = await readFile(log, "utf8").catch(() => ""); await writeFile(log, ""); return text.split("\n").filter(Boolean).map((line) => JSON.parse(line)); } const conn = { host: "127.0.0.1", port: 5432, user: "postgres", password: "admin-secret" }; test("a caller's statement runs as the reader, as given, read-only, and comes back keyed by its columns", async () => { const client = new PostgresClient({ ...conn, readerPassword: "reader-secret" }); const statement = "COMMIT; DROP TABLE everything"; const result = await client.readOnlyQuery("inventory", statement); const made = await calls(); const asked = made.at(-1)!; assert.equal(asked.user, READER, "the statement never runs as the admin"); assert.equal(asked.password, "reader-secret"); assert.equal(asked.sql, statement, "sent as given: no transaction wrapped around it as text"); assert.equal(asked.database, "inventory"); assert.equal(asked.quiet, true, "no command tags, which came back as rows keyed by BEGIN"); assert.match(String(asked.options), /default_transaction_read_only=on/); assert.deepEqual(result.rows, [{ name: "alpha", n: "1" }, { name: "b,eta", n: "2" }]); assert.equal(result.command, "COMMIT"); }); test("the reader is made as the admin, with every attribute stated, once per process", async () => { const client = new PostgresClient({ ...conn, readerPassword: "reader-secret" }); await client.readOnlyQuery("inventory", "SELECT 1"); await client.readOnlyQuery("inventory", "SELECT 2"); const made = await calls(); const asAdmin = made.filter((c) => c.user === "postgres"); assert.ok(asAdmin.every((c) => c.password === "admin-secret")); const ddl = asAdmin.map((c) => String(c.sql)); const role = ddl.find((s) => s.startsWith(`CREATE ROLE "${READER}"`)); assert.ok(role, "made when it does not exist"); for (const attribute of ["LOGIN", "NOSUPERUSER", "NOCREATEDB", "NOCREATEROLE", "NOREPLICATION", "NOBYPASSRLS"]) { assert.match(role!, new RegExp(`\\b${attribute}\\b`)); } assert.ok(ddl.includes(`GRANT pg_read_all_data TO "${READER}"`), "reads everything and is granted nothing else"); assert.ok(ddl.some((s) => /default_transaction_read_only = on/.test(s))); assert.equal(ddl.filter((s) => s.startsWith("CREATE ROLE")).length, 1, "made once, not per call"); assert.equal(made.filter((c) => c.user === READER).length, 2); }); test("without the reader's password the statement is refused, and nothing runs as the admin", async () => { const client = new PostgresClient(conn); await assert.rejects(client.readOnlyQuery("inventory", "SELECT 1"), /refused rather than run as the admin/); assert.deepEqual(await calls(), []); }); test("the reader's password is read from the file the mesh delivers", async () => { const file = join(dir, "reader.secret"); await writeFile(file, "from-the-file\n"); const client = PostgresClient.fromEnv({ MESH_POSTGRES_HOST: "127.0.0.1", MESH_POSTGRES_PASSWORD: "admin-secret", MESH_POSTGRES_READER_PASSWORD_FILE: file, }); await client.readOnlyQuery("inventory", "SELECT 1"); const asked = (await calls()).at(-1)!; assert.equal(asked.password, "from-the-file"); });