Skip to content

Commit

Permalink
feat: allow setting root cert directly
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
soedirgo committed Oct 10, 2023
1 parent a11bae5 commit ff5b6c5
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 8 deletions.
28 changes: 28 additions & 0 deletions src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,34 @@ export const init: (config: PoolConfig) => {
query: (sql: string) => Promise<PostgresMetaResult<any>>
end: () => Promise<void>
} = (config) => {
// node-postgres ignores config.ssl if any of sslmode, sslca, sslkey, sslcert,
// sslrootcert are in the connection string. Here we allow setting sslmode in
// the connection string while setting the rest in config.ssl.
if (config.connectionString) {
const u = new URL(config.connectionString)
const sslmode = u.searchParams.get('sslmode')
u.searchParams.delete('sslmode')
// For now, we don't support setting these from the connection string.
u.searchParams.delete('sslca')
u.searchParams.delete('sslkey')
u.searchParams.delete('sslcert')
u.searchParams.delete('sslrootcert')
config.connectionString = u.toString()

// sslmode: null, 'disable', 'prefer', 'require', 'verify-ca', 'verify-full', 'no-verify'
// config.ssl: true, false, {}
if (sslmode === null) {
// skip
} else if (sslmode === 'disable') {
config.ssl = false
} else {
if (typeof config.ssl !== 'object') {
config.ssl = {}
}
config.ssl.rejectUnauthorized = sslmode !== 'no-verify'
}
}

// NOTE: Race condition could happen here: one async task may be doing
// `pool.end()` which invalidates the pool and subsequently all existing
// handles to `query`. Normally you might only deal with one DB so you don't
Expand Down
18 changes: 13 additions & 5 deletions src/server/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import crypto from 'crypto'
import { PoolConfig } from 'pg'
import { getSecret } from '../lib/secrets.js'

export const PG_META_HOST = process.env.PG_META_HOST || '0.0.0.0'
Expand All @@ -10,7 +12,6 @@ const PG_META_DB_USER = process.env.PG_META_DB_USER || 'postgres'
const PG_META_DB_PORT = process.env.PG_META_DB_PORT || '5432'
const PG_META_DB_PASSWORD = (await getSecret('PG_META_DB_PASSWORD')) || 'postgres'
const PG_META_DB_SSL_MODE = process.env.PG_META_DB_SSL_MODE || 'disable'
const PG_META_DB_SSL_ROOT_CERT_PATH = process.env.PG_META_DB_SSL_ROOT_CERT_PATH

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

Expand All @@ -23,17 +24,24 @@ if (!PG_CONNECTION) {
pgConn.password = PG_META_DB_PASSWORD
pgConn.pathname = encodeURIComponent(PG_META_DB_NAME)
pgConn.searchParams.set('sslmode', PG_META_DB_SSL_MODE)
if (PG_META_DB_SSL_ROOT_CERT_PATH) {
pgConn.searchParams.set('sslrootcert', PG_META_DB_SSL_ROOT_CERT_PATH)
}
PG_CONNECTION = `${pgConn}`
}

export const PG_META_DB_SSL_ROOT_CERT = process.env.PG_META_DB_SSL_ROOT_CERT
if (PG_META_DB_SSL_ROOT_CERT) {
// validate cert
new crypto.X509Certificate(PG_META_DB_SSL_ROOT_CERT)
}

export const EXPORT_DOCS = process.argv[2] === 'docs' && process.argv[3] === 'export'
export const GENERATE_TYPES =
process.argv[2] === 'gen' && process.argv[3] === 'types' ? process.argv[4] : undefined
export const GENERATE_TYPES_INCLUDED_SCHEMAS =
GENERATE_TYPES && process.argv[5] === '--include-schemas' ? process.argv[6]?.split(',') ?? [] : []

export const DEFAULT_POOL_CONFIG = { max: 1, connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000 }
export const DEFAULT_POOL_CONFIG: PoolConfig = {
max: 1,
connectionTimeoutMillis: PG_CONN_TIMEOUT_SECS * 1000,
}

export const PG_META_REQ_HEADER = process.env.PG_META_REQ_HEADER || 'request-id'
13 changes: 10 additions & 3 deletions test/server/ssl.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import CryptoJS from 'crypto-js'
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import { app } from './utils'
import { CRYPTO_KEY } from '../../src/server/constants'
import { CRYPTO_KEY, DEFAULT_POOL_CONFIG } from '../../src/server/constants'

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

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

test('query with ssl with root cert', async () => {
const defaultSsl = DEFAULT_POOL_CONFIG.ssl
DEFAULT_POOL_CONFIG.ssl = { ca: sslRootCert }

const res = await app.inject({
method: 'POST',
path: '/query',
headers: {
'x-connection-encrypted': CryptoJS.AES.encrypt(
`postgresql://postgres:postgres@localhost:5432/postgres?sslmode=verify-full&sslrootcert=${SSL_ROOT_CERT_PATH}`,
`postgresql://postgres:postgres@localhost:5432/postgres?sslmode=verify-full`,
CRYPTO_KEY
).toString(),
},
Expand All @@ -63,4 +68,6 @@ test('query with ssl with root cert', async () => {
},
]
`)

DEFAULT_POOL_CONFIG.ssl = defaultSsl
})

0 comments on commit ff5b6c5

Please sign in to comment.