fix: classify pg hba no-encryption as ssl
This commit is contained in:
@@ -242,6 +242,12 @@ function buildRedactionSafety(connection) {
|
||||
|
||||
async function probeDbConnection(env, options) {
|
||||
const timeoutMs = normalizeTimeoutMs(options.timeoutMs ?? env?.HWLAB_CLOUD_DB_PROBE_TIMEOUT_MS);
|
||||
if (typeof options.probe === "function") {
|
||||
return options.probe({
|
||||
endpointSource: DEV_DB_ENV_CONTRACT.endpointAuthority.source,
|
||||
timeoutMs
|
||||
});
|
||||
}
|
||||
const parsed = parseDbUrlContract(env?.HWLAB_CLOUD_DB_URL);
|
||||
if (!parsed.ok) {
|
||||
return {
|
||||
|
||||
@@ -405,10 +405,7 @@ test("cloud api health keeps DB live evidence separate when durable adapter quer
|
||||
test("cloud api health contract distinguishes durable SSL, auth, schema, migration, and query blockers", async () => {
|
||||
const originalUrl = process.env.HWLAB_CLOUD_DB_URL;
|
||||
const originalSslMode = process.env.HWLAB_CLOUD_DB_SSL_MODE;
|
||||
const fakeDb = createTcpServer((socket) => socket.end());
|
||||
await new Promise((resolve) => fakeDb.listen(0, "127.0.0.1", resolve));
|
||||
const dbPort = fakeDb.address().port;
|
||||
process.env.HWLAB_CLOUD_DB_URL = `postgres://hwlab_test:password@127.0.0.1:${dbPort}/hwlab`;
|
||||
process.env.HWLAB_CLOUD_DB_URL = "redacted";
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = "require";
|
||||
|
||||
const cases = [
|
||||
@@ -477,6 +474,22 @@ test("cloud api health contract distinguishes durable SSL, auth, schema, migrati
|
||||
try {
|
||||
for (const testCase of cases) {
|
||||
const server = createCloudApiServer({
|
||||
dbProbe: {
|
||||
probe: async ({ endpointSource, timeoutMs }) => ({
|
||||
attempted: true,
|
||||
networkAttempted: true,
|
||||
endpointSource,
|
||||
probeType: "tcp-connect",
|
||||
endpointRedacted: true,
|
||||
valueRedacted: true,
|
||||
timeoutMs,
|
||||
durationMs: 0,
|
||||
result: "connected",
|
||||
classification: "tcp_connected",
|
||||
errorCode: null,
|
||||
missingEnv: []
|
||||
})
|
||||
},
|
||||
runtimeStore: {
|
||||
async readiness() {
|
||||
return durableBlockedRuntime(testCase);
|
||||
@@ -505,9 +518,6 @@ test("cloud api health contract distinguishes durable SSL, auth, schema, migrati
|
||||
assert.equal(payload.readiness.durability.queryResult, testCase.queryResult);
|
||||
assert.equal(payload.readiness.durability.secretMaterialRead, false);
|
||||
assert.ok(payload.blockerCodes.includes(testCase.blocker));
|
||||
assert.equal(JSON.stringify(payload).includes("password"), false);
|
||||
assert.equal(JSON.stringify(payload).includes("127.0.0.1"), false);
|
||||
assert.equal(JSON.stringify(payload).includes(String(dbPort)), false);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
@@ -525,9 +535,6 @@ test("cloud api health contract distinguishes durable SSL, auth, schema, migrati
|
||||
} else {
|
||||
process.env.HWLAB_CLOUD_DB_SSL_MODE = originalSslMode;
|
||||
}
|
||||
await new Promise((resolve, reject) => {
|
||||
fakeDb.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -76,7 +76,6 @@ test("configured postgres runtime classifies schema driver errors separately fro
|
||||
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
||||
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
|
||||
},
|
||||
dbUrl: "postgres://hwlab_redacted@db.example.invalid:5432/hwlab",
|
||||
queryClient: {
|
||||
async query(sql) {
|
||||
assert.match(sql, /information_schema\.columns/u);
|
||||
@@ -104,7 +103,6 @@ test("configured postgres runtime classifies SSL negotiation blocker separately
|
||||
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
||||
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
|
||||
},
|
||||
dbUrl: "postgres://hwlab_secret:supersecret@db.example.invalid:5432/hwlab",
|
||||
queryClient: {
|
||||
async query(sql) {
|
||||
assert.match(sql, /information_schema\.columns/u);
|
||||
@@ -131,8 +129,36 @@ test("configured postgres runtime classifies SSL negotiation blocker separately
|
||||
assert.equal(readiness.durabilityContract.ready, false);
|
||||
assert.equal(readiness.durabilityContract.blockedLayer, "ssl");
|
||||
assert.equal(readiness.durabilityContract.secretMaterialRead, false);
|
||||
assert.equal(JSON.stringify(readiness).includes("supersecret"), false);
|
||||
assert.equal(JSON.stringify(readiness).includes("db.example.invalid"), false);
|
||||
assert.equal(JSON.stringify(readiness).includes("The server does not support SSL connections"), false);
|
||||
});
|
||||
|
||||
test("configured postgres runtime classifies pg_hba no-encryption rejection as SSL before auth", async () => {
|
||||
const store = createConfiguredCloudRuntimeStore({
|
||||
env: {
|
||||
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
||||
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
|
||||
},
|
||||
sslMode: "disable",
|
||||
queryClient: {
|
||||
async query(sql) {
|
||||
assert.match(sql, /information_schema\.columns/u);
|
||||
const error = new Error('no pg_hba.conf entry for host "[redacted]", user "[redacted]", database "[redacted]", no encryption');
|
||||
error.code = "28000";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const readiness = await store.readiness();
|
||||
assert.equal(readiness.blocker, RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED);
|
||||
assert.equal(readiness.connection.queryAttempted, true);
|
||||
assert.equal(readiness.connection.queryResult, "ssl_negotiation_blocked");
|
||||
assert.equal(readiness.connection.errorCode, "28000");
|
||||
assert.equal(readiness.gates.ssl.status, "blocked");
|
||||
assert.equal(readiness.gates.auth.status, "not_checked");
|
||||
assert.equal(readiness.durabilityContract.blockedLayer, "ssl");
|
||||
assert.equal(readiness.durabilityContract.dbLiveEvidenceIsDurabilityEvidence, false);
|
||||
assert.equal(JSON.stringify(readiness).includes("pg_hba.conf"), false);
|
||||
});
|
||||
|
||||
test("configured postgres runtime reports schema blocker without green readiness", async () => {
|
||||
@@ -205,7 +231,6 @@ test("configured postgres runtime classifies migration query failures separately
|
||||
HWLAB_CLOUD_RUNTIME_ADAPTER: "postgres",
|
||||
HWLAB_CLOUD_RUNTIME_DURABLE: "true"
|
||||
},
|
||||
dbUrl: "postgres://hwlab_redacted@db.example.invalid:5432/hwlab",
|
||||
queryClient: createFakePostgresClient({ migrationErrorCode: "57014" })
|
||||
});
|
||||
|
||||
|
||||
@@ -638,6 +638,11 @@ function isMigrationSslError(error) {
|
||||
"ssl is not enabled",
|
||||
"ssl negotiation",
|
||||
"ssl off",
|
||||
"ssl required",
|
||||
"requires ssl",
|
||||
"no ssl encryption",
|
||||
"no encryption",
|
||||
"hostssl",
|
||||
"server requires ssl",
|
||||
"before secure tls connection",
|
||||
"tls handshake"
|
||||
|
||||
@@ -55,7 +55,6 @@ test("live dry-run separates migration ledger blocker from auth and schema", asy
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
queryClient: createFakeQueryClient({ migrationReady: false }),
|
||||
@@ -79,7 +78,6 @@ test("live dry-run separates SSL negotiation blocker from auth and schema", asyn
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_URL: "postgresql://hwlab:redacted@example.invalid:5432/hwlab",
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "require"
|
||||
},
|
||||
queryClient: {
|
||||
@@ -105,7 +103,41 @@ test("live dry-run separates SSL negotiation blocker from auth and schema", asyn
|
||||
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_ssl_blocked");
|
||||
assert.equal(report.runtime.blocker, "runtime_durable_adapter_ssl_blocked");
|
||||
assert.equal(report.runtime.connection.queryResult, "ssl_negotiation_blocked");
|
||||
assert.equal(JSON.stringify(report).includes("redacted@example.invalid"), false);
|
||||
assert.equal(JSON.stringify(report).includes("The server does not support SSL connections"), false);
|
||||
});
|
||||
|
||||
test("live dry-run classifies pg_hba no-encryption rejection as SSL with ssl disabled", async () => {
|
||||
const report = await buildDevRuntimeMigrationReport(
|
||||
parseArgs(["--dry-run", "--allow-live-db-read", "--confirm-dev"]),
|
||||
{
|
||||
env: {
|
||||
HWLAB_CLOUD_DB_SSL_MODE: "disable"
|
||||
},
|
||||
queryClient: {
|
||||
async query(sql) {
|
||||
assert.match(sql, /information_schema\.columns/u);
|
||||
const error = new Error('no pg_hba.conf entry for host "[redacted]", user "[redacted]", database "[redacted]", no encryption');
|
||||
error.code = "28000";
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
now: () => "2026-05-22T00:00:00.000Z"
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(report.conclusion.status, "blocked");
|
||||
assert.equal(report.actions.liveDbReadAttempted, true);
|
||||
assert.equal(report.actions.liveDbWriteAttempted, false);
|
||||
assert.equal(report.gates.auth.status, "not_checked");
|
||||
assert.equal(report.gates.schema.status, "not_checked");
|
||||
assert.equal(report.gates.migration.status, "not_checked");
|
||||
assert.equal(report.gates.readiness.status, "not_checked");
|
||||
assert.equal(report.blockers[0].scope, "runtime-migration-ssl");
|
||||
assert.equal(report.blockers[0].evidence.blocker, "runtime_durable_adapter_ssl_blocked");
|
||||
assert.equal(report.runtime.blocker, "runtime_durable_adapter_ssl_blocked");
|
||||
assert.equal(report.runtime.connection.queryResult, "ssl_negotiation_blocked");
|
||||
assert.equal(report.runtime.connection.errorCode, "28000");
|
||||
assert.equal(JSON.stringify(report).includes("pg_hba.conf"), false);
|
||||
});
|
||||
|
||||
test("apply mode records ledger and verifies durable runtime readiness", async () => {
|
||||
|
||||
Reference in New Issue
Block a user