-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Send GitHub payload for POST webhook endpoints and send delivery ID #156
Changes from all commits
a6ddc08
5cd52c4
96ef78c
e871352
70540f8
2ad36d6
23c6bf7
7fff3f6
444b3d6
5383dbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,8 +3,8 @@ | ||||||||||
const url = require("url"); | |||||||||||
const https = require("https"); | |||||||||||
const crypto = require("crypto"); | |||||||||||
const qs = require("querystring"); | |||||||||||
const LAMBDA_USER_AGENT = "DockstoreLambda (NodeJs)"; | |||||||||||
const DELIVERY_ID_HEADER = "X-GitHub-Delivery"; | |||||||||||
|
|||||||||||
// Verification function to check if it is actually GitHub who is POSTing here | |||||||||||
const verifyGitHub = (req, payload) => { | |||||||||||
|
@@ -26,16 +26,17 @@ | ||||||||||
}; | |||||||||||
|
|||||||||||
// Makes a POST request to the given path | |||||||||||
function postEndpoint(path, postBody, callback) { | |||||||||||
function postEndpoint(path, postBody, deliveryId, callback) { | |||||||||||
console.log("POST " + path); | |||||||||||
console.log(qs.stringify(postBody)); | |||||||||||
console.log(postBody); | |||||||||||
|
|||||||||||
const options = url.parse(path); | |||||||||||
options.method = "POST"; | |||||||||||
options.headers = { | |||||||||||
"Content-Type": "application/x-www-form-urlencoded", | |||||||||||
"Content-Type": "application/json", | |||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The old web service will probably return a 415 if invoked with this header, won't it? If so, that means that GitHub app events will be lost in the window between the deployment of this lambda and the new web service. If we feel it's worth the effort, you could check for a 415 status code in line 59/60 and covert it to a 5xx, which I believe will force a retry later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch, yeah it would result in a 415 that would not be retried.
Think the fix would be a bit more involved than this. The following table specifies what would happen if the lambda sends the request to a specific webservice version for the old and new lambda.
I think your suggestion would be the fix on the second row of the table. We could still get a 415 in the first row scenario. A fix could be to create a hotfix lambda that converts a 415 to 500 in anticipation of the 1.15 release to prevent loss of GitHub App events during the 1.15 deploy. Is it worth it? I'm not sure. Alternative is to have some down time by bringing down the old webservice first, but I'm not sure where we stand on having down time There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my initial comment, I was thinking the lambda would be deployed with the core stack, and so we had a window between the core stack update and the the new Dockstore deploy when we would miss events. But this lambda is deployed with the Dockstore stack. So what will happen is, I think:
Three questions:
I think we're OK with downtime. But I'd prefer it if it doesn't entail missing GitHub events -- I wouldn't want to miss a major release/tag of a major workflow. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There's a small period of time between 5 and 6 where both 1.14 and 1.15 will be running because it may take a few minutes to set the desired count of the 1.14 ECS service to 0. During this time, both lambdas will invoke dockstore.org and the load balancer will send the request to either the 1.14 or 1.15 webservice, but we don't know which it will send it to. This is the window where we might lose events. If both lambdas send the request to the same webservice, then one out of the two will succeed so no problem. However, if the 1.14 lambda sends the request to the 1.15 webservice and the 1.15 lambda sends the request to the 1.14 webservice, then both will fail and we lose the event.
Sounds about right, with the note I added above.
I don't think we need to worry on the SQS side. The lambda has retries if the status is >= 500. Half will be failing because of a 415 error, so those will not retry unless we add special handling to convert it to a 500. In this case, seems like it's fine to let the 415 fail because the other half will succeed, i.e. the event will be successfully processed by one of the two lambdas.
Bringing down the 1.14 SQS lambda trigger will ensure that only the 1.15 lambda is triggered, but I think it's still possible for the 1.15 lambda to call the 1.14 webservice during the window I initially mentioned where both the 1.14 and 1.15 webservices are running and we could lose the event.
Hmm, thinking it through, I'm not sure downtime would fix our problem and we could lose events.
Looks like we don't have a perfect solution yet for how to prevent loss of GitHub events so I'm gonna do some thinking. I'm wondering if https://ucsc-cgl.atlassian.net/browse/SEAB-5604 can help us by allowing us to record events and retry missed events after a deploy There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't thought it through entirely, but what if we:
I guess it depends on what happens events occurring between 1 and 3; when there is no trigger, do the events stay in the queue until a trigger is added again? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm sounds promising, I'll test it. My theory is that the events should stay in the queue until a trigger is added again. The SQS message retention period is 14 Days and AWS docs say that the "lambda polls the queue" as opposed to the queue pushing an event to the lambda There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tested in QA by:
So yes seems like the events stay in the queue until a trigger is added again |
|||||||||||
Authorization: "Bearer " + process.env.DOCKSTORE_TOKEN, | |||||||||||
"User-Agent": LAMBDA_USER_AGENT, | |||||||||||
"X-GitHub-Delivery": deliveryId, | |||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's unlikely, and there will only be a small window where it could happen, but I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, a null or undefined deliveryId header results in an invocation error in the lambda (example). Generating one as suggested by dockstore/dockstore#5626 (comment) would fix that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed it so it generates a random UUID if the header isn't available |
|||||||||||
}; | |||||||||||
|
|||||||||||
const req = https.request(options, (res) => { | |||||||||||
|
@@ -63,7 +64,7 @@ | ||||||||||
}); | |||||||||||
return res; | |||||||||||
}); | |||||||||||
req.write(qs.stringify(postBody)); | |||||||||||
req.write(JSON.stringify(postBody)); | |||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We were already passing the body? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We were passing our own custom body like below, not the GitHub payload
|
|||||||||||
req.end(); | |||||||||||
} | |||||||||||
|
|||||||||||
|
@@ -73,7 +74,7 @@ | ||||||||||
repository, | |||||||||||
reference, | |||||||||||
username, | |||||||||||
installationId, | |||||||||||
deliveryId, | |||||||||||
callback | |||||||||||
) { | |||||||||||
console.log("DELETE " + path); | |||||||||||
|
@@ -82,13 +83,13 @@ | ||||||||||
urlWithParams.searchParams.append("gitReference", reference); | |||||||||||
urlWithParams.searchParams.append("repository", repository); | |||||||||||
urlWithParams.searchParams.append("username", username); | |||||||||||
urlWithParams.searchParams.append("installationId", installationId); | |||||||||||
|
|||||||||||
const options = url.parse(urlWithParams.href); | |||||||||||
options.method = "DELETE"; | |||||||||||
options.headers = { | |||||||||||
Authorization: "Bearer " + process.env.DOCKSTORE_TOKEN, | |||||||||||
"User-Agent": LAMBDA_USER_AGENT, | |||||||||||
"X-GitHub-Delivery": deliveryId, | |||||||||||
}; | |||||||||||
|
|||||||||||
const req = https.request(options, (res) => { | |||||||||||
|
@@ -110,38 +111,6 @@ | ||||||||||
req.end(); | |||||||||||
} | |||||||||||
|
|||||||||||
/** | |||||||||||
* Handles github apps payload parsing for the 'installation_repositories' event type and creates a JSON to call the post endpoint. | |||||||||||
* Currently, if the payload's action is not 'added', we return null, as we don't want to call the endpoint. | |||||||||||
* | |||||||||||
* @param body - JSON payload body | |||||||||||
* @returns {null|{repositories: *, installationId: string, username: *}} | |||||||||||
*/ | |||||||||||
function handleInstallationRepositoriesEvent(body) { | |||||||||||
// Currently ignoring repository removal events, only calling the endpoint if we are adding a repository. | |||||||||||
if (body.action === "added") { | |||||||||||
console.log("Valid installation event"); | |||||||||||
const username = body.sender.login; | |||||||||||
const installationId = body.installation.id; | |||||||||||
const repositoriesAdded = body.repositories_added; | |||||||||||
const repositories = []; | |||||||||||
repositoriesAdded.forEach((repo) => { | |||||||||||
repositories.push(repo.full_name); | |||||||||||
}); | |||||||||||
|
|||||||||||
return { | |||||||||||
installationId: installationId, | |||||||||||
username: username, | |||||||||||
repositories: repositories.join(","), | |||||||||||
}; | |||||||||||
} else { | |||||||||||
console.log( | |||||||||||
'installation_repositories event ignored "' + body.action + '" action' | |||||||||||
); | |||||||||||
return null; | |||||||||||
} | |||||||||||
} | |||||||||||
|
|||||||||||
// Performs an action based on the event type (action) | |||||||||||
function processEvent(event, callback) { | |||||||||||
// Usually returns array of records, however it is fixed to only return 1 record | |||||||||||
|
@@ -174,19 +143,39 @@ | ||||||||||
|
|||||||||||
var path = process.env.API_URL; | |||||||||||
|
|||||||||||
// Handle installation events | |||||||||||
var deliveryId; | |||||||||||
if (requestBody[DELIVERY_ID_HEADER]) { | |||||||||||
deliveryId = requestBody[DELIVERY_ID_HEADER]; | |||||||||||
} else { | |||||||||||
// TODO: remove this after 1.15. | |||||||||||
// This was added because there's a small period of time during the 1.15 deploy where the header isn't available | |||||||||||
console.log( | |||||||||||
"Could not retrieve X-GitHub-Delivery header, generating a random UUID" | |||||||||||
); | |||||||||||
deliveryId = crypto.randomUUID(); | |||||||||||
} | |||||||||||
|
|||||||||||
console.log("X-GitHub-Delivery: " + deliveryId); | |||||||||||
|
|||||||||||
var githubEventType = requestBody["X-GitHub-Event"]; | |||||||||||
// Handle installation events | |||||||||||
if (githubEventType === "installation_repositories") { | |||||||||||
const pushPostBody = handleInstallationRepositoriesEvent(body); | |||||||||||
path += "workflows/github/install"; | |||||||||||
// Currently ignoring repository removal events, only calling the endpoint if we are adding a repository. | |||||||||||
if (body.action === "added") { | |||||||||||
console.log("Valid installation event"); | |||||||||||
path += "workflows/github/install"; | |||||||||||
const repositoriesAdded = body.repositories_added; | |||||||||||
const repositories = repositoriesAdded.map((repo) => repo.full_name); | |||||||||||
|
|||||||||||
if (pushPostBody != null) { | |||||||||||
postEndpoint(path, pushPostBody, (response) => { | |||||||||||
postEndpoint(path, body, deliveryId, (response) => { | |||||||||||
const successMessage = | |||||||||||
"The GitHub app was successfully installed on repositories " + | |||||||||||
pushPostBody.repositories; | |||||||||||
repositories; | |||||||||||
handleCallback(response, successMessage, callback); | |||||||||||
}); | |||||||||||
} else { | |||||||||||
console.log( | |||||||||||
'installation_repositories event ignored "' + body.action + '" action' | |||||||||||
Check failure Code scanning / CodeQL Log injection High
Log entry depends on a
user-provided value Error loading related location Loading There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if we need to worry about these log injections. This was existing code that I just moved, and the payload is from GitHub There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BUT WHAT IF ELON BYES GITHUB There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @david4096 Double-checking, are these log injection findings okay to ignore? The payload is from GitHub There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Confirmed via Slack convo with David that these are okay to ignore |
|||||||||||
); | |||||||||||
} | |||||||||||
} else if (githubEventType === "push") { | |||||||||||
/** | |||||||||||
|
@@ -212,20 +201,11 @@ | ||||||||||
if (!body.deleted) { | |||||||||||
console.log("Valid push event"); | |||||||||||
const repository = body.repository.full_name; | |||||||||||
const username = body.sender.login; | |||||||||||
const gitReference = body.ref; | |||||||||||
const installationId = body.installation.id; | |||||||||||
|
|||||||||||
const pushPostBody = { | |||||||||||
gitReference: gitReference, | |||||||||||
installationId: installationId, | |||||||||||
repository: repository, | |||||||||||
username: username, | |||||||||||
}; | |||||||||||
|
|||||||||||
path += "workflows/github/release"; | |||||||||||
|
|||||||||||
postEndpoint(path, pushPostBody, (response) => { | |||||||||||
postEndpoint(path, body, deliveryId, (response) => { | |||||||||||
const successMessage = | |||||||||||
"The associated entries on Dockstore for repository " + | |||||||||||
repository + | |||||||||||
|
@@ -239,7 +219,6 @@ | ||||||||||
const repository = body.repository.full_name; | |||||||||||
const gitReference = body.ref; | |||||||||||
const username = body.sender.login; | |||||||||||
const installationId = body.installation.id; | |||||||||||
|
|||||||||||
path += "workflows/github"; | |||||||||||
|
|||||||||||
|
@@ -248,7 +227,7 @@ | ||||||||||
repository, | |||||||||||
gitReference, | |||||||||||
username, | |||||||||||
installationId, | |||||||||||
deliveryId, | |||||||||||
(response) => { | |||||||||||
const successMessage = | |||||||||||
"The associated versions on Dockstore for repository " + | |||||||||||
|
@@ -294,10 +273,6 @@ | ||||||||||
} | |||||||||||
} | |||||||||||
|
|||||||||||
module.exports = { | |||||||||||
handleInstallationRepositoriesEvent, | |||||||||||
}; | |||||||||||
|
|||||||||||
module.exports.handler = (event, context, callback) => { | |||||||||||
processEvent(event, callback); | |||||||||||
}; |
This file was deleted.
Check failure
Code scanning / CodeQL
Log injection High