feat: restrict customer DB user permissions on provision
Some checks failed
Build & Release / build (push) Failing after 1m3s
Some checks failed
Build & Release / build (push) Failing after 1m3s
This commit is contained in:
3
bun.lock
3
bun.lock
@@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"@fastify/static": "^9.0.0",
|
||||
"fastify": "^5.8.4",
|
||||
"postgres": "^3.4.8",
|
||||
"zod": "^4.3.6",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -121,6 +122,8 @@
|
||||
|
||||
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
|
||||
|
||||
"postgres": ["postgres@3.4.8", "", {}, "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg=="],
|
||||
|
||||
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||
|
||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"dependencies": {
|
||||
"@fastify/static": "^9.0.0",
|
||||
"fastify": "^5.8.4",
|
||||
"postgres": "^3.4.8",
|
||||
"zod": "^4.3.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export const config = {
|
||||
gitSshKey: process.env.GIT_SSH_KEY!,
|
||||
gitRepoUrl: process.env.GIT_REPO_URL ?? "ssh://git@git-ssh.lunarfront.tech/ryan/lunarfront-charts.git",
|
||||
dbUrl: process.env.DATABASE_URL!,
|
||||
doadminDbUrl: process.env.DOADMIN_DATABASE_URL!,
|
||||
};
|
||||
|
||||
for (const [key, val] of Object.entries(config)) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
import { createDatabase, createDatabaseUser, deleteDatabase, deleteDatabaseUser } from "../services/do";
|
||||
import { addCustomerToPool, removeCustomerFromPool } from "../services/pgbouncer";
|
||||
import { addCustomerChart, removeCustomerChart } from "../services/git";
|
||||
import { setupCustomerDatabase, teardownCustomerDatabase } from "../services/db";
|
||||
|
||||
const ProvisionSchema = z.object({
|
||||
name: z.string().min(2).max(32).regex(/^[a-z0-9-]+$/, "lowercase letters, numbers, and hyphens only"),
|
||||
@@ -21,6 +22,7 @@ export async function customerRoutes(app: FastifyInstance) {
|
||||
createDatabaseUser(slug),
|
||||
]);
|
||||
|
||||
await setupCustomerDatabase(slug, user.name);
|
||||
await addCustomerToPool(slug, user.password);
|
||||
addCustomerChart(slug, body.appVersion);
|
||||
|
||||
@@ -35,6 +37,7 @@ export async function customerRoutes(app: FastifyInstance) {
|
||||
|
||||
removeCustomerChart(slug);
|
||||
await removeCustomerFromPool(slug);
|
||||
await teardownCustomerDatabase(slug, slug);
|
||||
await Promise.all([
|
||||
deleteDatabase(slug),
|
||||
deleteDatabaseUser(slug),
|
||||
|
||||
37
src/services/db.ts
Normal file
37
src/services/db.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import postgres from "postgres";
|
||||
import { config } from "../lib/config";
|
||||
|
||||
// Runs setup SQL as doadmin against a specific database
|
||||
export async function setupCustomerDatabase(dbName: string, username: string) {
|
||||
const sql = postgres(config.doadminDbUrl.replace(/\/\w+(\?|$)/, `/${dbName}$1`), { max: 1 });
|
||||
|
||||
try {
|
||||
// Revoke all public access, then grant only to this user
|
||||
await sql.unsafe(`
|
||||
REVOKE ALL ON DATABASE "${dbName}" FROM PUBLIC;
|
||||
GRANT CONNECT ON DATABASE "${dbName}" TO "${username}";
|
||||
GRANT ALL PRIVILEGES ON DATABASE "${dbName}" TO "${username}";
|
||||
ALTER DATABASE "${dbName}" OWNER TO "${username}";
|
||||
`);
|
||||
|
||||
// Set default privileges so any tables the app creates are accessible to itself
|
||||
await sql.unsafe(`
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "${username}" IN SCHEMA public
|
||||
GRANT ALL ON TABLES TO "${username}";
|
||||
ALTER DEFAULT PRIVILEGES FOR ROLE "${username}" IN SCHEMA public
|
||||
GRANT ALL ON SEQUENCES TO "${username}";
|
||||
`);
|
||||
} finally {
|
||||
await sql.end();
|
||||
}
|
||||
}
|
||||
|
||||
export async function teardownCustomerDatabase(dbName: string, username: string) {
|
||||
// Reassign ownership back to doadmin before dropping
|
||||
const sql = postgres(config.doadminDbUrl.replace(/\/\w+(\?|$)/, `/${dbName}$1`), { max: 1 });
|
||||
try {
|
||||
await sql.unsafe(`REASSIGN OWNED BY "${username}" TO doadmin;`);
|
||||
} finally {
|
||||
await sql.end();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user