From 1d8cfa3f6e3246b75414353a8814191e7e1958e5 Mon Sep 17 00:00:00 2001 From: avallete Date: Fri, 11 Apr 2025 13:12:56 +0200 Subject: [PATCH 1/3] chore: add more tests invalid connection strings --- test/server/ssl.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/server/ssl.ts b/test/server/ssl.ts index 05944dac..bc53ec63 100644 --- a/test/server/ssl.ts +++ b/test/server/ssl.ts @@ -72,3 +72,55 @@ test('query with ssl with root cert', async () => { DEFAULT_POOL_CONFIG.ssl = defaultSsl }) + +test('query with invalid space empty encrypted connection string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt(` `, CRYPTO_KEY).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "Invalid URL", + } + `) +}) + +test('query with invalid empty encrypted connection string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt(``, CRYPTO_KEY).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string", + "message": "SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string", + } + `) +}) + +test('query with missing host connection string encrypted connection string', async () => { + const res = await app.inject({ + method: 'POST', + path: '/query', + headers: { + 'x-connection-encrypted': CryptoJS.AES.encrypt( + `postgres://name:password@:5432/postgres?sslmode=prefer`, + CRYPTO_KEY + ).toString(), + }, + payload: { query: 'select 1;' }, + }) + expect(res.json()).toMatchInlineSnapshot(` + { + "error": "Invalid URL", + } + `) +}) From a2b89d051e026548fd54f17e54c02072279eaf2c Mon Sep 17 00:00:00 2001 From: avallete Date: Fri, 11 Apr 2025 13:15:26 +0200 Subject: [PATCH 2/3] fix: raise connection string error early on request processing --- src/server/routes/index.ts | 43 +++++++++++++++++++++++++------------- test/server/ssl.ts | 10 +++++---- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 55b85618..f23cb125 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -26,24 +26,39 @@ import { PG_CONNECTION, CRYPTO_KEY } from '../constants.js' export default async (fastify: FastifyInstance) => { // Adds a "pg" object to the request if it doesn't exist fastify.addHook('onRequest', (request, _reply, done) => { - // Node converts headers to lowercase - const encryptedHeader = request.headers['x-connection-encrypted']?.toString() - if (encryptedHeader) { + try { + // Node converts headers to lowercase + const encryptedHeader = request.headers['x-connection-encrypted']?.toString() + if (encryptedHeader) { + try { + request.headers.pg = CryptoJS.AES.decrypt(encryptedHeader, CRYPTO_KEY) + .toString(CryptoJS.enc.Utf8) + .trim() + } catch (e: any) { + request.log.warn({ + message: 'failed to parse encrypted connstring', + error: e.toString(), + }) + throw new Error('failed to process upstream connection details') + } + } else { + request.headers.pg = PG_CONNECTION + } + if (!request.headers.pg) { + request.log.error({ message: 'failed to get connection string' }) + throw new Error('failed to get upstream connection details') + } + // Ensure the resulting connection string is a valid URL try { - request.headers.pg = CryptoJS.AES.decrypt(encryptedHeader, CRYPTO_KEY).toString( - CryptoJS.enc.Utf8 - ) - } catch (e: any) { - request.log.warn({ - message: 'failed to parse encrypted connstring', - error: e.toString(), - }) + new URL(request.headers.pg) + } catch (error) { + request.log.error({ message: 'pg connection string is invalid url' }) throw new Error('failed to process upstream connection details') } - } else { - request.headers.pg = PG_CONNECTION + done() + } catch (err) { + return done(err as Error) } - done() }) fastify.register(ColumnPrivilegesRoute, { prefix: '/column-privileges' }) diff --git a/test/server/ssl.ts b/test/server/ssl.ts index bc53ec63..5b6cc83b 100644 --- a/test/server/ssl.ts +++ b/test/server/ssl.ts @@ -82,9 +82,10 @@ test('query with invalid space empty encrypted connection string', async () => { }, payload: { query: 'select 1;' }, }) + expect(res.statusCode).toBe(500) expect(res.json()).toMatchInlineSnapshot(` { - "error": "Invalid URL", + "error": "failed to get upstream connection details", } `) }) @@ -98,10 +99,10 @@ test('query with invalid empty encrypted connection string', async () => { }, payload: { query: 'select 1;' }, }) + expect(res.statusCode).toBe(500) expect(res.json()).toMatchInlineSnapshot(` { - "error": "SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string", - "message": "SASL: SCRAM-SERVER-FIRST-MESSAGE: client password must be a string", + "error": "failed to get upstream connection details", } `) }) @@ -118,9 +119,10 @@ test('query with missing host connection string encrypted connection string', as }, payload: { query: 'select 1;' }, }) + expect(res.statusCode).toBe(500) expect(res.json()).toMatchInlineSnapshot(` { - "error": "Invalid URL", + "error": "failed to process upstream connection details", } `) }) From 86a10077768dcd27da0a02a2d40a9b99c222a55c Mon Sep 17 00:00:00 2001 From: avallete Date: Fri, 11 Apr 2025 13:20:17 +0200 Subject: [PATCH 3/3] chore: return done --- src/server/routes/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index f23cb125..1532c4ea 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -55,7 +55,7 @@ export default async (fastify: FastifyInstance) => { request.log.error({ message: 'pg connection string is invalid url' }) throw new Error('failed to process upstream connection details') } - done() + return done() } catch (err) { return done(err as Error) }