From 22ae1aeee4fa409a935958b969d474de8ad884e7 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 7 Jul 2025 19:07:18 -0400 Subject: [PATCH] Swap away from using hash checks --- .../bitbucket-secret-scanning-factory.ts | 15 ++---- .../bitbucket-secret-scanning-schemas.ts | 3 +- .../bitbucket-secret-scanning-service.ts | 50 +++++++++++++++++-- .../secret-scanning-v2-schemas.ts | 3 +- .../secret-scanning-v2-service.ts | 2 +- .../src/server/plugins/secret-scanner-v2.ts | 18 ++----- 6 files changed, 58 insertions(+), 33 deletions(-) diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts index fc42bd186..a4d9df981 100644 --- a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory.ts @@ -1,4 +1,3 @@ -import crypto from "crypto"; import { join } from "path"; import { scanContentAndGetFindings } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; @@ -24,6 +23,7 @@ import { import { getConfig } from "@app/lib/config/env"; import { request } from "@app/lib/config/request"; import { titleCaseToCamelCase } from "@app/lib/fn"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; import { GitHubRepositoryRegex } from "@app/lib/regex"; import { getBitbucketUser, @@ -39,13 +39,6 @@ import { TQueueBitbucketResourceDiffScan } from "./bitbucket-secret-scanning-types"; -export function generateBitbucketWebhookSecret(serverSecret: string, dataSourceId: string) { - return crypto - .createHash("sha256") - .update(serverSecret + dataSourceId) - .digest("hex"); -} - export const BitbucketSecretScanningFactory = () => { const initialize: TSecretScanningFactoryInitialize< TBitbucketDataSourceInput, @@ -74,7 +67,7 @@ export const BitbucketSecretScanningFactory = () => { ); return callback({ - credentials: { webhookId: data.uuid } + credentials: { webhookId: data.uuid, webhookSecret: alphaNumericNanoId(64) } }); }; @@ -84,7 +77,7 @@ export const BitbucketSecretScanningFactory = () => { TBitbucketDataSourceCredentials > = async ({ dataSourceId, credentials, connection, payload }) => { const { email, apiToken } = connection.credentials; - const { webhookId } = credentials; + const { webhookId, webhookSecret } = credentials; const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`; @@ -98,7 +91,7 @@ export const BitbucketSecretScanningFactory = () => { url: newWebhookUrl, active: true, events: ["repo:push"], - secret: generateBitbucketWebhookSecret(cfg.AUTH_SECRET, dataSourceId) + secret: webhookSecret }, { headers: { diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts index a7a0d7f14..676abf026 100644 --- a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-schemas.ts @@ -92,5 +92,6 @@ export const BitbucketFindingSchema = BaseSecretScanningFindingSchema.extend({ }); export const BitbucketDataSourceCredentialsSchema = z.object({ - webhookId: z.string() + webhookId: z.string(), + webhookSecret: z.string() }); diff --git a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts index 62825f397..c5a0aedd6 100644 --- a/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-service.ts @@ -1,16 +1,27 @@ +import crypto from "crypto"; + import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums"; import { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue"; import { logger } from "@app/lib/logger"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; -import { TBitbucketDataSource, TBitbucketPushEvent } from "./bitbucket-secret-scanning-types"; +import { + TBitbucketDataSource, + TBitbucketDataSourceCredentials, + TBitbucketPushEvent +} from "./bitbucket-secret-scanning-types"; export const bitbucketSecretScanningService = ( secretScanningV2DAL: TSecretScanningV2DALFactory, - secretScanningV2Queue: Pick + secretScanningV2Queue: Pick, + kmsService: Pick ) => { - const handlePushEvent = async (payload: TBitbucketPushEvent & { dataSourceId: string }) => { - const { push, repository } = payload; + const handlePushEvent = async ( + payload: TBitbucketPushEvent & { dataSourceId: string; receivedSignature: string; bodyString: string } + ) => { + const { push, repository, bodyString, receivedSignature } = payload; if (!push?.changes?.length || !repository?.workspace?.uuid) { logger.warn( @@ -35,9 +46,38 @@ export const bitbucketSecretScanningService = ( const { isAutoScanEnabled, - config: { includeRepos } + config: { includeRepos }, + encryptedCredentials, + projectId } = dataSource; + if (!encryptedCredentials) { + logger.info( + `secretScanningV2PushEvent: Bitbucket - Could not find encrypted credentials [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]` + ); + return; + } + + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + + const decryptedCredentials = decryptor({ cipherTextBlob: encryptedCredentials }); + + const credentials = JSON.parse(decryptedCredentials.toString()) as TBitbucketDataSourceCredentials; + + const hmac = crypto.createHmac("sha256", credentials.webhookSecret); + hmac.update(bodyString); + const calculatedSignature = hmac.digest("hex"); + + if (calculatedSignature !== receivedSignature) { + logger.error( + `secretScanningV2PushEvent: Bitbucket - Invalid signature for webhook [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]` + ); + return; + } + if (!isAutoScanEnabled) { logger.info( `secretScanningV2PushEvent: Bitbucket - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]` diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-schemas.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-schemas.ts index 832b73bda..730427a18 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-schemas.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-schemas.ts @@ -19,8 +19,7 @@ export const BaseSecretScanningDataSourceSchema = ({ // unique to provider type: true, connectionId: true, - config: true, - encryptedCredentials: true + config: true }).extend({ type: z.literal(type), connectionId: isConnectionRequired ? z.string().uuid() : z.null(), diff --git a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts index 9d3d5dda6..761059d2a 100644 --- a/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts +++ b/backend/src/ee/services/secret-scanning-v2/secret-scanning-v2-service.ts @@ -901,6 +901,6 @@ export const secretScanningV2ServiceFactory = ({ findSecretScanningConfigByProjectId, upsertSecretScanningConfig, github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue), - bitbucket: bitbucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue) + bitbucket: bitbucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue, kmsService) }; }; diff --git a/backend/src/server/plugins/secret-scanner-v2.ts b/backend/src/server/plugins/secret-scanner-v2.ts index b61754d7e..ad631b683 100644 --- a/backend/src/server/plugins/secret-scanner-v2.ts +++ b/backend/src/server/plugins/secret-scanner-v2.ts @@ -4,7 +4,6 @@ import crypto from "crypto"; import { Probot } from "probot"; import { z } from "zod"; -import { generateBitbucketWebhookSecret } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory"; import { TBitbucketPushEvent } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-types"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; @@ -101,24 +100,17 @@ export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvide return res.status(401).send({ message: "Unauthorized: Invalid signature format" }); } - const cfg = getConfig(); - - const hmac = crypto.createHmac("sha256", generateBitbucketWebhookSecret(cfg.AUTH_SECRET, dataSourceId)); - hmac.update(JSON.stringify(req.body)); - const calculatedSignature = hmac.digest("hex"); - const receivedSignature = signature.substring(expectedSignaturePrefix.length); - if (calculatedSignature !== receivedSignature) { - logger.error("Invalid signature for Bitbucket webhook"); - return res.status(401).send({ message: "Unauthorized: Invalid signature" }); - } - if (!dataSourceId) return res.status(400).send({ message: "Data Source ID is required" }); + console.log("111"); + await server.services.secretScanningV2.bitbucket.handlePushEvent({ ...(req.body as TBitbucketPushEvent), - dataSourceId + dataSourceId, + receivedSignature, + bodyString: JSON.stringify(req.body) }); return res.send("ok");