From e89e60d50b870ec858d2c68424cbcfdc73412664 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 3 Apr 2026 15:33:20 -0400 Subject: [PATCH] Encrypt the streaming data --- .../lib/logStreaming/LogStreamingManager.ts | 24 ++++++++++++++-- .../createEventStreamingDestination.ts | 9 +++++- .../listEventStreamingDestinations.ts | 28 ++++++++++++++++++- .../updateEventStreamingDestination.ts | 8 +++++- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/server/private/lib/logStreaming/LogStreamingManager.ts b/server/private/lib/logStreaming/LogStreamingManager.ts index 0067c0690..a531d9b80 100644 --- a/server/private/lib/logStreaming/LogStreamingManager.ts +++ b/server/private/lib/logStreaming/LogStreamingManager.ts @@ -23,6 +23,8 @@ import { } from "@server/db"; import logger from "@server/logger"; import { and, eq, gt, desc, max, sql } from "drizzle-orm"; +import { decryptData } from "@server/lib/encryption"; +import privateConfig from "#private/lib/config"; import { LogType, LOG_TYPES, @@ -34,6 +36,21 @@ import { LogDestinationProvider } from "./providers/LogDestinationProvider"; import { HttpLogDestination } from "./providers/HttpLogDestination"; import type { EventStreamingDestination } from "@server/db"; +// --------------------------------------------------------------------------- +// Encryption helpers +// --------------------------------------------------------------------------- + +let encryptionKey: Buffer | undefined; + +function getEncryptionKey(): Buffer { + if (!encryptionKey) { + const keyHex = + privateConfig.getRawPrivateConfig().server.encryption_key; + encryptionKey = Buffer.from(keyHex, "hex"); + } + return encryptionKey; +} + // --------------------------------------------------------------------------- // Configuration // --------------------------------------------------------------------------- @@ -272,13 +289,14 @@ export class LogStreamingManager { return; } - // Parse config – skip destination if config is unparseable + // Decrypt and parse config – skip destination if either step fails let config: HttpConfig; try { - config = JSON.parse(dest.config) as HttpConfig; + const decryptedConfig = decryptData(dest.config, getEncryptionKey()); + config = JSON.parse(decryptedConfig) as HttpConfig; } catch (err) { logger.error( - `LogStreamingManager: destination ${dest.destinationId} has invalid JSON config`, + `LogStreamingManager: destination ${dest.destinationId} has invalid or undecryptable config`, err ); return; diff --git a/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts b/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts index 623f2d9e0..19a39a03d 100644 --- a/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts +++ b/server/private/routers/eventStreamingDestination/createEventStreamingDestination.ts @@ -22,6 +22,8 @@ import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; +import { encryptData } from "@server/lib/encryption"; +import privateConfig from "#private/lib/config"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() @@ -89,6 +91,11 @@ export async function createEventStreamingDestination( const { type, config, enabled } = parsedBody.data; + const encryptionKeyHex = + privateConfig.getRawPrivateConfig().server.encryption_key; + const encryptionKey = Buffer.from(encryptionKeyHex, "hex"); + const encryptedConfig = encryptData(config, encryptionKey); + const now = Date.now(); const [destination] = await db @@ -96,7 +103,7 @@ export async function createEventStreamingDestination( .values({ orgId, type, - config, + config: encryptedConfig, enabled, createdAt: now, updatedAt: now, diff --git a/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts b/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts index b3f5ff149..46cfb6b9c 100644 --- a/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts +++ b/server/private/routers/eventStreamingDestination/listEventStreamingDestinations.ts @@ -22,6 +22,19 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { eq, sql } from "drizzle-orm"; +import { decryptData } from "@server/lib/encryption"; +import privateConfig from "#private/lib/config"; + +let encryptionKey: Buffer; + +function getEncryptionKey(): Buffer { + if (!encryptionKey) { + const keyHex = + privateConfig.getRawPrivateConfig().server.encryption_key; + encryptionKey = Buffer.from(keyHex, "hex"); + } + return encryptionKey; +} const paramsSchema = z.strictObject({ orgId: z.string().nonempty() @@ -121,9 +134,22 @@ export async function listEventStreamingDestinations( .from(eventStreamingDestinations) .where(eq(eventStreamingDestinations.orgId, orgId)); + const key = getEncryptionKey(); + const decryptedList = list.map((dest) => { + try { + return { ...dest, config: decryptData(dest.config, key) }; + } catch (err) { + logger.error( + `listEventStreamingDestinations: failed to decrypt config for destination ${dest.destinationId}`, + err + ); + return { ...dest, config: "" }; + } + }); + return response(res, { data: { - destinations: list, + destinations: decryptedList, pagination: { total: count, limit, diff --git a/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts b/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts index 3d3321824..1f7cb1007 100644 --- a/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts +++ b/server/private/routers/eventStreamingDestination/updateEventStreamingDestination.ts @@ -22,6 +22,8 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { and, eq } from "drizzle-orm"; +import { encryptData } from "@server/lib/encryption"; +import privateConfig from "#private/lib/config"; const paramsSchema = z @@ -117,7 +119,11 @@ export async function updateEventStreamingDestination( }; if (type !== undefined) updateData.type = type; - if (config !== undefined) updateData.config = config; + if (config !== undefined) { + const encryptionKeyHex = privateConfig.getRawPrivateConfig().server.encryption_key; + const encryptionKey = Buffer.from(encryptionKeyHex, "hex"); + updateData.config = encryptData(config, encryptionKey); + } if (enabled !== undefined) updateData.enabled = enabled; if (sendAccessLogs !== undefined) updateData.sendAccessLogs = sendAccessLogs; if (sendActionLogs !== undefined) updateData.sendActionLogs = sendActionLogs;