test: Add test utility to intercept requests to Sentry#7271
Conversation
size-limit report 📦
|
Replay SDK metrics 🚀
develop |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Revision | LCP | CLS | CPU | JS heap avg | JS heap max | netTx | netRx | netCount | netTime |
|---|---|---|---|---|---|---|---|---|---|
| e60cd02 | +56.25 ms | -0.00 ms | +6.32 pp | +927.44 kB | +1.06 MB | +2.21 kB | +41 B | +1 | +117.55 ms |
| e25c067 | +48.34 ms | +0.00 ms | +5.59 pp | +926.37 kB | +1.05 MB | +2.22 kB | +41 B | +1 | +65.23 ms |
| b1b249b | +43.88 ms | +0.00 ms | +4.80 pp | +937.99 kB | +1.05 MB | +2.22 kB | +41 B | +1 | +111.56 ms |
| 12e34d4 | +28.57 ms | +0.00 ms | +5.77 pp | +930.12 kB | +1.04 MB | +2.26 kB | +41 B | +1 | +109.67 ms |
| c46c56c | +65.45 ms | -0.00 ms | +5.38 pp | +930.26 kB | +1.07 MB | +2.21 kB | +41 B | +1 | +91.29 ms |
| 7f4c4ec | +56.64 ms | -0.00 ms | +5.57 pp | +927.42 kB | +1.06 MB | +2.21 kB | +41 B | +1 | +110.83 ms |
| 00d2360 | +55.18 ms | +0.00 ms | +2.23 pp | +934.14 kB | +1.05 MB | +2.22 kB | +41 B | +1 | +71.65 ms |
Last updated: Thu, 23 Feb 2023 14:42:24 GMT
| const sentryRequest = https.request( | ||
| sentryIngestUrl, | ||
| { headers: proxyRequest.headers, method: proxyRequest.method }, | ||
| sentryResponse => { | ||
| sentryResponse.addListener('data', (chunk: Buffer) => { | ||
| proxyResponse.write(chunk, 'binary'); | ||
| sentryResponseChunks.push(chunk); | ||
| }); | ||
|
|
||
| sentryResponse.addListener('end', () => { | ||
| eventCallbackListeners.forEach(listener => { | ||
| const rawProxyRequestBody = Buffer.concat(proxyRequestChunks).toString(); | ||
| const rawSentryResponseBody = Buffer.concat(sentryResponseChunks).toString(); | ||
|
|
||
| const data: SentryRequestCallbackData = { | ||
| envelope: parseEnvelope(rawProxyRequestBody, new TextEncoder(), new TextDecoder()), | ||
| rawProxyRequestBody, | ||
| rawSentryResponseBody, | ||
| sentryResponseStatusCode: sentryResponse.statusCode, | ||
| }; | ||
|
|
||
| listener(Buffer.from(JSON.stringify(data)).toString('base64')); | ||
| }); | ||
| proxyResponse.end(); | ||
| }); | ||
|
|
||
| sentryResponse.addListener('error', err => { | ||
| throw err; | ||
| }); | ||
|
|
||
| proxyResponse.writeHead(sentryResponse.statusCode || 500, sentryResponse.headers); | ||
| }, | ||
| ); |
Check failure
Code scanning / CodeQL
Server-side request forgery
AbhiPrasad
left a comment
There was a problem hiding this comment.
Do we really need both the eventCallbackServer and a proxyServer?
| for (const envelopeItem of envelopeItems) { | ||
| if (callback(envelopeItem)) { | ||
| resolve(envelopeItem); | ||
| return true; |
There was a problem hiding this comment.
m: Why are we returning from the promise? Ditto for the other waitForX methods.
There was a problem hiding this comment.
We're not returning from the promise, we're returning from the callback of waitForRequest. This is to stop listening for events. Do you know a better way to do this?
There was a problem hiding this comment.
yup you're right, the nesting just confused me.
I think it's fine, just takes a bit to get - this is the best way to share state this functional way.
The alternative would be to have this all be in a class mutating some internal state, but that is prob less clean.
| proxyServerName: string, | ||
| callback: (eventData: SentryRequestCallbackData) => boolean, | ||
| ): Promise<SentryRequestCallbackData> { | ||
| const tmpFileWithPort = path.join(os.tmpdir(), `event-proxy-server-${proxyServerName}`); |
There was a problem hiding this comment.
l: If this is expected to keep in sync with tmpFileWithPort generated from eventCallbackServerStartupPromise, I would like it to be a helper func. This way there's no way that it'll ever differ unless done intentionally.
| callback: (eventData: SentryRequestCallbackData) => boolean, | ||
| ): Promise<SentryRequestCallbackData> { | ||
| const tmpFileWithPort = path.join(os.tmpdir(), `event-proxy-server-${proxyServerName}`); | ||
| const eventCallbackServerPort = fs.readFileSync(tmpFileWithPort, 'utf8'); |
| } | ||
| eventContents = ''; | ||
| } else { | ||
| eventContents = eventContents.concat(char); |
There was a problem hiding this comment.
l: why concat over push? push is faster, and we already break mutability by using let eventContents
There was a problem hiding this comment.
Sadly string doesn't have a push method.
|
|
||
| const eventCallbackServerStartupPromise = new Promise<void>(resolve => { | ||
| const listener = eventCallbackServer.listen(0, () => { | ||
| const port = String((listener.address() as AddressInfo).port); |
There was a problem hiding this comment.
| const port = String((listener.address() as AddressInfo).port); | |
| const port = String(eventCallbackServer.address().port); |
l: We can then forgo the listener return as well.
Co-authored-by: Abhijeet Prasad <aprasad@sentry.io>
@AbhiPrasad Thought about just making it different endpoints on the same server but it simplifies defining the tunnel a lot. |
This PR adds a package containing utilities to start a proxy server that is able to intercept requests to sentry (via the
tunneloption) and additionally provides utility functions to listen for particular envelopes/events.Resolves #7263