From 1bc03de1b629f425bf0781f591f77c280dcb32fb Mon Sep 17 00:00:00 2001 From: Patrick Malouin Date: Tue, 15 Feb 2022 22:20:13 -0500 Subject: [PATCH 1/9] fix(pg-connection-string): get closer to libpq semantics for `sslmode` --- packages/pg-connection-string/README.md | 6 ++++-- packages/pg-connection-string/index.js | 21 ++++++++++++++++----- packages/pg-connection-string/test/parse.js | 17 +++++++++++------ 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index d3a16881a..90dbd8678 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -89,8 +89,10 @@ Query parameters follow a `?` character, including the following special query p * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly * `sslmode=` * `sslmode=disable` - sets `ssl` to false - * `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }` - * `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true + * `sslmode=no-verify`, `sslmode=prefer`, - sets `ssl` to `{ rejectUnauthorized: false }` + * `sslmode=require`, - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` + * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op}` (verify CA, but not server identity) + * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) * `sslcert=` - reads data from the given file and includes the result as `ssl.cert` * `sslkey=` - reads data from the given file and includes the result as `ssl.key` * `sslrootcert=` - reads data from the given file and includes the result as `ssl.ca` diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index 53c3859e5..5a4d59bbf 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -93,15 +93,26 @@ function parse(str) { break } case 'prefer': - case 'require': - case 'verify-ca': - case 'verify-full': { - break - } case 'no-verify': { config.ssl.rejectUnauthorized = false break } + case 'require': { + if (config.sslrootcert) { + // If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca` + config.ssl.checkServerIdentity = function () {} + } else { + config.ssl.rejectUnauthorized = false + } + break + } + case 'verify-ca': { + config.ssl.checkServerIdentity = function () {} + break + } + case 'verify-full': { + break + } } return config diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js index 59f16a62e..feb616a4f 100644 --- a/packages/pg-connection-string/test/parse.js +++ b/packages/pg-connection-string/test/parse.js @@ -258,19 +258,24 @@ describe('parse', function () { it('configuration parameter sslmode=prefer', function () { var connectionString = 'pg:///?sslmode=prefer' var subject = parse(connectionString) - subject.ssl.should.eql({}) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) }) it('configuration parameter sslmode=require', function () { var connectionString = 'pg:///?sslmode=require' var subject = parse(connectionString) - subject.ssl.should.eql({}) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) }) it('configuration parameter sslmode=verify-ca', function () { var connectionString = 'pg:///?sslmode=verify-ca' var subject = parse(connectionString) - subject.ssl.should.eql({}) + subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') + expect(subject.ssl.checkServerIdentity()).be.undefined }) it('configuration parameter sslmode=verify-full', function () { @@ -282,9 +287,9 @@ describe('parse', function () { it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca', function () { var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require' var subject = parse(connectionString) - subject.ssl.should.eql({ - ca: 'example ca\n', - }) + subject.ssl.should.have.property('ca', 'example ca\n') + subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') + expect(subject.ssl.checkServerIdentity()).be.undefined }) it('allow other params like max, ...', function () { From c3aebe7fd7c898f3fe30201cf2d4165997259b7e Mon Sep 17 00:00:00 2001 From: Patrick Malouin Date: Wed, 23 Feb 2022 19:59:35 -0500 Subject: [PATCH 2/9] Apply suggestions from code review Co-authored-by: Charmander <~@charmander.me> --- packages/pg-connection-string/README.md | 8 ++++---- packages/pg-connection-string/test/parse.js | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index 90dbd8678..d90bc03f8 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -87,11 +87,11 @@ Query parameters follow a `?` character, including the following special query p * `host=` - sets `host` property, overriding the URL's host * `encoding=` - sets the `client_encoding` property * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly - * `sslmode=` +* `sslmode=` * `sslmode=disable` - sets `ssl` to false - * `sslmode=no-verify`, `sslmode=prefer`, - sets `ssl` to `{ rejectUnauthorized: false }` - * `sslmode=require`, - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` - * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op}` (verify CA, but not server identity) + * `sslmode=no-verify`, `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` + * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` + * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity) * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) * `sslcert=` - reads data from the given file and includes the result as `ssl.cert` * `sslkey=` - reads data from the given file and includes the result as `ssl.key` diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js index feb616a4f..d66425c35 100644 --- a/packages/pg-connection-string/test/parse.js +++ b/packages/pg-connection-string/test/parse.js @@ -275,7 +275,7 @@ describe('parse', function () { var connectionString = 'pg:///?sslmode=verify-ca' var subject = parse(connectionString) subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') - expect(subject.ssl.checkServerIdentity()).be.undefined + expect(subject.ssl.checkServerIdentity()).to.be.undefined }) it('configuration parameter sslmode=verify-full', function () { @@ -289,7 +289,7 @@ describe('parse', function () { var subject = parse(connectionString) subject.ssl.should.have.property('ca', 'example ca\n') subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') - expect(subject.ssl.checkServerIdentity()).be.undefined + expect(subject.ssl.checkServerIdentity()).to.be.undefined }) it('allow other params like max, ...', function () { From 8d56f865a8e9b8c7aa801e8a5357ad119e63976f Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Tue, 8 Apr 2025 21:08:51 -0400 Subject: [PATCH 3/9] introduce sslcompat=libpq flag to support libpq sslmode semantics --- packages/pg-connection-string/README.md | 7 ++- packages/pg-connection-string/index.js | 63 ++++++++++++++------- packages/pg-connection-string/test/parse.js | 46 ++++++++++++--- 3 files changed, 86 insertions(+), 30 deletions(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index d90bc03f8..38f659f60 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -87,12 +87,17 @@ Query parameters follow a `?` character, including the following special query p * `host=` - sets `host` property, overriding the URL's host * `encoding=` - sets the `client_encoding` property * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly -* `sslmode=` + * `sslcompat=libpq` - use libpq semantics for `sslmode` + * `sslmode=` when `sslcompat=libpq` * `sslmode=disable` - sets `ssl` to false * `sslmode=no-verify`, `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity) * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) + * `sslmode=` when `sslcompat` is not set + * `sslmode=disable` - sets `ssl` to false + * `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }` + * `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true * `sslcert=` - reads data from the given file and includes the result as `ssl.cert` * `sslkey=` - reads data from the given file and includes the result as `ssl.key` * `sslrootcert=` - reads data from the given file and includes the result as `ssl.ca` diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index 5a4d59bbf..73f207cc9 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -87,31 +87,50 @@ function parse(str) { config.ssl.ca = fs.readFileSync(config.sslrootcert).toString() } - switch (config.sslmode) { - case 'disable': { - config.ssl = false - break - } - case 'prefer': - case 'no-verify': { - config.ssl.rejectUnauthorized = false - break - } - case 'require': { - if (config.sslrootcert) { - // If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca` - config.ssl.checkServerIdentity = function () {} - } else { + if (config.sslcompat === 'libpq') { + switch (config.sslmode) { + case 'disable': { + config.ssl = false + break + } + case 'prefer': + case 'no-verify': { config.ssl.rejectUnauthorized = false + break + } + case 'require': { + if (config.sslrootcert) { + // If a root CA is specified, behavior of `sslmode=require` will be the same as that of `verify-ca` + config.ssl.checkServerIdentity = function () {} + } else { + config.ssl.rejectUnauthorized = false + } + break + } + case 'verify-ca': { + config.ssl.checkServerIdentity = function () {} + break + } + case 'verify-full': { + break } - break - } - case 'verify-ca': { - config.ssl.checkServerIdentity = function () {} - break } - case 'verify-full': { - break + } else { + switch (config.sslmode) { + case 'disable': { + config.ssl = false + break + } + case 'prefer': + case 'require': + case 'verify-ca': + case 'verify-full': { + break + } + case 'no-verify': { + config.ssl.rejectUnauthorized = false + break + } } } diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js index d66425c35..fe9fec23a 100644 --- a/packages/pg-connection-string/test/parse.js +++ b/packages/pg-connection-string/test/parse.js @@ -1,6 +1,7 @@ 'use strict' var chai = require('chai') +var expect = chai.expect chai.should() var parse = require('../').parse @@ -255,29 +256,52 @@ describe('parse', function () { subject.ssl.should.eql(false) }) - it('configuration parameter sslmode=prefer', function () { - var connectionString = 'pg:///?sslmode=prefer' + it('configuration parameter sslmode=prefer with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=prefer&sslcompat=libpq' var subject = parse(connectionString) subject.ssl.should.eql({ rejectUnauthorized: false, }) }) - it('configuration parameter sslmode=require', function () { - var connectionString = 'pg:///?sslmode=require' + it('configuration parameter sslmode=require with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=require&sslcompat=libpq' var subject = parse(connectionString) subject.ssl.should.eql({ rejectUnauthorized: false, }) }) - it('configuration parameter sslmode=verify-ca', function () { - var connectionString = 'pg:///?sslmode=verify-ca' + it('configuration parameter sslmode=verify-ca with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq' var subject = parse(connectionString) subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') expect(subject.ssl.checkServerIdentity()).to.be.undefined }) + it('configuration parameter sslmode=prefer with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=prefer&sslcompat=libpq' + var subject = parse(connectionString) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) + }) + + it('configuration parameter sslmode=require with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=require&sslcompat=libpq' + var subject = parse(connectionString) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) + }) + + it('configuration parameter sslmode=verify-ca with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq' + var subject = parse(connectionString) + subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') + expect(subject.ssl.checkServerIdentity()).be.undefined + }) + it('configuration parameter sslmode=verify-full', function () { var connectionString = 'pg:///?sslmode=verify-full' var subject = parse(connectionString) @@ -287,9 +311,17 @@ describe('parse', function () { it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca', function () { var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require' var subject = parse(connectionString) + subject.ssl.should.eql({ + ca: 'example ca\n', + }) + }) + + it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with libpq compatibility', function () { + var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require&sslcompat=libpq' + var subject = parse(connectionString) subject.ssl.should.have.property('ca', 'example ca\n') subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') - expect(subject.ssl.checkServerIdentity()).to.be.undefined + expect(subject.ssl.checkServerIdentity()).be.undefined }) it('allow other params like max, ...', function () { From 116459377639955f2bb5123280159525a50ab96b Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Wed, 9 Apr 2025 17:44:26 -0400 Subject: [PATCH 4/9] document behavior of verify-ca for libpq compat --- packages/pg-connection-string/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index 38f659f60..2f1275ae0 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -92,7 +92,7 @@ Query parameters follow a `?` character, including the following special query p * `sslmode=disable` - sets `ssl` to false * `sslmode=no-verify`, `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` - * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity) + * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA, i.e. the one specified in sslrootcert or the system CA if sslrootcert was not specified. * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) * `sslmode=` when `sslcompat` is not set * `sslmode=disable` - sets `ssl` to false From f16737e3ce92a3fff44c6aa876880e74408659d3 Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Wed, 9 Apr 2025 17:46:03 -0400 Subject: [PATCH 5/9] remove sslmode=no-verify from libpq compat --- packages/pg-connection-string/README.md | 2 +- packages/pg-connection-string/index.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index 2f1275ae0..60947ea1b 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -90,7 +90,7 @@ Query parameters follow a `?` character, including the following special query p * `sslcompat=libpq` - use libpq semantics for `sslmode` * `sslmode=` when `sslcompat=libpq` * `sslmode=disable` - sets `ssl` to false - * `sslmode=no-verify`, `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` + * `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA, i.e. the one specified in sslrootcert or the system CA if sslrootcert was not specified. * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index 73f207cc9..c1fe58277 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -93,8 +93,7 @@ function parse(str) { config.ssl = false break } - case 'prefer': - case 'no-verify': { + case 'prefer': { config.ssl.rejectUnauthorized = false break } From 165b5c6e05dadbbea9175a312ccdb4cbaf28c500 Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Tue, 8 Apr 2025 21:17:10 -0400 Subject: [PATCH 6/9] require verify-ca to specify sslrootcert when sslmode is libpq compat --- packages/pg-connection-string/README.md | 11 ++-- packages/pg-connection-string/index.js | 5 ++ packages/pg-connection-string/test/parse.js | 58 +++++++++++++-------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index 60947ea1b..52d01e87a 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -88,18 +88,21 @@ Query parameters follow a `?` character, including the following special query p * `encoding=` - sets the `client_encoding` property * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly * `sslcompat=libpq` - use libpq semantics for `sslmode` + * `sslmode=` when `sslcompat` is not set + * `sslmode=disable` - sets `ssl` to false + * `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }` + * `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true * `sslmode=` when `sslcompat=libpq` * `sslmode=disable` - sets `ssl` to false * `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA, i.e. the one specified in sslrootcert or the system CA if sslrootcert was not specified. * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) - * `sslmode=` when `sslcompat` is not set - * `sslmode=disable` - sets `ssl` to false - * `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }` - * `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true * `sslcert=` - reads data from the given file and includes the result as `ssl.cert` * `sslkey=` - reads data from the given file and includes the result as `ssl.key` * `sslrootcert=` - reads data from the given file and includes the result as `ssl.ca` A bare relative URL, such as `salesdata`, will indicate a database name while leaving other properties empty. + +> [!CAUTION] +> Choosing an sslmode other than verify-full has serious security implications. Please read https://www.postgresql.org/docs/current/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS to understand the trade-offs. diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index c1fe58277..d10442c54 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -107,6 +107,11 @@ function parse(str) { break } case 'verify-ca': { + if (!config.ssl.ca) { + throw new Error( + 'SECURITY WARNING: Using sslmode=verify-ca requires specifying a CA with sslrootcert. If a public CA is used, verify-ca allows connections to a server that somebody else may have registered with the CA, making you vulnerable to Man-in-the-Middle attacks. Either specify a custom CA certificate with sslrootcert parameter or use sslmode=verify-full for proper security.' + ) + } config.ssl.checkServerIdentity = function () {} break } diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js index fe9fec23a..ce4fa7faa 100644 --- a/packages/pg-connection-string/test/parse.js +++ b/packages/pg-connection-string/test/parse.js @@ -256,27 +256,42 @@ describe('parse', function () { subject.ssl.should.eql(false) }) - it('configuration parameter sslmode=prefer with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=prefer&sslcompat=libpq' + it('configuration parameter sslmode=prefer', function () { + var connectionString = 'pg:///?sslmode=prefer' var subject = parse(connectionString) - subject.ssl.should.eql({ - rejectUnauthorized: false, - }) + subject.ssl.should.eql({}) }) - it('configuration parameter sslmode=require with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=require&sslcompat=libpq' + it('configuration parameter sslmode=require', function () { + var connectionString = 'pg:///?sslmode=require' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter sslmode=verify-ca', function () { + var connectionString = 'pg:///?sslmode=verify-ca' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter sslmode=verify-full', function () { + var connectionString = 'pg:///?sslmode=verify-full' + var subject = parse(connectionString) + subject.ssl.should.eql({}) + }) + + it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca', function () { + var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require' var subject = parse(connectionString) subject.ssl.should.eql({ - rejectUnauthorized: false, + ca: 'example ca\n', }) }) - it('configuration parameter sslmode=verify-ca with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq' + it('configuration parameter sslmode=disable with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=disable&sslcompat=libpq' var subject = parse(connectionString) - subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') - expect(subject.ssl.checkServerIdentity()).to.be.undefined + subject.ssl.should.eql(false) }) it('configuration parameter sslmode=prefer with libpq compatibility', function () { @@ -297,25 +312,24 @@ describe('parse', function () { it('configuration parameter sslmode=verify-ca with libpq compatibility', function () { var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq' + expect(function () { + parse(connectionString) + }).to.throw() + }) + + it('configuration parameter sslmode=verify-ca and sslrootcert with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq&sslrootcert=' + __dirname + '/example.ca' var subject = parse(connectionString) subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') expect(subject.ssl.checkServerIdentity()).be.undefined }) - it('configuration parameter sslmode=verify-full', function () { - var connectionString = 'pg:///?sslmode=verify-full' + it('configuration parameter sslmode=verify-full with libpq compatibility', function () { + var connectionString = 'pg:///?sslmode=verify-full&sslcompat=libpq' var subject = parse(connectionString) subject.ssl.should.eql({}) }) - it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca', function () { - var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require' - var subject = parse(connectionString) - subject.ssl.should.eql({ - ca: 'example ca\n', - }) - }) - it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with libpq compatibility', function () { var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require&sslcompat=libpq' var subject = parse(connectionString) From ab4d18546008fb8e59c0d8f7ec5ec3028c7649e7 Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Thu, 17 Apr 2025 05:58:26 -0400 Subject: [PATCH 7/9] Update packages/pg-connection-string/README.md Co-authored-by: Patrick Malouin --- packages/pg-connection-string/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index 52d01e87a..e1bee241e 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -96,7 +96,7 @@ Query parameters follow a `?` character, including the following special query p * `sslmode=disable` - sets `ssl` to false * `sslmode=prefer` - sets `ssl` to `{ rejectUnauthorized: false }` * `sslmode=require` - sets `ssl` to `{ rejectUnauthorized: false }` unless `sslrootcert` is specified, in which case it behaves like `verify-ca` - * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA, i.e. the one specified in sslrootcert or the system CA if sslrootcert was not specified. + * `sslmode=verify-ca` - sets `ssl` to `{ checkServerIdentity: no-op }` (verify CA, but not server identity). This verifies the presented certificate against the effective CA specified in sslrootcert. * `sslmode=verify-full` - sets `ssl` to `{}` (verify CA and server identity) * `sslcert=` - reads data from the given file and includes the result as `ssl.cert` * `sslkey=` - reads data from the given file and includes the result as `ssl.key` From 7aa88ad4b3a99ef9bca6d88a5d9edd45953fe8de Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Thu, 17 Apr 2025 06:14:16 -0400 Subject: [PATCH 8/9] add useLibpqCompat parsing option --- packages/pg-connection-string/index.d.ts | 7 ++- packages/pg-connection-string/index.js | 8 ++- packages/pg-connection-string/test/parse.js | 57 +++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/packages/pg-connection-string/index.d.ts b/packages/pg-connection-string/index.d.ts index d68cc23d3..88618ba92 100644 --- a/packages/pg-connection-string/index.d.ts +++ b/packages/pg-connection-string/index.d.ts @@ -1,6 +1,11 @@ import { ClientConfig } from 'pg' -export function parse(connectionString: string): ConnectionOptions +export function parse(connectionString: string, options: Options): ConnectionOptions + +export interface Options { + // Use libpq semantics when interpreting the connection string + useLibpqCompat?: boolean +} export interface ConnectionOptions { host: string | null diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index d10442c54..39ac3c613 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -5,7 +5,7 @@ //MIT License //parses a connection string -function parse(str) { +function parse(str, options = {}) { //unix socket if (str.charAt(0) === '/') { const config = str.split(' ') @@ -87,7 +87,11 @@ function parse(str) { config.ssl.ca = fs.readFileSync(config.sslrootcert).toString() } - if (config.sslcompat === 'libpq') { + if (options.useLibpqCompat && config.sslcompat) { + throw new Error('Both useLibpqCompat and sslcompat are set. Please use only one of them.') + } + + if (config.sslcompat === 'libpq' || options.useLibpqCompat) { switch (config.sslmode) { case 'disable': { config.ssl = false diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js index ce4fa7faa..e184b693b 100644 --- a/packages/pg-connection-string/test/parse.js +++ b/packages/pg-connection-string/test/parse.js @@ -338,6 +338,63 @@ describe('parse', function () { expect(subject.ssl.checkServerIdentity()).be.undefined }) + it('configuration parameter sslmode=disable with useLibpqCompat option', function () { + var connectionString = 'pg:///?sslmode=disable' + var subject = parse(connectionString, { useLibpqCompat: true }) + subject.ssl.should.eql(false) + }) + + it('configuration parameter sslmode=prefer with useLibpqCompat option', function () { + var connectionString = 'pg:///?sslmode=prefer' + var subject = parse(connectionString, { useLibpqCompat: true }) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) + }) + + it('configuration parameter sslmode=require with useLibpqCompat option', function () { + var connectionString = 'pg:///?sslmode=require' + var subject = parse(connectionString, { useLibpqCompat: true }) + subject.ssl.should.eql({ + rejectUnauthorized: false, + }) + }) + + it('configuration parameter sslmode=verify-ca with useLibpqCompat option', function () { + var connectionString = 'pg:///?sslmode=verify-ca' + expect(function () { + parse(connectionString, { useLibpqCompat: true }) + }).to.throw() + }) + + it('configuration parameter sslmode=verify-ca and sslrootcert with useLibpqCompat option', function () { + var connectionString = 'pg:///?sslmode=verify-ca&sslrootcert=' + __dirname + '/example.ca' + var subject = parse(connectionString, { useLibpqCompat: true }) + subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') + expect(subject.ssl.checkServerIdentity()).be.undefined + }) + + it('configuration parameter sslmode=verify-full with useLibpqCompat option', function () { + var connectionString = 'pg:///?sslmode=verify-full' + var subject = parse(connectionString, { useLibpqCompat: true }) + subject.ssl.should.eql({}) + }) + + it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with useLibpqCompat option', function () { + var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require' + var subject = parse(connectionString, { useLibpqCompat: true }) + subject.ssl.should.have.property('ca', 'example ca\n') + subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') + expect(subject.ssl.checkServerIdentity()).be.undefined + }) + + it('does not allow sslcompat query parameter and useLibpqCompat option at the same time', function () { + var connectionString = 'pg:///?sslcompat=libpq' + expect(function () { + parse(connectionString, { useLibpqCompat: true }) + }).to.throw() + }) + it('allow other params like max, ...', function () { var subject = parse('pg://myhost/db?max=18&min=4') subject.max.should.equal('18') From 89161f388009e2295cfe02ec79cb508bc7d12169 Mon Sep 17 00:00:00 2001 From: "Herman J. Radtke III" Date: Thu, 17 Apr 2025 06:14:44 -0400 Subject: [PATCH 9/9] rename sslcompat=libpq to uselibpqcompat=true --- packages/pg-connection-string/README.md | 2 +- packages/pg-connection-string/index.js | 6 ++-- packages/pg-connection-string/test/parse.js | 31 +++++++++++---------- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/pg-connection-string/README.md b/packages/pg-connection-string/README.md index e1bee241e..26d52bff7 100644 --- a/packages/pg-connection-string/README.md +++ b/packages/pg-connection-string/README.md @@ -87,7 +87,7 @@ Query parameters follow a `?` character, including the following special query p * `host=` - sets `host` property, overriding the URL's host * `encoding=` - sets the `client_encoding` property * `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly - * `sslcompat=libpq` - use libpq semantics for `sslmode` + * `uselibpqcompat=true` - use libpq semantics * `sslmode=` when `sslcompat` is not set * `sslmode=disable` - sets `ssl` to false * `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }` diff --git a/packages/pg-connection-string/index.js b/packages/pg-connection-string/index.js index 39ac3c613..d40ec1252 100644 --- a/packages/pg-connection-string/index.js +++ b/packages/pg-connection-string/index.js @@ -87,11 +87,11 @@ function parse(str, options = {}) { config.ssl.ca = fs.readFileSync(config.sslrootcert).toString() } - if (options.useLibpqCompat && config.sslcompat) { - throw new Error('Both useLibpqCompat and sslcompat are set. Please use only one of them.') + if (options.useLibpqCompat && config.uselibpqcompat) { + throw new Error('Both useLibpqCompat and uselibpqcompat are set. Please use only one of them.') } - if (config.sslcompat === 'libpq' || options.useLibpqCompat) { + if (config.uselibpqcompat === 'true' || options.useLibpqCompat) { switch (config.sslmode) { case 'disable': { config.ssl = false diff --git a/packages/pg-connection-string/test/parse.js b/packages/pg-connection-string/test/parse.js index e184b693b..12f64ab49 100644 --- a/packages/pg-connection-string/test/parse.js +++ b/packages/pg-connection-string/test/parse.js @@ -288,50 +288,51 @@ describe('parse', function () { }) }) - it('configuration parameter sslmode=disable with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=disable&sslcompat=libpq' + it('configuration parameter sslmode=disable with uselibpqcompat query param', function () { + var connectionString = 'pg:///?sslmode=disable&uselibpqcompat=true' var subject = parse(connectionString) subject.ssl.should.eql(false) }) - it('configuration parameter sslmode=prefer with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=prefer&sslcompat=libpq' + it('configuration parameter sslmode=prefer with uselibpqcompat query param', function () { + var connectionString = 'pg:///?sslmode=prefer&uselibpqcompat=true' var subject = parse(connectionString) subject.ssl.should.eql({ rejectUnauthorized: false, }) }) - it('configuration parameter sslmode=require with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=require&sslcompat=libpq' + it('configuration parameter sslmode=require with uselibpqcompat query param', function () { + var connectionString = 'pg:///?sslmode=require&uselibpqcompat=true' var subject = parse(connectionString) subject.ssl.should.eql({ rejectUnauthorized: false, }) }) - it('configuration parameter sslmode=verify-ca with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq' + it('configuration parameter sslmode=verify-ca with uselibpqcompat query param', function () { + var connectionString = 'pg:///?sslmode=verify-ca&uselibpqcompat=true' expect(function () { parse(connectionString) }).to.throw() }) - it('configuration parameter sslmode=verify-ca and sslrootcert with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=verify-ca&sslcompat=libpq&sslrootcert=' + __dirname + '/example.ca' + it('configuration parameter sslmode=verify-ca and sslrootcert with uselibpqcompat query param', function () { + var connectionString = 'pg:///?sslmode=verify-ca&uselibpqcompat=true&sslrootcert=' + __dirname + '/example.ca' var subject = parse(connectionString) subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') expect(subject.ssl.checkServerIdentity()).be.undefined }) - it('configuration parameter sslmode=verify-full with libpq compatibility', function () { - var connectionString = 'pg:///?sslmode=verify-full&sslcompat=libpq' + it('configuration parameter sslmode=verify-full with uselibpqcompat query param', function () { + var connectionString = 'pg:///?sslmode=verify-full&uselibpqcompat=true' var subject = parse(connectionString) subject.ssl.should.eql({}) }) - it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with libpq compatibility', function () { - var connectionString = 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require&sslcompat=libpq' + it('configuration parameter ssl=true and sslmode=require still work with sslrootcert=/path/to/ca with uselibpqcompat query param', function () { + var connectionString = + 'pg:///?ssl=true&sslrootcert=' + __dirname + '/example.ca&sslmode=require&uselibpqcompat=true' var subject = parse(connectionString) subject.ssl.should.have.property('ca', 'example ca\n') subject.ssl.should.have.property('checkServerIdentity').that.is.a('function') @@ -389,7 +390,7 @@ describe('parse', function () { }) it('does not allow sslcompat query parameter and useLibpqCompat option at the same time', function () { - var connectionString = 'pg:///?sslcompat=libpq' + var connectionString = 'pg:///?uselibpqcompat=true' expect(function () { parse(connectionString, { useLibpqCompat: true }) }).to.throw()