Skip to content

Commit ff5b6c5

Browse files
committed
feat: allow setting root cert directly
Set the root cert directly via env instead of setting the path of the cert. Also, sslrootcert will be applied on all pg connections, instead of the user having to set &sslrootcert= path in the connection string.
1 parent a11bae5 commit ff5b6c5

File tree

3 files changed

+51
-8
lines changed

3 files changed

+51
-8
lines changed

src/lib/db.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ export const init: (config: PoolConfig) => {
2323
query: (sql: string) => Promise<PostgresMetaResult<any>>
2424
end: () => Promise<void>
2525
} = (config) => {
26+
// node-postgres ignores config.ssl if any of sslmode, sslca, sslkey, sslcert,
27+
// sslrootcert are in the connection string. Here we allow setting sslmode in
28+
// the connection string while setting the rest in config.ssl.
29+
if (config.connectionString) {
30+
const u = new URL(config.connectionString)
31+
const sslmode = u.searchParams.get('sslmode')
32+
u.searchParams.delete('sslmode')
33+
// For now, we don't support setting these from the connection string.
34+
u.searchParams.delete('sslca')
35+
u.searchParams.delete('sslkey')
36+
u.searchParams.delete('sslcert')
37+
u.searchParams.delete('sslrootcert')
38+
config.connectionString = u.toString()
39+
40+
// sslmode: null, 'disable', 'prefer', 'require', 'verify-ca', 'verify-full', 'no-verify'
41+
// config.ssl: true, false, {}
42+
if (sslmode === null) {
43+
// skip
44+
} else if (sslmode === 'disable') {
45+
config.ssl = false
46+
} else {
47+
if (typeof config.ssl !== 'object') {
48+
config.ssl = {}
49+
}
50+
config.ssl.rejectUnauthorized = sslmode !== 'no-verify'
51+
}
52+
}
53+
2654
// NOTE: Race condition could happen here: one async task may be doing
2755
// `pool.end()` which invalidates the pool and subsequently all existing
2856
// handles to `query`. Normally you might only deal with one DB so you don't

src/server/constants.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import crypto from 'crypto'
2+
import { PoolConfig } from 'pg'
13
import { getSecret } from '../lib/secrets.js'
24

35
export const PG_META_HOST = process.env.PG_META_HOST || '0.0.0.0'
@@ -10,7 +12,6 @@ const PG_META_DB_USER = process.env.PG_META_DB_USER || 'postgres'
1012
const PG_META_DB_PORT = process.env.PG_META_DB_PORT || '5432'
1113
const PG_META_DB_PASSWORD = (await getSecret('PG_META_DB_PASSWORD')) || 'postgres'
1214
const PG_META_DB_SSL_MODE = process.env.PG_META_DB_SSL_MODE || 'disable'
13-
const PG_META_DB_SSL_ROOT_CERT_PATH = process.env.PG_META_DB_SSL_ROOT_CERT_PATH
1415

1516
const PG_CONN_TIMEOUT_SECS = Number(process.env.PG_CONN_TIMEOUT_SECS || 15)
1617

@@ -23,17 +24,24 @@ if (!PG_CONNECTION) {
2324
pgConn.password = PG_META_DB_PASSWORD
2425
pgConn.pathname = encodeURIComponent(PG_META_DB_NAME)
2526
pgConn.searchParams.set('sslmode', PG_META_DB_SSL_MODE)
26-
if (PG_META_DB_SSL_ROOT_CERT_PATH) {
27-
pgConn.searchParams.set('sslrootcert', PG_META_DB_SSL_ROOT_CERT_PATH)
28-
}
2927
PG_CONNECTION = `${pgConn}`
3028
}
3129

30+
export const PG_META_DB_SSL_ROOT_CERT = process.env.PG_META_DB_SSL_ROOT_CERT
31+
if (PG_META_DB_SSL_ROOT_CERT) {
32+
// validate cert
33+
new crypto.X509Certificate(PG_META_DB_SSL_ROOT_CERT)
34+
}
35+
3236
export const EXPORT_DOCS = process.argv[2] === 'docs' && process.argv[3] === 'export'
3337
export const GENERATE_TYPES =
3438
process.argv[2] === 'gen' && process.argv[3] === 'types' ? process.argv[4] : undefined
3539
export const GENERATE_TYPES_INCLUDED_SCHEMAS =
3640
GENERATE_TYPES && process.argv[5] === '--include-schemas' ? process.argv[6]?.split(',') ?? [] : []
3741

38-
export const DEFAULT_POOL_CONFIG = { max: 1, connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000 }
42+
export const DEFAULT_POOL_CONFIG: PoolConfig = {
43+
max: 1,
44+
connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000,
45+
}
46+
3947
export const PG_META_REQ_HEADER = process.env.PG_META_REQ_HEADER || 'request-id'

test/server/ssl.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import CryptoJS from 'crypto-js'
2+
import fs from 'fs'
23
import path from 'path'
34
import { fileURLToPath } from 'url'
45
import { app } from './utils'
5-
import { CRYPTO_KEY } from '../../src/server/constants'
6+
import { CRYPTO_KEY, DEFAULT_POOL_CONFIG } from '../../src/server/constants'
67

78
// @ts-ignore: Harmless type error on import.meta.
89
const cwd = path.dirname(fileURLToPath(import.meta.url))
9-
const SSL_ROOT_CERT_PATH = path.join(cwd, '../db/server.crt')
10+
const sslRootCertPath = path.join(cwd, '../db/server.crt')
11+
const sslRootCert = fs.readFileSync(sslRootCertPath, { encoding: 'utf8' })
1012

1113
test('query with no ssl', async () => {
1214
const res = await app.inject({
@@ -45,12 +47,15 @@ test('query with ssl w/o root cert', async () => {
4547
})
4648

4749
test('query with ssl with root cert', async () => {
50+
const defaultSsl = DEFAULT_POOL_CONFIG.ssl
51+
DEFAULT_POOL_CONFIG.ssl = { ca: sslRootCert }
52+
4853
const res = await app.inject({
4954
method: 'POST',
5055
path: '/query',
5156
headers: {
5257
'x-connection-encrypted': CryptoJS.AES.encrypt(
53-
`postgresql://postgres:postgres@localhost:5432/postgres?sslmode=verify-full&sslrootcert=${SSL_ROOT_CERT_PATH}`,
58+
`postgresql://postgres:postgres@localhost:5432/postgres?sslmode=verify-full`,
5459
CRYPTO_KEY
5560
).toString(),
5661
},
@@ -63,4 +68,6 @@ test('query with ssl with root cert', async () => {
6368
},
6469
]
6570
`)
71+
72+
DEFAULT_POOL_CONFIG.ssl = defaultSsl
6673
})

0 commit comments

Comments
 (0)