Swap away from using hash checks

This commit is contained in:
x032205
2025-07-07 19:07:18 -04:00
parent c5a8786d1c
commit 22ae1aeee4
6 changed files with 58 additions and 33 deletions
@@ -1,4 +1,3 @@
import crypto from "crypto";
import { join } from "path"; import { join } from "path";
import { scanContentAndGetFindings } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns"; 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 { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { titleCaseToCamelCase } from "@app/lib/fn"; import { titleCaseToCamelCase } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { GitHubRepositoryRegex } from "@app/lib/regex"; import { GitHubRepositoryRegex } from "@app/lib/regex";
import { import {
getBitbucketUser, getBitbucketUser,
@@ -39,13 +39,6 @@ import {
TQueueBitbucketResourceDiffScan TQueueBitbucketResourceDiffScan
} from "./bitbucket-secret-scanning-types"; } from "./bitbucket-secret-scanning-types";
export function generateBitbucketWebhookSecret(serverSecret: string, dataSourceId: string) {
return crypto
.createHash("sha256")
.update(serverSecret + dataSourceId)
.digest("hex");
}
export const BitbucketSecretScanningFactory = () => { export const BitbucketSecretScanningFactory = () => {
const initialize: TSecretScanningFactoryInitialize< const initialize: TSecretScanningFactoryInitialize<
TBitbucketDataSourceInput, TBitbucketDataSourceInput,
@@ -74,7 +67,7 @@ export const BitbucketSecretScanningFactory = () => {
); );
return callback({ return callback({
credentials: { webhookId: data.uuid } credentials: { webhookId: data.uuid, webhookSecret: alphaNumericNanoId(64) }
}); });
}; };
@@ -84,7 +77,7 @@ export const BitbucketSecretScanningFactory = () => {
TBitbucketDataSourceCredentials TBitbucketDataSourceCredentials
> = async ({ dataSourceId, credentials, connection, payload }) => { > = async ({ dataSourceId, credentials, connection, payload }) => {
const { email, apiToken } = connection.credentials; const { email, apiToken } = connection.credentials;
const { webhookId } = credentials; const { webhookId, webhookSecret } = credentials;
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`; const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
@@ -98,7 +91,7 @@ export const BitbucketSecretScanningFactory = () => {
url: newWebhookUrl, url: newWebhookUrl,
active: true, active: true,
events: ["repo:push"], events: ["repo:push"],
secret: generateBitbucketWebhookSecret(cfg.AUTH_SECRET, dataSourceId) secret: webhookSecret
}, },
{ {
headers: { headers: {
@@ -92,5 +92,6 @@ export const BitbucketFindingSchema = BaseSecretScanningFindingSchema.extend({
}); });
export const BitbucketDataSourceCredentialsSchema = z.object({ export const BitbucketDataSourceCredentialsSchema = z.object({
webhookId: z.string() webhookId: z.string(),
webhookSecret: z.string()
}); });
@@ -1,16 +1,27 @@
import crypto from "crypto";
import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal"; 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 { 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 { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue";
import { logger } from "@app/lib/logger"; 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 = ( export const bitbucketSecretScanningService = (
secretScanningV2DAL: TSecretScanningV2DALFactory, secretScanningV2DAL: TSecretScanningV2DALFactory,
secretScanningV2Queue: Pick<TSecretScanningV2QueueServiceFactory, "queueResourceDiffScan"> secretScanningV2Queue: Pick<TSecretScanningV2QueueServiceFactory, "queueResourceDiffScan">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => { ) => {
const handlePushEvent = async (payload: TBitbucketPushEvent & { dataSourceId: string }) => { const handlePushEvent = async (
const { push, repository } = payload; payload: TBitbucketPushEvent & { dataSourceId: string; receivedSignature: string; bodyString: string }
) => {
const { push, repository, bodyString, receivedSignature } = payload;
if (!push?.changes?.length || !repository?.workspace?.uuid) { if (!push?.changes?.length || !repository?.workspace?.uuid) {
logger.warn( logger.warn(
@@ -35,9 +46,38 @@ export const bitbucketSecretScanningService = (
const { const {
isAutoScanEnabled, isAutoScanEnabled,
config: { includeRepos } config: { includeRepos },
encryptedCredentials,
projectId
} = dataSource; } = 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) { if (!isAutoScanEnabled) {
logger.info( logger.info(
`secretScanningV2PushEvent: Bitbucket - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]` `secretScanningV2PushEvent: Bitbucket - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [workspaceUuid=${repository.workspace.uuid}]`
@@ -19,8 +19,7 @@ export const BaseSecretScanningDataSourceSchema = ({
// unique to provider // unique to provider
type: true, type: true,
connectionId: true, connectionId: true,
config: true, config: true
encryptedCredentials: true
}).extend({ }).extend({
type: z.literal(type), type: z.literal(type),
connectionId: isConnectionRequired ? z.string().uuid() : z.null(), connectionId: isConnectionRequired ? z.string().uuid() : z.null(),
@@ -901,6 +901,6 @@ export const secretScanningV2ServiceFactory = ({
findSecretScanningConfigByProjectId, findSecretScanningConfigByProjectId,
upsertSecretScanningConfig, upsertSecretScanningConfig,
github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue), github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue),
bitbucket: bitbucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue) bitbucket: bitbucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue, kmsService)
}; };
}; };
@@ -4,7 +4,6 @@ import crypto from "crypto";
import { Probot } from "probot"; import { Probot } from "probot";
import { z } from "zod"; 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 { TBitbucketPushEvent } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger"; 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" }); 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); 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" }); if (!dataSourceId) return res.status(400).send({ message: "Data Source ID is required" });
console.log("111");
await server.services.secretScanningV2.bitbucket.handlePushEvent({ await server.services.secretScanningV2.bitbucket.handlePushEvent({
...(req.body as TBitbucketPushEvent), ...(req.body as TBitbucketPushEvent),
dataSourceId dataSourceId,
receivedSignature,
bodyString: JSON.stringify(req.body)
}); });
return res.send("ok"); return res.send("ok");