mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Verify requests are from Bitbucket using signing
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
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";
|
||||||
@@ -38,6 +39,13 @@ 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,
|
||||||
@@ -54,7 +62,7 @@ export const BitbucketSecretScanningFactory = () => {
|
|||||||
{
|
{
|
||||||
description: "Infisical webhook for push events",
|
description: "Infisical webhook for push events",
|
||||||
url: `${cfg.SITE_URL}/secret-scanning/webhooks/bitbucket`,
|
url: `${cfg.SITE_URL}/secret-scanning/webhooks/bitbucket`,
|
||||||
active: true,
|
active: false,
|
||||||
events: ["repo:push"]
|
events: ["repo:push"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,7 +97,8 @@ export const BitbucketSecretScanningFactory = () => {
|
|||||||
description: "Infisical webhook for push events",
|
description: "Infisical webhook for push events",
|
||||||
url: newWebhookUrl,
|
url: newWebhookUrl,
|
||||||
active: true,
|
active: true,
|
||||||
events: ["repo:push"]
|
events: ["repo:push"],
|
||||||
|
secret: generateBitbucketWebhookSecret(cfg.AUTH_SECRET, dataSourceId)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { TBitbucketPushEvent } from "@app/ee/services/secret-scanning-v2/bitbuck
|
|||||||
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";
|
||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import crypto from "crypto";
|
||||||
|
import { generateBitbucketWebhookSecret } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory";
|
||||||
|
|
||||||
export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvider) => {
|
export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvider) => {
|
||||||
const probotApp = (app: Probot) => {
|
const probotApp = (app: Probot) => {
|
||||||
@@ -73,16 +75,45 @@ export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvide
|
|||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
dataSourceId: z.string().min(1, { message: "Data Source ID is required" })
|
dataSourceId: z.string().min(1, { message: "Data Source ID is required" })
|
||||||
})
|
}),
|
||||||
|
headers: z
|
||||||
|
.object({
|
||||||
|
"x-hub-signature": z.string().min(1, { message: "X-Hub-Signature header is required" })
|
||||||
|
})
|
||||||
|
.passthrough()
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
// TODO(andrey): Verify request is from bitbucket
|
|
||||||
|
|
||||||
const { dataSourceId } = req.query;
|
const { dataSourceId } = req.query;
|
||||||
|
|
||||||
|
// Verify signature
|
||||||
|
const signature = req.headers["x-hub-signature"];
|
||||||
|
if (!signature) {
|
||||||
|
logger.error("Missing X-Hub-Signature header for Bitbucket webhook");
|
||||||
|
return res.status(401).send({ message: "Unauthorized: Missing signature" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedSignaturePrefix = "sha256=";
|
||||||
|
if (!signature.startsWith(expectedSignaturePrefix)) {
|
||||||
|
logger.error({ signature }, "Invalid X-Hub-Signature format for Bitbucket webhook");
|
||||||
|
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" });
|
if (!dataSourceId) return res.status(400).send({ message: "Data Source ID is required" });
|
||||||
|
|
||||||
await server.services.secretScanningV2.bitbucket.handlePushEvent({
|
await server.services.secretScanningV2.bitbucket.handlePushEvent({
|
||||||
|
|||||||
Reference in New Issue
Block a user