From ae249fc1c3b859770c6bc898ea525c1fda914c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 24 Apr 2025 15:02:19 +0200 Subject: [PATCH] leverage webhook secrets from github integrations too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/large-experts-sort.md | 5 +++ .../events-backend-module-github/package.json | 1 + .../createGithubSignatureValidator.test.ts | 28 ++++++++++++ .../http/createGithubSignatureValidator.ts | 44 +++++++++++++------ yarn.lock | 1 + 5 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 .changeset/large-experts-sort.md diff --git a/.changeset/large-experts-sort.md b/.changeset/large-experts-sort.md new file mode 100644 index 0000000000..003be57430 --- /dev/null +++ b/.changeset/large-experts-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-events-backend-module-github': patch +--- + +Support webhook validation based on `integrations.github.[].apps.[].webhookSecret` too diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 13ab583c0c..54bee4bfa0 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -45,6 +45,7 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/integration": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@octokit/webhooks-methods": "^3.0.0" }, diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts index bb604e88d8..c771e29d67 100644 --- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts +++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts @@ -47,6 +47,24 @@ describe('createGithubSignatureValidator', () => { }, }, }); + const configWithAppSecret = new ConfigReader({ + integrations: { + github: [ + { + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'a', + clientId: 'b', + clientSecret: 'c', + webhookSecret: secret, + }, + ], + }, + ], + }, + }); const payloadString = '{"test": "payload", "score": 5.0}'; const payload = JSON.parse(payloadString); const payloadBuffer = Buffer.from(payloadString); @@ -104,4 +122,14 @@ describe('createGithubSignatureValidator', () => { expect(context.details).toBeUndefined(); }); + + it('secret configured, accept request with valid signature defined in integrations', async () => { + const request = await requestWithSignature(await validSignature); + const context = new TestContext(); + + const validator = createGithubSignatureValidator(configWithAppSecret); + await validator!(request, context); + + expect(context.details).toBeUndefined(); + }); }); diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts index f54c00b58a..137b2a08f3 100644 --- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts +++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts @@ -15,6 +15,7 @@ */ import { Config } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; import { RequestDetails, RequestValidationContext, @@ -36,10 +37,25 @@ import { verify } from '@octokit/webhooks-methods'; export function createGithubSignatureValidator( config: Config, ): RequestValidator | undefined { - const secret = config.getOptionalString( + const webhookSecrets = new Set(); + + const integrations = ScmIntegrations.fromConfig(config); + for (const integration of integrations.github.list()) { + for (const app of integration.config.apps ?? []) { + if (app.webhookSecret) { + webhookSecrets.add(app.webhookSecret); + } + } + } + + const moduleSecret = config.getOptionalString( 'events.modules.github.webhookSecret', ); - if (!secret) { + if (moduleSecret) { + webhookSecrets.add(moduleSecret); + } + + if (webhookSecrets.size === 0) { return undefined; } @@ -51,18 +67,18 @@ export function createGithubSignatureValidator( | string | undefined; - if ( - !signature || - !(await verify( - secret, - request.raw.body.toString(request.raw.encoding), - signature, - )) - ) { - context.reject({ - status: 403, - payload: { message: 'invalid signature' }, - }); + if (signature) { + const body = request.raw.body.toString(request.raw.encoding); + for (const secret of webhookSecrets) { + if (await verify(secret, body, signature)) { + return; // OK + } + } } + + context.reject({ + status: 403, + payload: { message: 'invalid signature' }, + }); }; } diff --git a/yarn.lock b/yarn.lock index fac71e8789..7acee57c6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6576,6 +6576,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/integration": "workspace:^" "@backstage/plugin-events-backend-test-utils": "workspace:^" "@backstage/plugin-events-node": "workspace:^" "@octokit/webhooks-methods": "npm:^3.0.0"