Put secret_scanning_alert events into queue before processinggaryxuehong/gh4j#4
![Logo of Sweep](/_next/image?url=%2Flogo.png&w=64&q=75)
Put secret_scanning_alert events into queue before processing
garyxuehong/gh4j#4
> > >
✓ Completed in 19 minutes, 5 months ago using GPT-4  •  Book a call  •  Report a bug
Progress
  Create
src/sqs/secret-scanning-alert.ts
b7e8720
 1import { WebhookContext } from "../routes/github/webhook/webhook-context";
2import { secretScanningAlertWebhookHandler } from "../github/secret-scanning-alert";
3import { GithubWebhookMiddleware } from "../middleware/github-webhook-middleware";
4import { Logger } from "bunyan";
5import { getLogger } from "../config/logger";
6
7export type SecretScanningAlertMessagePayload = {
8 action: string;
9 alert: {
10 number: number;
11 created_at: string;
12 url: string;
13 html_url: string;
14 state: string;
15 resolution: string | null;
16 resolved_by: string | null;
17 resolved_at: string | null;
18 };
19 repository: {
20 id: number;
21 name: string;
22 full_name: string;
23 private: boolean;
24 };
25 sender: {
26 login: string;
27 id: number;
28 };
29 // Add any additional properties that are specific to the "secret_scanning_alert" event payload
30};
31
32export const secretScanningAlertQueueMessageHandler = async (messagePayload: SecretScanningAlertMessagePayload): Promise<void> => {
33 const logger: Logger = getLogger("secret-scanning-alert");
34 try {
35 const webhookContext = new WebhookContext({
36 id: messagePayload.alert.number.toString(),
37 name: "secret_scanning_alert",
38 payload: messagePayload,
39 log: logger,
40 action: messagePayload.action,
41 gitHubAppConfig: {
42 // Populate with necessary GitHub app configuration
43 }
44 });
45
46 await GithubWebhookMiddleware(secretScanningAlertWebhookHandler)(webhookContext);
47 logger.info("Secret scanning alert was successfully processed");
48 } catch (err: unknown) {
49 logger.error({ err }, "Failed to process secret scanning alert");
50 throw err; // Rethrow the error to be handled by the caller or SQS dead-letter queue
51 }
52};
53
- Create a new file
src/sqs/secret-scanning-alert.ts
for the "secret_scanning_alert" message handler. - Define a new TypeScript type
SecretScanningAlertMessagePayload
that matches the structure of the "secret_scanning_alert" event payload. - Implement a new function
secretScanningAlertQueueMessageHandler
that contains the logic to process a "secret_scanning_alert" event. This logic should be similar to the existing processing logic inwebhook-receiver-post.ts
. - Export the
secretScanningAlertQueueMessageHandler
function so it can be used in thequeues.ts
file.
  Run GitHub Actions for
src/sqs/secret-scanning-alert.tsÂ
Ran GitHub Actions for b7e8720751f5cc70edd7e5ae9519e295ddd5e1b3:
  Create
src/sqs/secret-scanning-alert.test.ts
0ecc70d
 1import { secretScanningAlertQueueMessageHandler } from './secret-scanning-alert';
2import { WebhookContext } from '../routes/github/webhook/webhook-context';
3import { secretScanningAlertWebhookHandler } from '../github/secret-scanning-alert';
4import { GithubWebhookMiddleware } from '../middleware/github-webhook-middleware';
5import { getLogger } from '../config/logger';
6
7jest.mock('../github/secret-scanning-alert', () => ({
8 secretScanningAlertWebhookHandler: jest.fn(),
9}));
10
11jest.mock('../middleware/github-webhook-middleware', () => ({
12 GithubWebhookMiddleware: jest.fn().mockImplementation((handler) => handler),
13}));
14
15jest.mock('../config/logger', () => ({
16 getLogger: jest.fn().mockReturnValue({
17 info: jest.fn(),
18 error: jest.fn(),
19 }),
20}));
21
22describe('secretScanningAlertQueueMessageHandler', () => {
23 let mockContext;
24 let mockPayload;
25
26 beforeEach(() => {
27 jest.clearAllMocks();
28 mockPayload = {
29 action: 'created',
30 alert: {
31 number: 42,
32 created_at: '2021-04-01T00:00:00Z',
33 url: 'https://api.github.com/repos/octocat/Hello-World/secret-scanning/42',
34 html_url: 'https://github.com/octocat/Hello-World/security/secret-scanning/42',
35 state: 'open',
36 resolution: null,
37 resolved_by: null,
38 resolved_at: null,
39 },
40 repository: {
41 id: 123456,
42 name: 'Hello-World',
43 full_name: 'octocat/Hello-World',
44 private: false,
45 },
46 sender: {
47 login: 'octocat',
48 id: 1,
49 },
50 };
51 mockContext = new WebhookContext({
52 id: mockPayload.alert.number.toString(),
53 name: 'secret_scanning_alert',
54 payload: mockPayload,
55 log: getLogger('secret-scanning-alert'),
56 action: mockPayload.action,
57 gitHubAppConfig: {},
58 });
59 });
60
61 it('should process a secret scanning alert event successfully', async () => {
62 await secretScanningAlertQueueMessageHandler(mockPayload);
63
64 expect(GithubWebhookMiddleware).toHaveBeenCalledWith(secretScanningAlertWebhookHandler);
65 expect(secretScanningAlertWebhookHandler).toHaveBeenCalledWith(mockContext);
66 expect(mockContext.log.info).toHaveBeenCalledWith('Secret scanning alert was successfully processed');
67 });
68
69 it('should log an error and rethrow when processing of a secret scanning alert event fails', async () => {
70 const error = new Error('Processing failed');
71 (secretScanningAlertWebhookHandler as jest.Mock).mockRejectedValueOnce(error);
72
73 await expect(secretScanningAlertQueueMessageHandler(mockPayload)).rejects.toThrow(error);
74
75 expect(mockContext.log.error).toHaveBeenCalledWith({ err: error }, 'Failed to process secret scanning alert');
76 });
77});
78
- Create a new test file
src/sqs/secret-scanning-alert.test.ts
for the "secret_scanning_alert" message handler. - Write tests for the
secretScanningAlertQueueMessageHandler
function to ensure it processes events correctly. - Use test data that simulates different scenarios of "secret_scanning_alert" events.
  Run GitHub Actions for
src/sqs/secret-scanning-alert.test.tsÂ
Ran GitHub Actions for 0ecc70d702d07918740b06deae759626c2d9c5ed:
  ModifyChanged
src/sqs/queues.ts:41-77Â
Changed src/sqs/queues.ts
in 0b7cc97
   Â
8 | import { branchQueueMessageHandler } from "./branch"; | 8 | import { branchQueueMessageHandler } from "./branch"; |
9 | import { getLogger } from "config/logger"; | 9 | import { getLogger } from "config/logger"; |
10 | import type { BackfillMessagePayload, PushQueueMessagePayload, DeploymentMessagePayload, BranchMessagePayload } from "./sqs.types"; | 10 | import type { BackfillMessagePayload, PushQueueMessagePayload, DeploymentMessagePayload, BranchMessagePayload } from "./sqs.types"; |
11 | import { SecretScanningAlertMessagePayload, secretScanningAlertQueueMessageHandler } from "./secret-scanning-alert"; | ||
11 | import { backfillErrorHandler } from "~/src/sqs/backfill-error-handler"; | 12 | import { backfillErrorHandler } from "~/src/sqs/backfill-error-handler"; |
12 | 13 | ||
13 | const LONG_POLLING_INTERVAL_SEC = 3; | 14 | const LONG_POLLING_INTERVAL_SEC = 3; |
... | |||
75 | webhookMetricWrapper(jiraAndGitHubErrorsHandler, "create") | 76 | webhookMetricWrapper(jiraAndGitHubErrorsHandler, "create") |
76 | ), | 77 | ), |
77 | 78 | ||
79 | secret_scanning_alert: new SqsQueue<SecretScanningAlertMessagePayload>({ | ||
80 | queueName: "secret_scanning_alert", | ||
81 | queueUrl: envVars.SQS_SECRET_SCANNING_ALERT_QUEUE_URL, | ||
82 | queueRegion: envVars.SQS_SECRET_SCANNING_ALERT_QUEUE_REGION, | ||
83 | longPollingIntervalSec: LONG_POLLING_INTERVAL_SEC, | ||
84 | timeoutSec: 60, | ||
85 | maxAttempts: 5 | ||
86 | }, secretScanningAlertQueueMessageHandler, webhookMetricWrapper(jiraAndGitHubErrorsHandler, "secret_scanning_alert")), | ||
87 | |||
78 | start: () => { | 88 | start: () => { |
79 | logger.info("Starting queues"); | 89 | logger.info("Starting queues"); |
80 | sqsQueues.backfill.start(); | 90 | sqsQueues.backfill.start(); |
- Import the
SecretScanningAlertMessagePayload
type and thesecretScanningAlertQueueMessageHandler
function from the newly createdsecret-scanning-alert.ts
file. - Add a new
SqsQueue
instance for "secret_scanning_alert" events to thesqsQueues
object. Configure it with the appropriate queue name, URL, region, and other settings similar to the existing queues. - Use the
secretScanningAlertQueueMessageHandler
as the message handler for the new queue.
Modified file with Assistant API
  Run GitHub Actions for
src/sqs/queues.tsÂ
Ran GitHub Actions for 0b7cc97d1b90d9fbf84671116c61eec72df4ec3d:
  ModifyChanged
src/routes/github/webhook/webhook-receiver-post.ts:27-150Â
Changed src/routes/github/webhook/webhook-receiver-post.ts
in 10b0f1f
   Â
140 | await GithubWebhookMiddleware(dependabotAlertWebhookHandler)(context); | 140 | await GithubWebhookMiddleware(dependabotAlertWebhookHandler)(context); |
141 | break; | 141 | break; |
142 | case "secret_scanning_alert": | 142 | case "secret_scanning_alert": |
143 | await GithubWebhookMiddleware(secretScanningAlertWebhookHandler)(context); | 143 | await sqsQueues.secret_scanning_alert.sendMessage(payload); |
144 | break; | 144 | break; |
145 | case "installation": | 145 | case "installation": |
146 | if (context.action === "new_permissions_accepted") { | 146 | if (context.action === "new_permissions_accepted") { |
- Locate the switch case for "secret_scanning_alert" events.
- Instead of processing the "secret_scanning_alert" event directly, enqueue the event to the new "secret_scanning_alert" queue created in
queues.ts
. - Ensure that the payload passed to the queue matches the
SecretScanningAlertMessagePayload
type.
Modified file with Assistant API
  Run GitHub Actions for
src/routes/github/webhook/webhook-receiver-post.tsÂ
Ran GitHub Actions for 10b0f1f7f48153da7e730651024ccf5b7d7c5855:
Plan
This is based on the results of the Planning step. The plan may expand from failed GitHub Actions runs.
  Create
src/sqs/secret-scanning-alert.ts
b7e8720
 1import { WebhookContext } from "../routes/github/webhook/webhook-context";
2import { secretScanningAlertWebhookHandler } from "../github/secret-scanning-alert";
3import { GithubWebhookMiddleware } from "../middleware/github-webhook-middleware";
4import { Logger } from "bunyan";
5import { getLogger } from "../config/logger";
6
7export type SecretScanningAlertMessagePayload = {
8 action: string;
9 alert: {
10 number: number;
11 created_at: string;
12 url: string;
13 html_url: string;
14 state: string;
15 resolution: string | null;
16 resolved_by: string | null;
17 resolved_at: string | null;
18 };
19 repository: {
20 id: number;
21 name: string;
22 full_name: string;
23 private: boolean;
24 };
25 sender: {
26 login: string;
27 id: number;
28 };
29 // Add any additional properties that are specific to the "secret_scanning_alert" event payload
30};
31
32export const secretScanningAlertQueueMessageHandler = async (messagePayload: SecretScanningAlertMessagePayload): Promise<void> => {
33 const logger: Logger = getLogger("secret-scanning-alert");
34 try {
35 const webhookContext = new WebhookContext({
36 id: messagePayload.alert.number.toString(),
37 name: "secret_scanning_alert",
38 payload: messagePayload,
39 log: logger,
40 action: messagePayload.action,
41 gitHubAppConfig: {
42 // Populate with necessary GitHub app configuration
43 }
44 });
45
46 await GithubWebhookMiddleware(secretScanningAlertWebhookHandler)(webhookContext);
47 logger.info("Secret scanning alert was successfully processed");
48 } catch (err: unknown) {
49 logger.error({ err }, "Failed to process secret scanning alert");
50 throw err; // Rethrow the error to be handled by the caller or SQS dead-letter queue
51 }
52};
53
  Run GitHub Actions for
src/sqs/secret-scanning-alert.tsÂ
  Create
src/sqs/secret-scanning-alert.test.ts
0ecc70d
 1import { secretScanningAlertQueueMessageHandler } from './secret-scanning-alert';
2import { WebhookContext } from '../routes/github/webhook/webhook-context';
3import { secretScanningAlertWebhookHandler } from '../github/secret-scanning-alert';
4import { GithubWebhookMiddleware } from '../middleware/github-webhook-middleware';
5import { getLogger } from '../config/logger';
6
7jest.mock('../github/secret-scanning-alert', () => ({
8 secretScanningAlertWebhookHandler: jest.fn(),
9}));
10
11jest.mock('../middleware/github-webhook-middleware', () => ({
12 GithubWebhookMiddleware: jest.fn().mockImplementation((handler) => handler),
13}));
14
15jest.mock('../config/logger', () => ({
16 getLogger: jest.fn().mockReturnValue({
17 info: jest.fn(),
18 error: jest.fn(),
19 }),
20}));
21
22describe('secretScanningAlertQueueMessageHandler', () => {
23 let mockContext;
24 let mockPayload;
25
26 beforeEach(() => {
27 jest.clearAllMocks();
28 mockPayload = {
29 action: 'created',
30 alert: {
31 number: 42,
32 created_at: '2021-04-01T00:00:00Z',
33 url: 'https://api.github.com/repos/octocat/Hello-World/secret-scanning/42',
34 html_url: 'https://github.com/octocat/Hello-World/security/secret-scanning/42',
35 state: 'open',
36 resolution: null,
37 resolved_by: null,
38 resolved_at: null,
39 },
40 repository: {
41 id: 123456,
42 name: 'Hello-World',
43 full_name: 'octocat/Hello-World',
44 private: false,
45 },
46 sender: {
47 login: 'octocat',
48 id: 1,
49 },
50 };
51 mockContext = new WebhookContext({
52 id: mockPayload.alert.number.toString(),
53 name: 'secret_scanning_alert',
54 payload: mockPayload,
55 log: getLogger('secret-scanning-alert'),
56 action: mockPayload.action,
57 gitHubAppConfig: {},
58 });
59 });
60
61 it('should process a secret scanning alert event successfully', async () => {
62 await secretScanningAlertQueueMessageHandler(mockPayload);
63
64 expect(GithubWebhookMiddleware).toHaveBeenCalledWith(secretScanningAlertWebhookHandler);
65 expect(secretScanningAlertWebhookHandler).toHaveBeenCalledWith(mockContext);
66 expect(mockContext.log.info).toHaveBeenCalledWith('Secret scanning alert was successfully processed');
67 });
68
69 it('should log an error and rethrow when processing of a secret scanning alert event fails', async () => {
70 const error = new Error('Processing failed');
71 (secretScanningAlertWebhookHandler as jest.Mock).mockRejectedValueOnce(error);
72
73 await expect(secretScanningAlertQueueMessageHandler(mockPayload)).rejects.toThrow(error);
74
75 expect(mockContext.log.error).toHaveBeenCalledWith({ err: error }, 'Failed to process secret scanning alert');
76 });
77});
78
  Run GitHub Actions for
src/sqs/secret-scanning-alert.test.tsÂ
  Modify
src/sqs/queues.ts:41-77Â
  Run GitHub Actions for
src/sqs/queues.tsÂ
  Run GitHub Actions for
src/routes/github/webhook/webhook-receiver-post.tsÂ
Code Snippets Found
This is based on the results of the Searching step.
src/routes/github/webhook/webhook-receiver-post.ts:27-150
 27
28export const WebhookReceiverPost = async (request: Request, response: Response): Promise<void> => {
29 const eventName = request.headers["x-github-event"] as string;
30 const signatureSHA256 = request.headers["x-hub-signature-256"] as string;
31 const id = request.headers["x-github-delivery"] as string;
32 const uuid = request.params.uuid;
33 const payload = request.body;
34 const parentLogger = (request.log || getLogger(LOGGER_NAME));
35 const logger = parentLogger.child({
36 paramUuid: uuid,
37 xGitHubDelivery: id,
38 xGitHubEvent: eventName,
39 ...extraLoggerInfo(payload, parentLogger)
40 });
41 logger.info("Webhook received");
42 let webhookContext;
43 try {
44 const { webhookSecrets, gitHubServerApp } = await getWebhookSecrets(uuid);
45 const isVerified = webhookSecrets.some((secret, index) => {
46 const matchesSignature = createHash(request.rawBody, secret) === signatureSHA256;
47 /**
48 * The latest updated webhook secret will be at index 0,
49 * Once we stop receiving logs with index other than 0,
50 * can then completely remove the old webhook secrets.
51 */
52 if (matchesSignature) {
53 logger.info({ index }, "Matched webhook index");
54 }
55 return matchesSignature;
56 });
57
58 if (!isVerified) {
59 logger.warn("Signature validation failed, returning 400");
60 response.status(400).send("signature does not match event payload and secret");
61 return;
62 }
63 webhookContext = new WebhookContext({
64 id: id,
65 name: eventName,
66 payload: payload,
67 log: logger,
68 action: payload.action,
69 gitHubAppConfig: {
70 ...(!gitHubServerApp ? {
71 gitHubAppId: undefined,
72 appId: parseInt(envVars.APP_ID),
73 clientId: envVars.GITHUB_CLIENT_ID,
74 gitHubBaseUrl: GITHUB_CLOUD_BASEURL,
75 gitHubApiUrl: GITHUB_CLOUD_API_BASEURL,
76 uuid: undefined
77 } : {
78 gitHubAppId: gitHubServerApp.id,
79 appId: gitHubServerApp.appId,
80 clientId: gitHubServerApp.gitHubClientId,
81 gitHubBaseUrl: gitHubServerApp.gitHubBaseUrl,
82 gitHubApiUrl: gitHubServerApp.gitHubBaseUrl,
83 uuid
84 })
85 }
86 });
87 await webhookRouter(webhookContext);
88 webhookContext.log.info("Webhook was successfully processed");
89 response.sendStatus(204);
90
91 } catch (err: unknown) {
92 (webhookContext?.log || logger).error({ err }, `Something went wrong, returning 400: ${errorStringFromUnknown(err)}`);
93 response.sendStatus(400);
94 }
95};
96
97const webhookRouter = async (context: WebhookContext) => {
98 const VALID_PULL_REQUEST_ACTIONS = ["opened", "reopened", "closed", "edited", "converted_to_draft", "ready_for_review"];
99 switch (context.name) {
100 case "push":
101 await GithubWebhookMiddleware(pushWebhookHandler)(context);
102 break;
103 case "issue_comment":
104 if (context.action === "created" || context.action === "edited") {
105 await webhookTimeout(GithubWebhookMiddleware(issueCommentWebhookHandler))(context);
106 }
107 break;
108 case "issues":
109 if (context.action === "opened" || context.action === "edited") {
110 await GithubWebhookMiddleware(issueWebhookHandler)(context);
111 }
112 break;
113 case "pull_request":
114 if (context.action && VALID_PULL_REQUEST_ACTIONS.includes(context.action)) {
115 await GithubWebhookMiddleware(pullRequestWebhookHandler)(context);
116 }
117 break;
118 case "pull_request_review":
119 await GithubWebhookMiddleware(pullRequestWebhookHandler)(context);
120 break;
121 case "create":
122 await GithubWebhookMiddleware(createBranchWebhookHandler)(context);
123 break;
124 case "delete":
125 await GithubWebhookMiddleware(deleteBranchWebhookHandler)(context);
126 break;
127 case "repository":
128 await GithubWebhookMiddleware(repositoryWebhookHandler)(context);
129 break;
130 case "workflow_run":
131 await GithubWebhookMiddleware(workflowWebhookHandler)(context);
132 break;
133 case "deployment_status":
134 await GithubWebhookMiddleware(deploymentWebhookHandler)(context);
135 break;
136 case "code_scanning_alert":
137 await GithubWebhookMiddleware(codeScanningAlertWebhookHandler)(context);
138 break;
139 case "dependabot_alert":
140 await GithubWebhookMiddleware(dependabotAlertWebhookHandler)(context);
141 break;
142 case "secret_scanning_alert":
143 await GithubWebhookMiddleware(secretScanningAlertWebhookHandler)(context);
144 break;
145 case "installation":
146 if (context.action === "new_permissions_accepted") {
147 await GithubWebhookMiddleware(installationWebhookHandler)(context);
148 }
149 break;
150 }
src/sqs/queues.ts:41-77
 41
42export const sqsQueues = {
43 backfill: backfillQueue,
44
45 push: new SqsQueue<PushQueueMessagePayload>({
46 queueName: "push",
47 queueUrl: envVars.SQS_PUSH_QUEUE_URL,
48 queueRegion: envVars.SQS_PUSH_QUEUE_REGION,
49 longPollingIntervalSec: LONG_POLLING_INTERVAL_SEC,
50 timeoutSec: 60,
51 maxAttempts: 5
52 }, pushQueueMessageHandler, webhookMetricWrapper(jiraAndGitHubErrorsHandler, "push")),
53
54 deployment: new SqsQueue<DeploymentMessagePayload>({
55 queueName: "deployment",
56 queueUrl: envVars.SQS_DEPLOYMENT_QUEUE_URL,
57 queueRegion: envVars.SQS_DEPLOYMENT_QUEUE_REGION,
58 longPollingIntervalSec: LONG_POLLING_INTERVAL_SEC,
59 timeoutSec: 60,
60 maxAttempts: 5
61 },
62 deploymentQueueMessageHandler,
63 webhookMetricWrapper(jiraAndGitHubErrorsHandler, "deployment_status")
64 ),
65
66 branch: new SqsQueue<BranchMessagePayload>({
67 queueName: "branch",
68 queueUrl: envVars.SQS_BRANCH_QUEUE_URL,
69 queueRegion: envVars.SQS_BRANCH_QUEUE_REGION,
70 longPollingIntervalSec: LONG_POLLING_INTERVAL_SEC,
71 timeoutSec: 60,
72 maxAttempts: 5
73 },
74 branchQueueMessageHandler,
75 webhookMetricWrapper(jiraAndGitHubErrorsHandler, "create")
76 ),
77