fix(db): implementa identidade persistente segura para homologacao
This commit is contained in:
1 parent
2c8c5d9507
commit
a4f0a2f649
11 files changed
+1146
-106
No files matched your search
@@ -9,9 +9,13 @@ import {
|
||||
resolvePoolMax,
|
||||
scrubDbError,
|
||||
} from "@/infra/db/connection-policy";
|
||||
import { evaluateAdminIdentity, evaluateRuntimeRole, type AdminAttestation } from "@/infra/db/target-guard";
|
||||
import { readFileSync, readdirSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { assertInitAuthorized, projectIdentityDefinitionSql, projectIdentityRevertSql } from "@/infra/db/project-identity";
|
||||
import { evaluateAdminIdentity, evaluateProjectIdentity, evaluateRuntimeRole, type AdminAttestation } from "@/infra/db/target-guard";
|
||||
|
||||
const REF = "abcdefghijabcdefghij";
|
||||
const REF_COM_DIGITO = "abcd1234abcd1234wxyz";
|
||||
const DASHBOARD_HOST = "aws-1-us-west-2.pooler.supabase.com";
|
||||
|
||||
function adminUrl(user = `postgres.${REF}`, sslmode = "verify-full", host = DASHBOARD_HOST): string {
|
||||
@@ -26,10 +30,8 @@ function attestation(overrides: Partial<AdminAttestation> = {}): AdminAttestatio
|
||||
rolbypassrls: true,
|
||||
hasAuth: true,
|
||||
hasStorage: true,
|
||||
databaseSettings: new Map([
|
||||
["app.hml_project", AUTHORIZED_HML_PROJECT],
|
||||
["app.hml_project_ref", REF],
|
||||
]),
|
||||
projectName: AUTHORIZED_HML_PROJECT,
|
||||
projectRef: REF,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -44,24 +46,9 @@ describe("barreira do banco de homologação", () => {
|
||||
});
|
||||
|
||||
it("reconhece o postgres do Supabase gerenciado sem tratar BYPASSRLS como autorização", () => {
|
||||
const settings = new Map([
|
||||
["app.settings.jwt_exp", "3600"],
|
||||
["app.hml_project", AUTHORIZED_HML_PROJECT],
|
||||
["app.hml_project_ref", REF],
|
||||
]);
|
||||
expect(() => evaluateAdminIdentity(attestation({ rolsuper: false, rolbypassrls: true }), env)).not.toThrow();
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(attestation({ rolsuper: false, rolbypassrls: true, databaseSettings: settings }), env),
|
||||
).not.toThrow();
|
||||
expect(settings.get("app.settings.jwt_exp")).toBe("3600");
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(
|
||||
attestation({
|
||||
rolsuper: false,
|
||||
rolbypassrls: true,
|
||||
databaseSettings: new Map([["app.settings.jwt_exp", "3600"]]),
|
||||
}),
|
||||
env,
|
||||
),
|
||||
evaluateAdminIdentity(attestation({ projectName: null, projectRef: null, rolsuper: false, rolbypassrls: true }), env),
|
||||
).toThrow(/marcador_ausente/);
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(attestation({ sessionUser: "supabase_admin", rolsuper: false, rolbypassrls: true }), env),
|
||||
@@ -71,6 +58,48 @@ describe("barreira do banco de homologação", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("aceita Reference ID com dígito e recusa GUC ou variável como única prova", () => {
|
||||
const envComDigito = { ...env, ADAPTER_DB_PROJECT_REF: REF_COM_DIGITO };
|
||||
expect(() =>
|
||||
evaluateProjectIdentity({ projectName: AUTHORIZED_HML_PROJECT, projectRef: REF_COM_DIGITO }, envComDigito),
|
||||
).not.toThrow();
|
||||
expect(() => evaluateProjectIdentity({ projectName: null, projectRef: null }, envComDigito)).toThrow(/marcador_ausente/);
|
||||
expect(() => evaluateProjectIdentity({ projectName: AUTHORIZED_HML_PROJECT, projectRef: REF }, envComDigito)).toThrow(
|
||||
/marcador_diverge/,
|
||||
);
|
||||
expect(() => evaluateAdminIdentity(attestation(), { ...env, ADAPTER_DB_PROJECT_REF: "ABCD1234ABCD1234WXYZ" })).toThrow(
|
||||
/ref_ausente/,
|
||||
);
|
||||
expect(() => assertInitAuthorized({ ADAPTER_ENV: "staging" })).toThrow(/init_nao_autorizada/);
|
||||
expect(() => assertInitAuthorized({ ADAPTER_ENV: "development", ADAPTER_INIT_PROJECT_IDENTITY: "1" })).toThrow(
|
||||
/init_fora_de_homologacao/,
|
||||
);
|
||||
expect(() => assertInitAuthorized({ ADAPTER_ENV: "staging", ADAPTER_INIT_PROJECT_IDENTITY: "1" })).not.toThrow();
|
||||
const sql = projectIdentityDefinitionSql(REF_COM_DIGITO);
|
||||
expect(sql).toContain(REF_COM_DIGITO);
|
||||
expect(sql.split("do $identity$")).toHaveLength(2);
|
||||
expect(sql).toContain("security definer");
|
||||
expect(sql).toContain("set search_path = pg_catalog");
|
||||
expect(sql).toContain("revoke all on table adaptador_guard.project_identity from service_role");
|
||||
expect(sql.toLowerCase()).not.toContain("jwt_exp");
|
||||
expect(sql.toLowerCase()).not.toContain("alter database");
|
||||
expect(sql).not.toContain("auth.");
|
||||
expect(sql).not.toContain("storage.");
|
||||
const prepared = readFileSync(path.join(process.cwd(), "scripts/sql/init-project-identity.sql"), "utf8");
|
||||
expect(prepared).toContain("<<PROJECT_REF>>");
|
||||
expect(prepared).toContain("security definer");
|
||||
expect(prepared.toLowerCase()).not.toContain("cascade");
|
||||
const revert = projectIdentityRevertSql();
|
||||
const revertFile = readFileSync(path.join(process.cwd(), "scripts/sql/revert-project-identity.sql"), "utf8");
|
||||
expect(revert).toContain("drop schema adaptador_guard restrict;");
|
||||
expect(revert.toLowerCase()).not.toContain("cascade");
|
||||
expect(revertFile).toContain("drop schema adaptador_guard restrict;");
|
||||
expect(revertFile.toLowerCase()).not.toContain("cascade");
|
||||
const initScript = readFileSync(path.join(process.cwd(), "scripts/init-project-identity.ts"), "utf8");
|
||||
expect(initScript).not.toContain("console.log(databaseUrl");
|
||||
expect(initScript).not.toContain("console.log(process.env");
|
||||
});
|
||||
|
||||
it("exige o host do Dashboard e recusa índice, região ou domínio diferentes", () => {
|
||||
expect(POOLER_HOST_PATTERN.test(DASHBOARD_HOST)).toBe(true);
|
||||
expect(expectedPoolerHost(env)).toBe(DASHBOARD_HOST);
|
||||
@@ -90,18 +119,19 @@ describe("barreira do banco de homologação", () => {
|
||||
|
||||
it("recusa identidade ausente, marcador ausente e marcador divergente", () => {
|
||||
expect(() => evaluateAdminIdentity(attestation(), {})).toThrow(DatabaseGuardError);
|
||||
expect(() => evaluateAdminIdentity(attestation({ databaseSettings: new Map() }), env)).toThrow(/marcador_ausente/);
|
||||
expect(() =>
|
||||
evaluateAdminIdentity(
|
||||
attestation({
|
||||
databaseSettings: new Map([
|
||||
["app.hml_project", AUTHORIZED_HML_PROJECT],
|
||||
["app.hml_project_ref", "zzzzzzzzzzzzzzzzzzzz"],
|
||||
]),
|
||||
}),
|
||||
env,
|
||||
),
|
||||
).toThrow(/marcador_diverge/);
|
||||
expect(() => evaluateAdminIdentity(attestation({ projectName: null, projectRef: null }), env)).toThrow(/marcador_ausente/);
|
||||
expect(() => evaluateAdminIdentity(attestation({ projectRef: "zzzzzzzzzzzzzzzzzzzz" }), env)).toThrow(/marcador_diverge/);
|
||||
expect(() => evaluateProjectIdentity({ projectName: "outro-projeto", projectRef: REF }, env)).toThrow(/marcador_diverge/);
|
||||
const migrations = readdirSync(path.join(process.cwd(), "migrations"), { recursive: true }).filter(
|
||||
(file) => typeof file === "string" && file.endsWith(".sql"),
|
||||
);
|
||||
for (const file of migrations) {
|
||||
const sql = readFileSync(path.join(process.cwd(), "migrations", file), "utf8");
|
||||
expect(sql, file).not.toContain("adaptador_guard");
|
||||
}
|
||||
for (const file of ["scripts/migrate.ts", "scripts/seed-staging.ts", "scripts/seed-dev.ts", "scripts/set-flag.ts"]) {
|
||||
expect(readFileSync(path.join(process.cwd(), file), "utf8"), file).not.toContain("installProjectIdentity");
|
||||
}
|
||||
});
|
||||
|
||||
it("não aceita só o nome postgres, o host ou um usuário sem o ref", () => {
|
||||
@@ -155,6 +185,7 @@ describe("barreira do banco de homologação", () => {
|
||||
expect(() =>
|
||||
evaluateRuntimeRole({ sessionUser: "adaptador_web", rolsuper: false, rolbypassrls: false }, "web", env),
|
||||
).not.toThrow();
|
||||
expect(() => evaluateProjectIdentity({ projectName: null, projectRef: null }, env)).toThrow(/marcador_ausente/);
|
||||
expect(() =>
|
||||
evaluateRuntimeRole({ sessionUser: `adaptador_worker.${REF}`, rolsuper: false, rolbypassrls: false }, "worker", env),
|
||||
).not.toThrow();
|
||||
|
||||
Reference in new issue
Block a user