Skip to content

Commit

Permalink
Add end2end test for geo restrictions
Browse files Browse the repository at this point in the history
  • Loading branch information
hansott committed Nov 27, 2024
1 parent 858d130 commit a80f62f
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 2 deletions.
7 changes: 6 additions & 1 deletion end2end/server/app.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// This is a insecure mock server for testing purposes
// This is an insecure mock server for testing purposes
const express = require("express");
const config = require("./src/handlers/getConfig");
const captureEvent = require("./src/handlers/captureEvent");
const listEvents = require("./src/handlers/listEvents");
const createApp = require("./src/handlers/createApp");
const checkToken = require("./src/middleware/checkToken");
const updateConfig = require("./src/handlers/updateConfig");
const ipLists = require("./src/handlers/ipLists");
const updateIPLists = require("./src/handlers/updateIPLists");

const app = express();

Expand All @@ -19,6 +21,9 @@ app.post("/api/runtime/config", checkToken, updateConfig);
app.get("/api/runtime/events", checkToken, listEvents);
app.post("/api/runtime/events", checkToken, captureEvent);

app.get("/api/runtime/firewall/lists", checkToken, ipLists);
app.post("/api/runtime/firewall/lists", checkToken, updateIPLists);

app.post("/api/runtime/apps", createApp);

app.listen(port, () => {
Expand Down
24 changes: 24 additions & 0 deletions end2end/server/src/handlers/ipLists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { getBlockedIPAddresses } = require("../zen/config");

module.exports = function ipLists(req, res) {
if (!req.app) {
throw new Error("App is missing");
}

const blockedIps = getBlockedIPAddresses(req.app);

res.json({
success: true,
serviceId: req.app.id,
blockedIPAddresses:
blockedIps.length > 0
? [
{
source: "geoip",
description: "geo restrictions",
ips: blockedIps,
},
]
: [],
});
};
32 changes: 32 additions & 0 deletions end2end/server/src/handlers/updateIPLists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { updateBlockedIPAddresses } = require("../zen/config");

module.exports = function updateIPLists(req, res) {
if (!req.app) {
throw new Error("App is missing");
}

// Insecure input validation - but this is only a mock server
if (
!req.body ||
typeof req.body !== "object" ||
Array.isArray(req.body) ||
!Object.keys(req.body).length
) {
return res.status(400).json({
message: "Request body is missing or invalid",
});
}

if (
!req.body.blockedIPAddresses ||
!Array.isArray(req.body.blockedIPAddresses)
) {
return res.status(400).json({
message: "blockedIPAddresses is missing or invalid",
});
}

updateBlockedIPAddresses(req.app, req.body.blockedIPAddresses);

res.json({ success: true });
};
34 changes: 33 additions & 1 deletion end2end/server/src/zen/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,43 @@ function updateAppConfig(app, newConfig) {
getAppConfig(app);
index = configs.length - 1;
}
configs[index] = { ...configs[index], ...newConfig };
configs[index] = {
...configs[index],
...newConfig,
lastUpdatedAt: Date.now(),
};
return true;
}

const blockedIPAddresses = [];

function updateBlockedIPAddresses(app, ips) {
let entry = blockedIPAddresses.find((ip) => ip.serviceId === app.serviceId);

if (entry) {
entry.ipAddresses = ips;
} else {
entry = { serviceId: app.serviceId, ipAddresses: ips };
blockedIPAddresses.push(entry);
}

// Bump lastUpdatedAt
updateAppConfig(app, {});
}

function getBlockedIPAddresses(app) {
const entry = blockedIPAddresses.find((ip) => ip.serviceId === app.serviceId);

if (entry) {
return entry.ipAddresses;
}

return { serviceId: app.serviceId, ipAddresses: [] };
}

module.exports = {
getAppConfig,
updateAppConfig,
updateBlockedIPAddresses,
getBlockedIPAddresses,
};
114 changes: 114 additions & 0 deletions end2end/tests/hono-xml-geo-blocking.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const t = require("tap");
const { spawn } = require("child_process");
const { resolve } = require("path");
const timeout = require("../timeout");

const pathToApp = resolve(__dirname, "../../sample-apps/hono-xml", "app.js");
const testServerUrl = "http://localhost:5874";

let token;
t.beforeEach(async () => {
const response = await fetch(`${testServerUrl}/api/runtime/apps`, {
method: "POST",
});
const body = await response.json();
token = body.token;

// Apply rate limiting
const updateConfigResponse = await fetch(
`${testServerUrl}/api/runtime/firewall/lists`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token,
},
body: JSON.stringify({
blockedIPAddresses: ["1.3.2.0/24", "fe80::1234:5678:abcd:ef12/64"],
}),
}
);
t.same(updateConfigResponse.status, 200);
});

t.test("it blocks geo restricted IPs", (t) => {
const server = spawn(`node`, ["--preserve-symlinks", pathToApp, "4002"], {
env: {
...process.env,
AIKIDO_DEBUG: "true",
AIKIDO_BLOCKING: "true",
AIKIDO_TOKEN: token,
AIKIDO_URL: testServerUrl,
},
});

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 () => {
const resp1 = await fetch("http://127.0.0.1:4002/add", {
method: "POST",
body: "<cat><name>Njuska</name></cat>",
headers: {
"Content-Type": "application/xml",
"X-Forwarded-For": "1.3.2.4",
},
signal: AbortSignal.timeout(5000),
});
t.same(resp1.status, 403);
t.same(
await resp1.text(),
"Your IP address is blocked due to geo restrictions. (Your IP: 1.3.2.4)"
);

const resp2 = await fetch("http://127.0.0.1:4002/add", {
method: "POST",
body: "<cat><name>Harry</name></cat>",
headers: {
"Content-Type": "application/xml",
"X-Forwarded-For": "fe80::1234:5678:abcd:ef12",
},
signal: AbortSignal.timeout(5000),
});
t.same(resp2.status, 403);
t.same(
await resp2.text(),
"Your IP address is blocked due to geo restrictions. (Your IP: fe80::1234:5678:abcd:ef12)"
);

const resp3 = await fetch("http://127.0.0.1:4002/add", {
method: "POST",
body: "<cat><name>Harry</name></cat>",
headers: {
"Content-Type": "application/xml",
"X-Forwarded-For": "9.8.7.6",
},
signal: AbortSignal.timeout(5000),
});
t.same(resp3.status, 200);
t.same(await resp3.text(), JSON.stringify({ success: true }));
})
.catch((error) => {
t.fail(error.message);
})
.finally(() => {
server.kill();
});
});
5 changes: 5 additions & 0 deletions sample-apps/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ services:
- "27019:8123"
volumes:
- clickhouse:/var/lib/clickhouse
server:
build:
context: ../end2end/server
ports:
- "5874:3000"

volumes:
mongodb:
Expand Down

0 comments on commit a80f62f

Please sign in to comment.