leverage webhook secrets from github integrations too

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2025-04-24 15:02:19 +02:00
parent e1e0ddad62
commit ae249fc1c3
5 changed files with 65 additions and 14 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-events-backend-module-github': patch
---
Support webhook validation based on `integrations.github.[].apps.[].webhookSecret` too
@@ -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"
},
@@ -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();
});
});
@@ -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<string>();
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' },
});
};
}
+1
View File
@@ -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"