diff --git a/src/js/bun/sql.ts b/src/js/bun/sql.ts index 7bce98a779db9f..ef3f5cb5cb04c9 100644 --- a/src/js/bun/sql.ts +++ b/src/js/bun/sql.ts @@ -1108,7 +1108,6 @@ function loadOptions(o) { url = _url; } } - if (o?.tls) { sslMode = SSLMode.require; tls = o.tls; @@ -1218,15 +1217,13 @@ function loadOptions(o) { if (sslMode !== SSLMode.disable && !tls?.serverName) { if (hostname) { - tls = { - serverName: hostname, - }; - } else { + tls = { ...tls, serverName: hostname }; + } else if (!!tls) { tls = true; } } - if (!!tls) { + if (!!tls && sslMode === SSLMode.disable) { sslMode = SSLMode.prefer; } port = Number(port); diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index d6b9e3ccd2caed..03909c2a1f1e4b 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1586,37 +1586,36 @@ pub const PostgresSQLConnection = struct { pub fn onHandshake(this: *PostgresSQLConnection, success: i32, ssl_error: uws.us_bun_verify_error_t) void { debug("onHandshake: {d} {d}", .{ success, ssl_error.error_no }); + const handshake_success = if (success == 1) true else false; + if (handshake_success) { + if (this.tls_config.reject_unauthorized != 0) { + // only reject the connection if reject_unauthorized == true + switch (this.ssl_mode) { + // https://github.com/porsager/postgres/blob/6ec85a432b17661ccacbdf7f765c651e88969d36/src/connection.js#L272-L279 + + .verify_ca, .verify_full => { + if (ssl_error.error_no != 0) { + this.failWithJSValue(ssl_error.toJS(this.globalObject)); + return; + } - if (this.tls_config.reject_unauthorized == 0) { - return; - } - - const do_tls_verification = switch (this.ssl_mode) { - // https://github.com/porsager/postgres/blob/6ec85a432b17661ccacbdf7f765c651e88969d36/src/connection.js#L272-L279 - .verify_ca, .verify_full => true, - else => false, - }; - - if (!do_tls_verification) { - return; - } - - if (success != 1) { - this.failWithJSValue(ssl_error.toJS(this.globalObject)); - return; - } - - if (ssl_error.error_no != 0) { - this.failWithJSValue(ssl_error.toJS(this.globalObject)); - return; - } - - const ssl_ptr = @as(*BoringSSL.SSL, @ptrCast(this.socket.getNativeHandle())); - if (BoringSSL.SSL_get_servername(ssl_ptr, 0)) |servername| { - const hostname = servername[0..bun.len(servername)]; - if (!BoringSSL.checkServerIdentity(ssl_ptr, hostname)) { - this.failWithJSValue(ssl_error.toJS(this.globalObject)); + const ssl_ptr = @as(*BoringSSL.SSL, @ptrCast(this.socket.getNativeHandle())); + if (BoringSSL.SSL_get_servername(ssl_ptr, 0)) |servername| { + const hostname = servername[0..bun.len(servername)]; + if (!BoringSSL.checkServerIdentity(ssl_ptr, hostname)) { + this.failWithJSValue(ssl_error.toJS(this.globalObject)); + } + } + }, + else => { + return; + }, + } } + } else { + // if we are here is because server rejected us, and the error_no is the cause of this + // no matter if reject_unauthorized is false because we are disconnected by the server + this.failWithJSValue(ssl_error.toJS(this.globalObject)); } } @@ -1772,9 +1771,10 @@ pub const PostgresSQLConnection = struct { return .zero; } - if (tls_config.reject_unauthorized != 0) - tls_config.request_cert = 1; - + // we always request the cert so we can verify it and also we manually abort the connection if the hostname doesn't match + const original_reject_unauthorized = tls_config.reject_unauthorized; + tls_config.reject_unauthorized = 0; + tls_config.request_cert = 1; // We create it right here so we can throw errors early. const context_options = tls_config.asUSockets(); var err: uws.create_bun_socket_error_t = .none; @@ -1785,7 +1785,8 @@ pub const PostgresSQLConnection = struct { return globalObject.throwValue(err.toJS(globalObject)); } }; - + // restore the original reject_unauthorized + tls_config.reject_unauthorized = original_reject_unauthorized; if (err != .none) { tls_config.deinit(); if (tls_ctx) |ctx| { diff --git a/test/js/sql/docker-tls/Dockerfile b/test/js/sql/docker-tls/Dockerfile new file mode 100644 index 00000000000000..54158eaf193080 --- /dev/null +++ b/test/js/sql/docker-tls/Dockerfile @@ -0,0 +1,78 @@ +# Dockerfile +FROM postgres:15 + +# Create directory for SSL certificates +RUN mkdir -p /etc/postgresql/ssl + +# Copy existing certificates +COPY server.key server.crt /etc/postgresql/ssl/ +RUN chmod 600 /etc/postgresql/ssl/server.key && \ + chown postgres:postgres /etc/postgresql/ssl/server.key /etc/postgresql/ssl/server.crt + +# Create initialization script +RUN echo '#!/bin/bash\n\ +set -e\n\ +\n\ +# Wait for PostgreSQL to start\n\ +until pg_isready; do\n\ + echo "Waiting for PostgreSQL to start..."\n\ + sleep 1\n\ +done\n\ +\n\ +dropdb --if-exists bun_sql_test\n\ +\n\ +# Drop and recreate users with different auth methods\n\ +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL\n\ + DROP USER IF EXISTS bun_sql_test;\n\ + CREATE USER bun_sql_test;\n\ + \n\ + ALTER SYSTEM SET password_encryption = '"'"'md5'"'"';\n\ + SELECT pg_reload_conf();\n\ + DROP USER IF EXISTS bun_sql_test_md5;\n\ + CREATE USER bun_sql_test_md5 WITH PASSWORD '"'"'bun_sql_test_md5'"'"';\n\ + \n\ + ALTER SYSTEM SET password_encryption = '"'"'scram-sha-256'"'"';\n\ + SELECT pg_reload_conf();\n\ + DROP USER IF EXISTS bun_sql_test_scram;\n\ + CREATE USER bun_sql_test_scram WITH PASSWORD '"'"'bun_sql_test_scram'"'"';\n\ +EOSQL\n\ +\n\ +# Create database and set permissions\n\ +createdb bun_sql_test\n\ +\n\ +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL\n\ + GRANT ALL ON DATABASE bun_sql_test TO bun_sql_test;\n\ + ALTER DATABASE bun_sql_test OWNER TO bun_sql_test;\n\ +EOSQL\n\ +' > /docker-entrypoint-initdb.d/init-users-db.sh + +# Make the script executable +RUN chmod +x /docker-entrypoint-initdb.d/init-users-db.sh + +# Create pg_hba.conf with SSL requirements +RUN mkdir -p /etc/postgresql && touch /etc/postgresql/pg_hba.conf && \ + echo "hostssl all postgres 127.0.0.1/32 trust" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all bun_sql_test 127.0.0.1/32 trust" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all bun_sql_test_md5 127.0.0.1/32 md5" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all bun_sql_test_scram 127.0.0.1/32 scram-sha-256" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all postgres ::1/128 trust" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all bun_sql_test ::1/128 trust" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all bun_sql_test_md5 ::1/128 md5" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl all bun_sql_test_scram ::1/128 scram-sha-256" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl replication all 127.0.0.1/32 trust" >> /etc/postgresql/pg_hba.conf && \ + echo "hostssl replication all ::1/128 trust" >> /etc/postgresql/pg_hba.conf && \ + echo "host all all all reject" >> /etc/postgresql/pg_hba.conf + +# Configure PostgreSQL for SSL +RUN mkdir -p /docker-entrypoint-initdb.d && \ + echo "ALTER SYSTEM SET max_prepared_transactions = '100';" > /docker-entrypoint-initdb.d/configure-postgres.sql && \ + echo "ALTER SYSTEM SET ssl = 'on';" >> /docker-entrypoint-initdb.d/configure-postgres.sql && \ + echo "ALTER SYSTEM SET ssl_cert_file = '/etc/postgresql/ssl/server.crt';" >> /docker-entrypoint-initdb.d/configure-postgres.sql && \ + echo "ALTER SYSTEM SET ssl_key_file = '/etc/postgresql/ssl/server.key';" >> /docker-entrypoint-initdb.d/configure-postgres.sql + +# Set environment variables +ENV POSTGRES_HOST_AUTH_METHOD=trust +ENV POSTGRES_USER=postgres + +# Expose PostgreSQL port +EXPOSE 5432 diff --git a/test/js/sql/docker-tls/server.crt b/test/js/sql/docker-tls/server.crt new file mode 100644 index 00000000000000..49ea0e1e8a959d --- /dev/null +++ b/test/js/sql/docker-tls/server.crt @@ -0,0 +1,33 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/test/js/sql/docker-tls/server.key b/test/js/sql/docker-tls/server.key new file mode 100644 index 00000000000000..e2ee5a9ea19a28 --- /dev/null +++ b/test/js/sql/docker-tls/server.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +AWDbBPL8WN2F2/Q0QBxC2BECKSVxhg== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/test/js/sql/docker/pg_hba.conf b/test/js/sql/docker/pg_hba.conf deleted file mode 100644 index 0079ef32148b46..00000000000000 --- a/test/js/sql/docker/pg_hba.conf +++ /dev/null @@ -1,98 +0,0 @@ -# PostgreSQL Client Authentication Configuration File -# =================================================== -# -# Refer to the "Client Authentication" section in the PostgreSQL -# documentation for a complete description of this file. A short -# synopsis follows. -# -# This file controls: which hosts are allowed to connect, how clients -# are authenticated, which PostgreSQL user names they can use, which -# databases they can access. Records take one of these forms: -# -# local DATABASE USER METHOD [OPTIONS] -# host DATABASE USER ADDRESS METHOD [OPTIONS] -# hostssl DATABASE USER ADDRESS METHOD [OPTIONS] -# hostnossl DATABASE USER ADDRESS METHOD [OPTIONS] -# hostgssenc DATABASE USER ADDRESS METHOD [OPTIONS] -# hostnogssenc DATABASE USER ADDRESS METHOD [OPTIONS] -# -# (The uppercase items must be replaced by actual values.) -# -# The first field is the connection type: -# - "local" is a Unix-domain socket -# - "host" is a TCP/IP socket (encrypted or not) -# - "hostssl" is a TCP/IP socket that is SSL-encrypted -# - "hostnossl" is a TCP/IP socket that is not SSL-encrypted -# - "hostgssenc" is a TCP/IP socket that is GSSAPI-encrypted -# - "hostnogssenc" is a TCP/IP socket that is not GSSAPI-encrypted -# -# DATABASE can be "all", "sameuser", "samerole", "replication", a -# database name, or a comma-separated list thereof. The "all" -# keyword does not match "replication". Access to replication -# must be enabled in a separate record (see example below). -# -# USER can be "all", a user name, a group name prefixed with "+", or a -# comma-separated list thereof. In both the DATABASE and USER fields -# you can also write a file name prefixed with "@" to include names -# from a separate file. -# -# ADDRESS specifies the set of hosts the record matches. It can be a -# host name, or it is made up of an IP address and a CIDR mask that is -# an integer (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that -# specifies the number of significant bits in the mask. A host name -# that starts with a dot (.) matches a suffix of the actual host name. -# Alternatively, you can write an IP address and netmask in separate -# columns to specify the set of hosts. Instead of a CIDR-address, you -# can write "samehost" to match any of the server's own IP addresses, -# or "samenet" to match any address in any subnet that the server is -# directly connected to. -# -# METHOD can be "trust", "reject", "md5", "password", "scram-sha-256", -# "gss", "sspi", "ident", "peer", "pam", "ldap", "radius" or "cert". -# Note that "password" sends passwords in clear text; "md5" or -# "scram-sha-256" are preferred since they send encrypted passwords. -# -# OPTIONS are a set of options for the authentication in the format -# NAME=VALUE. The available options depend on the different -# authentication methods -- refer to the "Client Authentication" -# section in the documentation for a list of which options are -# available for which authentication methods. -# -# Database and user names containing spaces, commas, quotes and other -# special characters must be quoted. Quoting one of the keywords -# "all", "sameuser", "samerole" or "replication" makes the name lose -# its special character, and just match a database or username with -# that name. -# -# This file is read on server startup and when the server receives a -# SIGHUP signal. If you edit the file on a running system, you have to -# SIGHUP the server for the changes to take effect, run "pg_ctl reload", -# or execute "SELECT pg_reload_conf()". -# -# Put your actual configuration here -# ---------------------------------- -# -# If you want to allow non-local connections, you need to add more -# "host" records. In that case you will also need to make PostgreSQL -# listen on a non-local interface via the listen_addresses -# configuration parameter, or via the -i or -h command line switches. - -# CAUTION: Configuring the system for local "trust" authentication -# allows any local user to connect as any PostgreSQL user, including -# the database superuser. If you do not trust all your local users, -# use another authentication method. - - -# TYPE DATABASE USER ADDRESS METHOD - -# "local" is for Unix domain socket connections only -local all all trust -# IPv4 local connections: -host all all 127.0.0.1/32 trust -# IPv6 local connections: -host all all ::1/128 trust -# Allow replication connections from localhost, by a user with the -# replication privilege. -local replication all trust -host replication all 127.0.0.1/32 trust -host replication all ::1/128 trust diff --git a/test/js/sql/local-sql.test.ts b/test/js/sql/local-sql.test.ts new file mode 100644 index 00000000000000..5d215d3662415a --- /dev/null +++ b/test/js/sql/local-sql.test.ts @@ -0,0 +1,152 @@ +import { SQL } from "bun"; +const postgres = (...args) => new SQL(...args); +import { expect, test, mock, beforeAll, afterAll } from "bun:test"; +import { $ } from "bun"; +import { bunExe, isCI, withoutAggressiveGC, isLinux } from "harness"; +import path from "path"; + +import { exec, execSync } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); +import net from "net"; +const dockerCLI = Bun.which("docker") as string; + +async function findRandomPort() { + return new Promise((resolve, reject) => { + // Create a server to listen on a random port + const server = net.createServer(); + server.listen(0, () => { + const port = server.address().port; + server.close(() => resolve(port)); + }); + server.on("error", reject); + }); +} +async function waitForPostgres(port) { + for (let i = 0; i < 3; i++) { + try { + const sql = new SQL(`postgres://bun_sql_test@localhost:${port}/bun_sql_test`, { + idle_timeout: 20, + max_lifetime: 60 * 30, + tls: { + ca: Bun.file(path.join(import.meta.dir, "docker-tls", "server.crt")), + }, + }); + + await sql`SELECT 1`; + await sql.end(); + console.log("PostgreSQL is ready!"); + return true; + } catch (error) { + console.log(`Waiting for PostgreSQL... (${i + 1}/3)`); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + throw new Error("PostgreSQL failed to start"); +} + +async function startContainer(): Promise<{ port: number; containerName: string }> { + try { + // Build the Docker image + console.log("Building Docker image..."); + const dockerfilePath = path.join(import.meta.dir, "docker-tls", "Dockerfile"); + await execAsync(`${dockerCLI} build --pull --rm -f "${dockerfilePath}" -t custom-postgres-tls .`, { + cwd: path.join(import.meta.dir, "docker-tls"), + }); + const port = await findRandomPort(); + const containerName = `postgres-test-${port}`; + // Check if container exists and remove it + try { + await execAsync(`${dockerCLI} rm -f ${containerName}`); + } catch (error) { + // Container might not exist, ignore error + } + + // Start the container + await execAsync(`${dockerCLI} run -d --name ${containerName} -p ${port}:5432 custom-postgres-tls`); + + // Wait for PostgreSQL to be ready + await waitForPostgres(port); + return { + port, + containerName, + }; + } catch (error) { + console.error("Error:", error); + process.exit(1); + } +} + +function isDockerEnabled(): boolean { + if (!dockerCLI) { + return false; + } + + // TODO: investigate why its not starting on Linux arm64 + if (isLinux && process.arch === "arm64") { + return false; + } + + try { + const info = execSync(`${dockerCLI} info`, { stdio: ["ignore", "pipe", "inherit"] }); + return info.toString().indexOf("Server Version:") !== -1; + } catch { + return false; + } +} +if (isDockerEnabled()) { + const container: { port: number; containerName: string } = await startContainer(); + afterAll(async () => { + try { + await execAsync(`${dockerCLI} stop -t 0 ${container.containerName}`); + await execAsync(`${dockerCLI} rm -f ${container.containerName}`); + } catch (error) {} + }); + + const connectionString = `postgres://bun_sql_test@localhost:${container.port}/bun_sql_test?sslmode=verify-full`; + test("Connects using connection string", async () => { + // we need at least the usename and port + await using sql = postgres(connectionString, { + max: 1, + idleTimeout: 1, + connectionTimeout: 1, + tls: { + ca: Bun.file(path.join(import.meta.dir, "docker-tls", "server.crt")), + }, + }); + + const result = (await sql`select 1 as x`)[0].x; + expect(result).toBe(1); + }); + + test("Dont connect using connection string without valid ca", async () => { + try { + // we need at least the usename and port + await using sql = postgres(connectionString, { + max: 1, + idleTimeout: 1, + connectionTimeout: 1, + }); + + (await sql`select 1 as x`)[0].x; + expect.unreachable(); + } catch (error: any) { + expect(error.code || error).toBe("DEPTH_ZERO_SELF_SIGNED_CERT"); + } + }); + + test("rejectUnauthorized should work", async () => { + // we need at least the usename and port + await using sql = postgres(connectionString, { + max: 1, + idleTimeout: 1, + connectionTimeout: 1, + tls: { + rejectUnauthorized: false, + }, + }); + const result = (await sql`select 1 as x`)[0].x; + expect(result).toBe(1); + }); +}