Skip to content
12 changes: 11 additions & 1 deletion packages/pg-connection-string/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,22 @@ Query parameters follow a `?` character, including the following special query p
* `host=<host>` - sets `host` property, overriding the URL's host
* `encoding=<encoding>` - sets the `client_encoding` property
* `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly
* `sslmode=<sslmode>`
* `sslcompat=libpq` - use libpq semantics for `sslmode`
* `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=<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)
* `sslcert=<filename>` - reads data from the given file and includes the result as `ssl.cert`
* `sslkey=<filename>` - reads data from the given file and includes the result as `ssl.key`
* `sslrootcert=<filename>` - 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.
60 changes: 47 additions & 13 deletions packages/pg-connection-string/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,54 @@ function parse(str) {
config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
}

switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer':
case 'require':
case 'verify-ca':
case 'verify-full': {
break
if (config.sslcompat === 'libpq') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than using a value from the connection string, I think it'd be better to have parse take an option: parse(str: string, useLibpqCompat: boolean) — that way connection URLs don't have non-standard parameters?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then every application using this library has to implement this out-of-band, right? That's simply not going to happen, so from a user perspective this effectively wouldn't fix the issue of not supporting standard libpq connection strings.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be an application concern, not a user choice?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many libraries that wrap pg accept a DSN. Those libraries would have to be updated to have whatever function that accepts the DSN also accept this extra parameter.

switch (config.sslmode) {
case 'disable': {
config.ssl = false
break
}
case 'prefer': {
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': {
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
}
case 'verify-full': {
break
}
}
case 'no-verify': {
config.ssl.rejectUnauthorized = false
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
}
}
}

Expand Down
51 changes: 51 additions & 0 deletions packages/pg-connection-string/test/parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

var chai = require('chai')
var expect = chai.expect
chai.should()

var parse = require('../').parse
Expand Down Expand Up @@ -287,6 +288,56 @@ describe('parse', function () {
})
})

it('configuration parameter sslmode=disable with libpq compatibility', function () {
var connectionString = 'pg:///?sslmode=disable&sslcompat=libpq'
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'
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'
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 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 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()).be.undefined
})

it('allow other params like max, ...', function () {
var subject = parse('pg://myhost/db?max=18&min=4')
subject.max.should.equal('18')
Expand Down