Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/scripts/provision-rls-roles.test.mjs
T

297 lines
9.7 KiB
JavaScript

import assert from "node:assert/strict";
import { describe, it } from "vitest";
import {
MANAGED_ROLES,
PROVISION_DATABASE_URL_ENV,
ROLE_PASSWORD_ENV,
ROLE_PROVISIONING_SQL,
RlsRoleProvisioningError,
provisionRlsRoles,
readProvisioningConfig,
verifyProvisioningState,
} from "./provision-rls-roles.mjs";
const SECRETS = Object.freeze({
adminUrl: "postgresql://postgres:admin-secret@db.example.test:5432/hivegrid",
migrator: "migrator-secret-abcdefghijklmnopqrstuvwxyz",
app: "app-secret-abcdefghijklmnopqrstuvwxyz-1234",
portal: "portal-secret-abcdefghijklmnopqrstuvwxyz",
worker: "worker-secret-abcdefghijklmnopqrstuvwxyz",
authentikJit: "authentik-jit-secret-abcdefghijklmnop",
});
function validEnv(overrides = {}) {
return {
[PROVISION_DATABASE_URL_ENV]: SECRETS.adminUrl,
[ROLE_PASSWORD_ENV.hivegrid_migrator]: SECRETS.migrator,
[ROLE_PASSWORD_ENV.hivegrid_app]: SECRETS.app,
[ROLE_PASSWORD_ENV.hivegrid_portal]: SECRETS.portal,
[ROLE_PASSWORD_ENV.hivegrid_worker]: SECRETS.worker,
[ROLE_PASSWORD_ENV.hivegrid_authentik_jit]: SECRETS.authentikJit,
...overrides,
};
}
function validRoles() {
return [
{
roleName: "hivegrid_app",
canLogin: true,
isSuperuser: false,
canCreateDb: false,
canCreateRole: false,
inherits: false,
replicates: false,
bypassesRls: false,
passwordStateValid: true,
},
{
roleName: "hivegrid_authentik_jit",
canLogin: true,
isSuperuser: false,
canCreateDb: false,
canCreateRole: false,
inherits: false,
replicates: false,
bypassesRls: false,
passwordStateValid: true,
},
{
roleName: "hivegrid_migrator",
canLogin: true,
isSuperuser: false,
canCreateDb: false,
canCreateRole: false,
inherits: false,
replicates: false,
bypassesRls: false,
passwordStateValid: true,
},
{
roleName: "hivegrid_portal",
canLogin: true,
isSuperuser: false,
canCreateDb: false,
canCreateRole: false,
inherits: false,
replicates: false,
bypassesRls: false,
passwordStateValid: true,
},
{
roleName: "hivegrid_rls_definer",
canLogin: false,
isSuperuser: false,
canCreateDb: false,
canCreateRole: false,
inherits: false,
replicates: false,
bypassesRls: true,
passwordStateValid: true,
},
{
roleName: "hivegrid_worker",
canLogin: true,
isSuperuser: false,
canCreateDb: false,
canCreateRole: false,
inherits: false,
replicates: false,
bypassesRls: false,
passwordStateValid: true,
},
];
}
function validMemberships() {
return [
{
grantedRole: "hivegrid_rls_definer",
memberRole: "hivegrid_migrator",
adminOption: false,
inheritOption: false,
setOption: true,
},
];
}
class FakeClient {
constructor({ superuser = true, failProvisioning = false, ownership } = {}) {
this.superuser = superuser;
this.failProvisioning = failProvisioning;
this.ownership = ownership ?? {
databaseOwner: "hivegrid_migrator",
schemaOwner: "hivegrid_migrator",
unexpectedOwnerCount: 0,
};
this.calls = [];
this.connected = false;
this.ended = false;
}
async connect() {
this.connected = true;
}
async end() {
this.ended = true;
}
async query(text, values) {
this.calls.push({ text, values });
if (text.includes('current_user AS "roleName"')) {
return { rows: [{ roleName: "postgres", isSuperuser: this.superuser }] };
}
if (text === ROLE_PROVISIONING_SQL && this.failProvisioning) {
throw new Error(`database rejected ${SECRETS.adminUrl}`);
}
if (text.includes('rolname AS "roleName"') && text.includes("FROM pg_authid")) {
return { rows: validRoles() };
}
if (text.includes('granted.rolname AS "grantedRole"')) {
return { rows: validMemberships() };
}
if (text.includes('AS "databaseOwner"')) {
return { rows: [this.ownership] };
}
return { rows: [] };
}
}
describe("provision-rls-roles", () => {
it("requires an explicit admin URL and five long, distinct passwords", () => {
assert.throws(() => readProvisioningConfig({}), new RegExp(PROVISION_DATABASE_URL_ENV));
assert.throws(() => readProvisioningConfig(validEnv({ [ROLE_PASSWORD_ENV.hivegrid_app]: "short" })), new RegExp(ROLE_PASSWORD_ENV.hivegrid_app));
assert.throws(() => readProvisioningConfig(validEnv({ [ROLE_PASSWORD_ENV.hivegrid_worker]: SECRETS.app })), /distinct password/);
assert.throws(() => readProvisioningConfig(validEnv({ [ROLE_PASSWORD_ENV.hivegrid_portal]: "short" })), new RegExp(ROLE_PASSWORD_ENV.hivegrid_portal));
assert.throws(
() => readProvisioningConfig(validEnv({ [ROLE_PASSWORD_ENV.hivegrid_authentik_jit]: "short" })),
new RegExp(ROLE_PASSWORD_ENV.hivegrid_authentik_jit)
);
});
it("never embeds password inputs in the role DDL", () => {
for (const secret of [SECRETS.migrator, SECRETS.app, SECRETS.portal, SECRETS.worker, SECRETS.authentikJit]) {
assert.doesNotMatch(ROLE_PROVISIONING_SQL, new RegExp(secret));
}
assert.match(ROLE_PROVISIONING_SQL, /hivegrid_migrator LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS/);
assert.match(ROLE_PROVISIONING_SQL, /hivegrid_portal LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS/);
assert.match(ROLE_PROVISIONING_SQL, /hivegrid_authentik_jit LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION NOBYPASSRLS/);
assert.match(ROLE_PROVISIONING_SQL, /hivegrid_rls_definer NOLOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOREPLICATION BYPASSRLS PASSWORD NULL/);
assert.match(ROLE_PROVISIONING_SQL, /GRANT hivegrid_rls_definer TO hivegrid_migrator/);
assert.match(ROLE_PROVISIONING_SQL, /WITH ADMIN FALSE, INHERIT FALSE, SET TRUE/);
});
it("provisions atomically, binds passwords as query parameters, and emits only a bounded result", async () => {
const client = new FakeClient();
let output = "";
await provisionRlsRoles({
env: validEnv(),
clientFactory: () => client,
stdout: { write: (value) => (output += value) },
});
assert.equal(client.connected, true);
assert.equal(client.ended, true);
assert.ok(client.calls.some((call) => call.text === "BEGIN"));
assert.ok(client.calls.some((call) => call.text === "COMMIT"));
assert.ok(!client.calls.some((call) => call.text === "ROLLBACK"));
const passwordBinding = client.calls.find((call) => call.text.includes("hivegrid.provision.migrator_password"));
assert.deepEqual(passwordBinding.values, [SECRETS.migrator, SECRETS.app, SECRETS.portal, SECRETS.worker, SECRETS.authentikJit]);
for (const call of client.calls) {
for (const secret of Object.values(SECRETS)) {
assert.doesNotMatch(call.text, new RegExp(secret));
}
}
assert.equal(output, "PASS HiveGrid PostgreSQL roles provisioned and verified\n");
for (const secret of Object.values(SECRETS)) {
assert.doesNotMatch(output, new RegExp(secret));
}
});
it("fails before a transaction when the provisioning connection is not a superuser", async () => {
const client = new FakeClient({ superuser: false });
await assert.rejects(
provisionRlsRoles({ env: validEnv(), clientFactory: () => client }),
new RegExp(`${PROVISION_DATABASE_URL_ENV} must authenticate as a PostgreSQL superuser`)
);
assert.ok(!client.calls.some((call) => call.text === "BEGIN"));
assert.equal(client.ended, true);
});
it("rolls back and suppresses database error details", async () => {
const client = new FakeClient({ failProvisioning: true });
let error;
try {
await provisionRlsRoles({ env: validEnv(), clientFactory: () => client });
} catch (caught) {
error = caught;
}
assert.ok(error instanceof RlsRoleProvisioningError);
assert.match(error.message, /inspect restricted database logs/);
assert.doesNotMatch(error.message, /admin-secret|db\.example\.test/);
assert.ok(client.calls.some((call) => call.text === "ROLLBACK"));
assert.equal(client.ended, true);
});
it("fails closed on any role, membership, or ownership drift", () => {
assert.throws(
() =>
verifyProvisioningState({
roles: validRoles().map((role) => (role.roleName === "hivegrid_app" ? { ...role, bypassesRls: true } : role)),
memberships: validMemberships(),
ownership: {
databaseOwner: "hivegrid_migrator",
schemaOwner: "hivegrid_migrator",
unexpectedOwnerCount: 0,
},
}),
/hivegrid_app does not match/
);
assert.throws(
() =>
verifyProvisioningState({
roles: validRoles(),
memberships: [...validMemberships(), { grantedRole: "admin", memberRole: "hivegrid_app" }],
ownership: {
databaseOwner: "hivegrid_migrator",
schemaOwner: "hivegrid_migrator",
unexpectedOwnerCount: 0,
},
}),
/unexpected membership/
);
assert.throws(
() =>
verifyProvisioningState({
roles: validRoles(),
memberships: validMemberships(),
ownership: {
databaseOwner: "postgres",
schemaOwner: "hivegrid_migrator",
unexpectedOwnerCount: 1,
},
}),
/not exclusively owned/
);
});
it("defines exactly the six managed role names", () => {
assert.deepEqual(MANAGED_ROLES, [
"hivegrid_migrator",
"hivegrid_app",
"hivegrid_portal",
"hivegrid_worker",
"hivegrid_authentik_jit",
"hivegrid_rls_definer",
]);
});
});