fix: disable PostgreSQL TLS for refusing RDS endpoint

This commit is contained in:
2026-08-16 23:26:03 +08:00
parent acf368b6fe
commit ed978142eb
17 changed files with 340 additions and 180 deletions

View File

@@ -39,13 +39,20 @@ assert(goApi.includes("name: zhinian-go-runtime"), "Go API must consume the Go r
assert(goApi.includes("name: zhinian-go-auth"), "Go API must own the browser session signing Secret");
assert(!goApi.includes("name: zhinian-web-auth"), "Go API must not reference the removed Web auth Secret");
assert(goApi.includes("name: zhinian-go-bootstrap"), "Go API must receive bootstrap administrator credentials");
assert(goApi.includes("secretName: zhinian-rds-ca"), "Go API must mount the optional RDS CA used by DATABASE_URL");
assert(!goApi.includes("DATABASE_SSL_MODE"), "Go API must keep database TLS inside DATABASE_URL");
assert(!goApi.includes("DATABASE_CA_CERT_PATH"), "Go API must keep database TLS inside DATABASE_URL");
assert(!goApi.includes("rds-ca"), "Go API must not mount an RDS CA when PostgreSQL TLS is disabled");
assert(!goApi.includes("/etc/zhinian/rds"), "Go API must not retain the removed RDS CA path");
assert(!/\bTLS\b/i.test(goApi), "Go API manifest must not retain PostgreSQL TLS configuration");
assert(!goApi.includes("DATABASE_SSL_MODE"), "Go API must not receive a separate database SSL mode");
assert(!goApi.includes("DATABASE_CA_CERT_PATH"), "Go API must not receive a database CA path");
const secrets = read("secrets.example.yaml");
assert(secrets.includes("name: zhinian-go-auth"), "Example secrets must name Go as the session Secret owner");
assert(!secrets.includes("name: zhinian-web-auth"), "Example secrets must not retain the removed Web auth Secret");
assert(!secrets.includes("zhinian-rds-ca"), "Example secrets must not define the removed RDS CA Secret");
assert(!secrets.includes("sslrootcert"), "Example DATABASE_URL values must not reference an RDS CA");
assert(!secrets.includes("verify-full"), "Example DATABASE_URL values must not request TLS");
assert(!/\bTLS\b/i.test(secrets), "Example secrets must not retain PostgreSQL TLS configuration");
assert((secrets.match(/sslmode=disable/g) ?? []).length === 2, "Migration and Go DATABASE_URL examples must disable TLS");
const namespace = read("namespace.yaml");
assert(namespace.includes("kind: Namespace"), "namespace.yaml must define a Namespace");
@@ -131,6 +138,9 @@ assert(
deploymentDocs.includes("Pod 重建或滚动升级同样会永久丢失上传文件和生成结果"),
"Deployment docs must disclose emptyDir data loss across Go Pod replacement",
);
assert(deploymentDocs.includes("不使用 TLS"), "Deployment docs must disclose plaintext PostgreSQL transport");
assert(!deploymentDocs.includes("sslrootcert"), "Deployment docs must not instruct operators to mount an RDS CA");
assert(!deploymentDocs.includes("verify-full"), "Deployment docs must not instruct operators to enable PostgreSQL TLS");
const gitIgnore = readRepository(".gitignore");
assert(

View File

@@ -1,4 +1,3 @@
import { readFileSync } from "node:fs";
import pg from "pg";
const { Pool } = pg;
@@ -14,8 +13,14 @@ export function createPostgresPool({ env = process.env, applicationName = "zhini
if (!connectionString) throw new Error("DATABASE_URL is required when ZHINIAN_DATA_BACKEND=postgres");
const parsed = assertConnectionStringContract(connectionString);
for (const key of [...parsed.searchParams.keys()]) {
if (["sslmode", "sslrootcert"].includes(key.toLowerCase())) parsed.searchParams.delete(key);
}
parsed.searchParams.set("sslmode", "disable");
const config = {
connectionString,
connectionString: parsed.toString(),
ssl: false,
max: positiveInteger(env, "DATABASE_POOL_MAX", 10),
idleTimeoutMillis: nonNegativeInteger(env, "DATABASE_IDLE_TIMEOUT_MS", 30_000),
connectionTimeoutMillis: positiveInteger(env, "DATABASE_CONNECTION_TIMEOUT_MS", 10_000),
@@ -23,21 +28,6 @@ export function createPostgresPool({ env = process.env, applicationName = "zhini
application_name: applicationName
};
const sslMode = parsed.searchParams.get("sslmode")?.trim().toLowerCase() || "verify-full";
if (sslMode === "verify-full") {
const caPath = parsed.searchParams.get("sslrootcert")?.trim();
config.ssl = {
...(caPath ? { ca: readFileSync(caPath, "utf8") } : {}),
rejectUnauthorized: true
};
} else if (sslMode !== "disable") {
throw new Error("DATABASE_URL sslmode must be 'disable' or 'verify-full'");
}
parsed.searchParams.delete("sslmode");
parsed.searchParams.delete("sslrootcert");
config.connectionString = parsed.toString();
return new Pool(config);
}
@@ -66,7 +56,7 @@ function assertConnectionStringContract(connectionString) {
const unsupportedSSLParameters = sslParameters.filter((key) => !["sslmode", "sslrootcert"].includes(key.toLowerCase()));
if (unsupportedSSLParameters.length > 0) {
throw new Error(
`DATABASE_URL contains unsupported SSL query parameters (${unsupportedSSLParameters.join(", ")}); use sslmode and optional sslrootcert`
`DATABASE_URL contains unsupported SSL query parameters (${unsupportedSSLParameters.join(", ")}); PostgreSQL transport is forced to sslmode=disable`
);
}
const sslMode = parsed.searchParams.get("sslmode")?.trim().toLowerCase();