From 06fc4e955d5e8ec51eff7e0baeca982456a297dc Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Fri, 4 Apr 2025 13:28:01 -0700 Subject: [PATCH] improvements: final feedback and ssl enforcement --- .../20250324142105_secret-rotation-v2.ts | 1 + backend/src/db/schemas/organizations.ts | 4 +- backend/src/db/schemas/secret-rotations-v2.ts | 3 +- .../secret-rotation-v2-service.ts | 3 +- backend/src/lib/config/env.ts | 1 + .../shared/sql/sql-connection-fns.ts | 51 +++++++++++++------ .../platform/secret-rotation/mssql.mdx | 2 +- .../platform/secret-rotation/postgres.mdx | 2 +- docs/self-hosting/configuration/envars.mdx | 9 +++- 9 files changed, 52 insertions(+), 24 deletions(-) diff --git a/backend/src/db/migrations/20250324142105_secret-rotation-v2.ts b/backend/src/db/migrations/20250324142105_secret-rotation-v2.ts index 0fd9b9881..dfe0e6888 100644 --- a/backend/src/db/migrations/20250324142105_secret-rotation-v2.ts +++ b/backend/src/db/migrations/20250324142105_secret-rotation-v2.ts @@ -28,6 +28,7 @@ export async function up(knex: Knex): Promise { t.binary("encryptedLastRotationMessage"); // we encrypt this because it may contain sensitive info (SQL errors showing credentials) t.string("lastRotationJobId"); t.datetime("nextRotationAt"); + t.boolean("isLastRotationManual").notNullable().defaultTo(true); // creation is considered a "manual" rotation }); await createOnUpdateTrigger(knex, TableName.SecretRotationV2); diff --git a/backend/src/db/schemas/organizations.ts b/backend/src/db/schemas/organizations.ts index 7e5994938..a18e258c7 100644 --- a/backend/src/db/schemas/organizations.ts +++ b/backend/src/db/schemas/organizations.ts @@ -23,10 +23,10 @@ export const OrganizationsSchema = z.object({ defaultMembershipRole: z.string().default("member"), enforceMfa: z.boolean().default(false), selectedMfaMethod: z.string().nullable().optional(), + allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(), shouldUseNewPrivilegeSystem: z.boolean().default(true), privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), - privilegeUpgradeInitiatedAt: z.date().nullable().optional(), - allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional() + privilegeUpgradeInitiatedAt: z.date().nullable().optional() }); export type TOrganizations = z.infer; diff --git a/backend/src/db/schemas/secret-rotations-v2.ts b/backend/src/db/schemas/secret-rotations-v2.ts index cecc58e07..95873b447 100644 --- a/backend/src/db/schemas/secret-rotations-v2.ts +++ b/backend/src/db/schemas/secret-rotations-v2.ts @@ -30,7 +30,8 @@ export const SecretRotationsV2Schema = z.object({ lastRotatedAt: z.date(), encryptedLastRotationMessage: zodBuffer.nullable().optional(), lastRotationJobId: z.string().nullable().optional(), - nextRotationAt: z.date().nullable().optional() + nextRotationAt: z.date().nullable().optional(), + isLastRotationManual: z.boolean().default(true) }); export type TSecretRotationsV2 = z.infer; diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts index 4a0e601df..d0b38808e 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-service.ts @@ -597,7 +597,7 @@ export const secretRotationV2ServiceFactory = ({ const nextRotationAt = calculateNextRotationAt({ ...(secretRotation as TSecretRotationV2), ...payload, - isManualRotation: false + isManualRotation: secretRotation.isLastRotationManual }); let secretsMappingUpdated = false; @@ -892,6 +892,7 @@ export const secretRotationV2ServiceFactory = ({ { encryptedGeneratedCredentials: encryptedUpdatedCredentials, activeIndex: inactiveIndex, + isLastRotationManual: isManualRotation, lastRotatedAt: currentTime, lastRotationAttemptedAt: currentTime, nextRotationAt: calculateNextRotationAt({ diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 3617509e2..f43b0b443 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -60,6 +60,7 @@ const envSchema = z HTTPS_ENABLED: zodStrBool, ROTATION_DEVELOPMENT_MODE: zodStrBool.default("false").optional(), DB_SSL_REJECT_UNAUTHORIZED: zodStrBool.default("true"), + DB_SSL_REQUIRED: zodStrBool.default("true"), // smtp options SMTP_HOST: zpStr(z.string().optional()), SMTP_IGNORE_TLS: zodStrBool.default("false"), diff --git a/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts b/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts index 3c9350395..4a6f2b05f 100644 --- a/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts +++ b/backend/src/services/app-connection/shared/sql/sql-connection-fns.ts @@ -19,18 +19,46 @@ const SQL_CONNECTION_CLIENT_MAP = { [AppConnection.MsSql]: "mssql" }; -export const getSqlConnectionClient = async (appConnection: Pick) => { +const getConnectionConfig = ({ + app, + credentials: { sslCertificate, host } +}: Pick) => { const appCfg = getConfig(); + switch (app) { + case AppConnection.Postgres: { + return { + ssl: appCfg.DB_SSL_REQUIRED + ? { + rejectUnauthorized: appCfg.DB_SSL_REJECT_UNAUTHORIZED, + ca: sslCertificate, + servername: host + } + : false + }; + } + case AppConnection.MsSql: { + return { + options: appCfg.DB_SSL_REQUIRED + ? { + trustServerCertificate: !appCfg.DB_SSL_REJECT_UNAUTHORIZED, + encrypt: true, + cryptoCredentialsDetails: sslCertificate ? { ca: sslCertificate } : {} + } + : undefined + }; + } + default: + throw new Error(`Unhandled SQL Connection Config: ${app as AppConnection}`); + } +}; + +export const getSqlConnectionClient = async (appConnection: Pick) => { const { app, - credentials: { host: baseHost, database, port, sslCertificate, password, username } + credentials: { host: baseHost, database, port, password, username } } = appConnection; - const ssl = sslCertificate - ? { rejectUnauthorized: appCfg.DB_SSL_REJECT_UNAUTHORIZED, ca: sslCertificate, servername: baseHost } - : undefined; - const [host] = await verifyHostInputValidity(baseHost); const client = knex({ @@ -42,16 +70,7 @@ export const getSqlConnectionClient = async (appConnection: Pick - Whether external database connections should reject unauthorized SSL certificates. Only set this to `false` if - the database you are connecting to is within your internal network. + Specify whether external database connections should reject unauthorized SSL certificates. + We highly recommend keeping this value set to `true` for production use-cases. + + + + Specify whether external database connections should require SSL. + We highly recommend keeping this value set to `true` for production use-cases.