Skip to content

Commit

Permalink
Add clickhouse e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
timokoessler committed Nov 4, 2024
1 parent 5325774 commit 3b2543e
Show file tree
Hide file tree
Showing 7 changed files with 791 additions and 1 deletion.
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ nestjs-sentry:
nestjs-fastify:
cd sample-apps/nestjs-fastify && AIKIDO_DEBUG=true AIKIDO_BLOCKING=true NODE_OPTIONS=--preserve-symlinks npm run start


.PHONY: fastify-mysql2
fastify-mysql2:
cd sample-apps/fastify-mysql2 && AIKIDO_DEBUG=true AIKIDO_BLOCKING=true node --preserve-symlinks app.js
Expand All @@ -81,6 +80,10 @@ fastify-mysql2:
koa-sqlite3:
cd sample-apps/koa-sqlite3 && AIKIDO_DEBUG=true AIKIDO_BLOCKING=true node --preserve-symlinks app.js

.PHONY: fastify-clickhouse
fastify-clickhouse:
cd sample-apps/fastify-clickhouse && AIKIDO_DEBUG=true AIKIDO_BLOCKING=true node app.js

.PHONY: install
install:
mkdir -p build
Expand Down
113 changes: 113 additions & 0 deletions end2end/tests/fastify-clickhouse.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
const t = require("tap");
const { spawn } = require("child_process");
const { resolve } = require("path");
const timeout = require("../timeout");

const pathToApp = resolve(
__dirname,
"../../sample-apps/fastify-clickhouse",
"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(4000)
.then(() => {
return Promise.all([
fetch(
`http://127.0.0.1:4000/?petname=${encodeURIComponent("Njuska'), (null, 'Kitty")}`,
{
signal: AbortSignal.timeout(5000),
}
),
fetch("http://127.0.0.1:4000/?petname=Njuska", {
signal: AbortSignal.timeout(5000),
}),
fetch("http://127.0.0.1:4000/context", {
signal: AbortSignal.timeout(5000),
}),
]);
})
.then(([sqlInjection, normalSearch]) => {
t.equal(sqlInjection.status, 500);
t.equal(normalSearch.status, 200);
t.match(stdout, /Starting agent/);
t.match(stdout, /Zen has blocked an SQL injection/);
})
.catch((error) => {
t.fail(error.message);
})
.finally(() => {
server.kill();
});
});

t.test("it does not block in dry mode", (t) => {
const server = spawn(`node`, [pathToApp, "4001"], {
env: { ...process.env, AIKIDO_DEBUG: "true" },
});

server.on("close", () => {
t.end();
});

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(4000)
.then(() =>
Promise.all([
fetch(
`http://127.0.0.1:4001/?petname=${encodeURIComponent("Njuska'), (null, 'Kitty")}`,
{
signal: AbortSignal.timeout(5000),
}
),
fetch("http://127.0.0.1:4001/?petname=Njuska", {
signal: AbortSignal.timeout(5000),
}),
])
)
.then(([sqlInjection, normalSearch]) => {
t.equal(sqlInjection.status, 200);
t.equal(normalSearch.status, 200);
t.match(stdout, /Starting agent/);
t.notMatch(stdout, /Zen has blocked an SQL injection/);
})
.catch((error) => {
t.fail(error.message);
})
.finally(() => {
server.kill();
});
});
5 changes: 5 additions & 0 deletions sample-apps/fastify-clickhouse/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# fastify-clickhouse

WARNING: This application contains security issues and should not be used in production (or taken as an example of how to write secure code).

In the root directory run `make fastify-clickhouse` to start the server.
79 changes: 79 additions & 0 deletions sample-apps/fastify-clickhouse/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Import Aikido Firewall
const Zen = require("@aikidosec/firewall");
const db = require("./db");

const fastify = require("fastify");

// Prevent Prototype Pollution
require("@aikidosec/firewall/nopp");

/**
* Get the HTML body of the root page
*/
function getHTMLBody(cats) {
return `
<html lang="en">
<bod>
<p>All cats : ${cats.map((cat) => cat.petname).join(", ")}</p>
<form action="/" method="GET">
<label for="search">Add a new cat</label>
<input type="text" name="petname">
<input type="submit" value="Add" />
</form>
<a href="http://localhost:4000/?petname=Kitty'); DELETE FROM cats;-- H">Test injection</a> / <a href="http://localhost:4000/clear">Clear table</a>
</body>
</html>`;
}

// Async main function that starts the Fastify server
(async () => {
await db.createConnection();

const app = fastify({
logger: true,
});

Zen.addFastifyHook(app);

// Handle GET requests to the root URL
app.get("/", async (request, reply) => {
if (request.query["petname"]) {
await db.addCat(request.query["petname"]);
}
const html = getHTMLBody(await db.getAllCats());
reply.header("Content-Type", "text/html").send(html);
});

app.route({
method: "GET",
url: "/cats",
handler: async (request, reply) => {
reply.send(await db.getAllCats());
},
});

// Handle GET requests to /clear
app.get("/clear", async (request, reply) => {
await db.clearCats();
reply.redirect("/");
});

// Start the server
try {
await app.listen({ port: getPort() });
} catch (err) {
app.log.error(err);
process.exit(1);
}
})();

function getPort() {
const port = parseInt(process.argv[2], 10) || 4000;

if (isNaN(port)) {
console.error("Invalid port");
process.exit(1);
}

return port;
}
50 changes: 50 additions & 0 deletions sample-apps/fastify-clickhouse/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const { createClient } = require("@clickhouse/client");

let client;

async function createConnection() {
client = createClient({
url: "http://localhost:27019",
username: "clickhouse",
password: "clickhouse",
database: "main_db",
});

await client.exec({
query: `CREATE TABLE IF NOT EXISTS cats_table (
id UUID DEFAULT generateUUIDv4() PRIMARY KEY,
petname String
);
`,
});

return client;
}

async function getAllCats() {
const resultSet = await client.query({
query: `SELECT * FROM cats_table;`,
format: "JSONEachRow",
});
return resultSet.json();
}

function addCat(name) {
console.log(`INSERT INTO cats_table (id, petname) VALUES (null, '${name}');`);
return client.exec({
query: `INSERT INTO cats_table (id, petname) VALUES (null, '${name}');`,
});
}

function clearCats() {
return client.exec({
query: `DELETE FROM cats_table WHERE 1;`,
});
}

module.exports = {
createConnection,
getAllCats,
addCat,
clearCats,
};
Loading

0 comments on commit 3b2543e

Please sign in to comment.