-
Notifications
You must be signed in to change notification settings - Fork 202
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
Sign k6 requests with HMAC to enable WAF bypass #4908
Changes from all commits
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 |
---|---|---|
|
@@ -11,8 +11,11 @@ | |
"author": "Openverse Contributors <[email protected]>", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^26.0.1", | ||
"@rollup/plugin-node-resolve": "^15.2.3", | ||
"@rollup/plugin-typescript": "^11.1.6", | ||
"@types/k6": "^0.53.1", | ||
"core-js": "^3.37.1", | ||
"glob": "^11.0.0", | ||
"rollup": "^4.21.1", | ||
"typescript": "^5.6.2" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Courtesy of @mbforbes via https://gist.github.com/robingustafsson/7dd6463d85efdddbb0e4bcd3ecc706e1?permalink_comment_id=4884925#gistcomment-4884925 | ||
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. Random thought but it'd be kinda cool to make folks co-authors of the PR when we reuse code like this! The comment is totally sufficient, of course. 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. Hmmm, I'd be worried about going to that extent, it might imply they were involved in the PR more broadly, because we squash commits onto main? I'd be worried about doing that without their consent. |
||
|
||
import { createHMAC } from "k6/crypto" | ||
|
||
import type { Algorithm } from "k6/crypto" | ||
|
||
export function sign( | ||
data: string | ArrayBuffer, | ||
hashAlg: Algorithm, | ||
secret: string | ArrayBuffer | ||
) { | ||
const hasher = createHMAC(hashAlg, secret) | ||
hasher.update(data) | ||
return hasher.digest("base64rawurl") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export const PROJECT_ID = 3713375 | ||
export const FRONTEND_URL = __ENV.FRONTEND_URL || "https://openverse.org/" | ||
// Default to location of `ov j p frontend prod` | ||
export const FRONTEND_URL = __ENV.FRONTEND_URL || "http://127.0.0.1:8443/" |
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. This whole file is great 👨🍳 💋 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* Wrap k6/http functions to sign requests to allow firewall bypass. | ||
*/ | ||
|
||
import { | ||
get, | ||
post, | ||
del, | ||
options, | ||
head, | ||
patch, | ||
put, | ||
type RequestBody, | ||
type RefinedParams, | ||
type ResponseType, | ||
} from "k6/http" | ||
import { HttpURL } from "k6/experimental/tracing" | ||
|
||
// Polyfills URL | ||
import "core-js/web/url" | ||
import "core-js/web/url-search-params" | ||
|
||
import { sign } from "./crypto" | ||
|
||
const signingSecret = __ENV.signing_secret | ||
|
||
function getSignedParams< | ||
RT extends ResponseType | undefined, | ||
P extends RefinedParams<RT>, | ||
>(url: string | HttpURL, params: P): P { | ||
if (!signingSecret) { | ||
return params | ||
} | ||
|
||
const _url = new URL(url.toString()) | ||
|
||
const timestamp = Math.floor(Date.now() / 1000) | ||
const resource = `${_url.pathname}${_url.search}${timestamp}` | ||
const mac = sign(resource, "sha256", signingSecret) | ||
|
||
return { | ||
...params, | ||
headers: { | ||
...(params.headers ?? {}), | ||
"x-ov-cf-mac": mac, | ||
"x-ov-cf-timestamp": timestamp.toString(), | ||
}, | ||
} | ||
} | ||
|
||
type BodylessHttpFn = typeof get | ||
type BodiedHttpFn = typeof post | ||
|
||
function wrapHttpFunction<F extends BodylessHttpFn | BodiedHttpFn>(fn: F): F { | ||
// url is always the first argument, params is always the last argument | ||
const urlIdx = 0 | ||
const paramsIdx = fn.length - 1 | ||
|
||
return (<RT extends ResponseType | undefined>(...args: Parameters<F>) => { | ||
const signedParams = getSignedParams( | ||
args[urlIdx], | ||
(args[paramsIdx] as RefinedParams<RT>) ?? {} | ||
) | ||
|
||
return paramsIdx === 1 | ||
? (fn as BodylessHttpFn)(args[urlIdx], signedParams) | ||
: (fn as BodiedHttpFn)(args[urlIdx], args[1] as RequestBody, signedParams) | ||
}) as F | ||
} | ||
|
||
/** | ||
* Wrapper around k6/http that signs requests with Openverse's | ||
* HMAC shared secret, enabling firewall bypass for load testing. | ||
*/ | ||
export const http = { | ||
get: wrapHttpFunction(get), | ||
post: wrapHttpFunction(post), | ||
del: wrapHttpFunction(del), | ||
options: wrapHttpFunction(options), | ||
head: wrapHttpFunction(head), | ||
put: wrapHttpFunction(put), | ||
patch: wrapHttpFunction(patch), | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New rollup plugins used to support bundling npm dependencies, because k6 doesn't support them unless they are bundled in. In our case, we need it for
core-js
to provide a sensible URL API.I chose this as an alternative to the jslib URL implementation because (a) that still requires importing
URL
and (b) there are no means to support types for it. The suggested approach for supporting types from k6 jslib libraries is to vendor the library code.I did not try declaring the types using
declare module "...jslib..."
so I'll try that for good measure (which would be nice for the other jslib function we use).However, I've found that so far the k6 and jslib implementations of certain Web APIs have very subtle and annoying differences to the specification, which mostly just causes a bit of mental overload trying to keep track of. Which is to say, it's nice to be able to bundle in polyfills from core-js that will behave according to the specification!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
k6-jslib-url just exports core-js: https://github.com/grafana/k6-jslib-url/blob/main/index.src.js
For the sake of making sure bundling in node dependencies is sorted for anyone else working in these tests, I think it's best to go ahead and use core-js directly in this PR, as a means of integrating the bundling process before it's needed otherwise.