-
Notifications
You must be signed in to change notification settings - Fork 6
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
Add WebSocket support (ws) #270
Open
timokoessler
wants to merge
29
commits into
main
Choose a base branch
from
ws
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 19 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
c7d2ccc
WIP: Start wrapping ws
timokoessler 0e29774
Fix ws event context, parse message event
timokoessler bfded04
Decode binary web socket messages
timokoessler d5c119c
Check data of ping and pong events
timokoessler 7f85dd1
Wrap other ws listener methods
timokoessler afff1f2
Wrap ws.onevent functions
timokoessler 972901f
fix: Node.js v16 does not support Blob
timokoessler 5fd0431
Parse ws close reason
timokoessler 8a43de0
Add sample ws chat app
timokoessler 5fb7fce
Add ws to source, use AsyncResource
timokoessler 141033a
Add ws e2e tests
timokoessler 3d09625
Add supported package version
timokoessler 2993202
Add AIKIDO_MAX_WS_MSG_SIZE_MB
timokoessler 20cd235
Implement rate limiting and user blocking (ws)
timokoessler 46e0bc2
Merge branch 'main' into ws
timokoessler 37790ad
Remove console.log
timokoessler 9def302
Fix AIKIDO_MAX_WS_MSG_SIZE_MB test
timokoessler f349c20
Improve test coverage, update readme
timokoessler 6a92c2b
Wrap handleUpgrade instead of events
timokoessler 07ef845
Refactor and comment code
timokoessler 905e6e9
Apply requested changes
timokoessler d88dcf4
Refactor websocket data parsing
timokoessler 2a2e252
Fix linting
timokoessler a533e10
Add test for ws data parsing
timokoessler 43aea41
Rename checkWsDataSize to isMessageDataTooLarge
timokoessler 7816dd8
Merge branch 'beta' into ws
timokoessler 0236881
Fix branch after merging beta
timokoessler 344742b
Merge branch 'main' into ws
timokoessler e014589
Merge branch 'main' into ws
timokoessler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
const t = require("tap"); | ||
const { spawn } = require("child_process"); | ||
const { resolve } = require("path"); | ||
const timeout = require("../timeout"); | ||
const { WebSocket } = require("ws"); | ||
|
||
const pathToApp = resolve(__dirname, "../../sample-apps/ws-postgres", "app.js"); | ||
|
||
t.test("it blocks in blocking mode", (t) => { | ||
const server = spawn(`node`, [pathToApp, "4000"], { | ||
env: { ...process.env, AIKIDO_DEBUG: "true", AIKIDO_BLOCKING: "true" }, | ||
}); | ||
|
||
server.on("close", () => { | ||
t.end(); | ||
}); | ||
|
||
server.on("error", (err) => { | ||
t.fail(err.message); | ||
}); | ||
|
||
let stdout = ""; | ||
server.stdout.on("data", (data) => { | ||
stdout += data.toString(); | ||
}); | ||
|
||
let stderr = ""; | ||
server.stderr.on("data", (data) => { | ||
stderr += data.toString(); | ||
}); | ||
|
||
// Wait for the server to start | ||
timeout(2000) | ||
.then(async () => { | ||
// Does not block normal messages | ||
return await new Promise((resolve, reject) => { | ||
const ws = new WebSocket(`ws://localhost:4000`); | ||
|
||
ws.on("error", (err) => { | ||
reject(err); | ||
}); | ||
|
||
ws.on("open", () => { | ||
ws.send("Hello world!"); | ||
}); | ||
|
||
ws.on("message", (data) => { | ||
const str = data.toString(); | ||
if (str.includes("Welcome")) { | ||
return; | ||
} | ||
t.match(str, /Hello world!/); | ||
ws.close(); | ||
resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(async () => { | ||
// Does block sql injection | ||
return await new Promise((resolve, reject) => { | ||
const ws = new WebSocket(`ws://localhost:4000`); | ||
|
||
ws.on("error", (err) => { | ||
reject(err); | ||
}); | ||
|
||
ws.on("open", () => { | ||
ws.send("Bye'); DELETE FROM messages;--"); | ||
}); | ||
|
||
ws.on("message", (data) => { | ||
const str = data.toString(); | ||
if ( | ||
str.includes("Welcome") || | ||
str.includes("Hello") || | ||
str.includes("Bye") | ||
) { | ||
return; | ||
} | ||
t.match(str, /An error occurred/); | ||
ws.close(); | ||
resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(() => { | ||
t.match(stdout, /Starting agent/); | ||
t.match(stderr, /Aikido firewall has blocked an SQL injection/); | ||
}) | ||
.catch((error) => { | ||
t.fail(error.message); | ||
}) | ||
.finally(() => { | ||
server.kill(); | ||
}); | ||
}); | ||
|
||
t.test("it does not block in non-blocking mode", (t) => { | ||
const server = spawn(`node`, [pathToApp, "4001"], { | ||
env: { ...process.env, AIKIDO_DEBUG: "true" }, | ||
}); | ||
|
||
server.on("close", () => { | ||
t.end(); | ||
}); | ||
|
||
server.on("error", (err) => { | ||
t.fail(err.message); | ||
}); | ||
|
||
let stdout = ""; | ||
server.stdout.on("data", (data) => { | ||
stdout += data.toString(); | ||
}); | ||
|
||
let stderr = ""; | ||
server.stderr.on("data", (data) => { | ||
stderr += data.toString(); | ||
}); | ||
|
||
// Wait for the server to start | ||
timeout(2000) | ||
.then(async () => { | ||
// Does not block normal messages | ||
return await new Promise((resolve, reject) => { | ||
const ws = new WebSocket(`ws://localhost:4001`); | ||
|
||
ws.on("error", (err) => { | ||
reject(err); | ||
}); | ||
|
||
ws.on("open", () => { | ||
ws.send("Hello world!"); | ||
}); | ||
|
||
ws.on("message", (data) => { | ||
const str = data.toString(); | ||
if (str.includes("Welcome")) { | ||
return; | ||
} | ||
t.match(str, /Hello world!/); | ||
ws.close(); | ||
resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(async () => { | ||
// Does block sql injection | ||
return await new Promise((resolve, reject) => { | ||
const ws = new WebSocket(`ws://localhost:4001`); | ||
|
||
ws.on("error", (err) => { | ||
reject(err); | ||
}); | ||
|
||
ws.on("open", () => { | ||
ws.send("Bye'); DELETE FROM messages;--"); | ||
}); | ||
|
||
ws.on("message", (data) => { | ||
const str = data.toString(); | ||
if (str.includes("Welcome") || str.includes("Hello")) { | ||
return; | ||
} | ||
t.match(str, /Bye/); | ||
ws.close(); | ||
resolve(); | ||
}); | ||
}); | ||
}) | ||
.then(() => { | ||
t.match(stdout, /Starting agent/); | ||
t.notMatch(stderr, /Aikido firewall has blocked an SQL injection/); | ||
}) | ||
.catch((error) => { | ||
t.fail(error.message); | ||
}) | ||
.finally(() => { | ||
server.kill(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import * as t from "tap"; | ||
import { getMaxWsMsgSize } from "./getMaxWsMsgSize"; | ||
|
||
t.test( | ||
"returns default max body size when AIKIDO_MAX_WS_MSG_SIZE_MB is not set", | ||
async (t) => { | ||
delete process.env.AIKIDO_MAX_WS_MSG_SIZE_MB; | ||
t.equal( | ||
getMaxWsMsgSize(), | ||
20 * 1024 * 1024, | ||
"should return 20 MB as default" | ||
); | ||
} | ||
); | ||
|
||
t.test( | ||
"returns parsed value from AIKIDO_MAX_WS_MSG_SIZE_MB without suffix", | ||
async (t) => { | ||
process.env.AIKIDO_MAX_WS_MSG_SIZE_MB = "10"; | ||
t.equal(getMaxWsMsgSize(), 10 * 1024 * 1024, "should return 10 MB"); | ||
} | ||
); | ||
|
||
t.test( | ||
"returns default max body size for non-numeric AIKIDO_MAX_WS_MSG_SIZE_MB", | ||
async (t) => { | ||
process.env.AIKIDO_MAX_WS_MSG_SIZE_MB = "invalid"; | ||
t.equal( | ||
getMaxWsMsgSize(), | ||
20 * 1024 * 1024, | ||
"should return 20 MB as default" | ||
); | ||
} | ||
); | ||
|
||
t.test( | ||
"returns default max body size for negative AIKIDO_MAX_WS_MSG_SIZE_MB", | ||
async (t) => { | ||
process.env.AIKIDO_MAX_WS_MSG_SIZE_MB = "-5"; | ||
t.equal( | ||
getMaxWsMsgSize(), | ||
20 * 1024 * 1024, | ||
"should return 20 MB as default" | ||
); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
const MAX_WS_MSG_SIZE_MB = 20; | ||
|
||
export function getMaxWsMsgSize() { | ||
if (process.env.AIKIDO_MAX_WS_MSG_SIZE_MB) { | ||
const parsed = parseInt(process.env.AIKIDO_MAX_WS_MSG_SIZE_MB, 10); | ||
if (!isNaN(parsed) && parsed > 0) { | ||
return parsed * 1024 * 1024; | ||
} | ||
} | ||
|
||
return MAX_WS_MSG_SIZE_MB * 1024 * 1024; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
let's reuse
firewall-node/library/helpers/getMaxBodySize.ts
Line 3 in 7757f32