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.
Comment thread
hjr3 marked this conversation as resolved.
Outdated
* `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') {
Copy link
Copy Markdown

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
Copy Markdown

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.

Copy link
Copy Markdown

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
Copy Markdown
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
Comment thread
pmalouin marked this conversation as resolved.
Outdated
})

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
Comment thread
pmalouin marked this conversation as resolved.
})

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