modsecurity-integration/tests/index.test.js

236 lines
8.1 KiB
JavaScript
Raw Normal View History

2025-01-12 18:33:26 +01:00
const { EventEmitter } = require("events");
const { Transaction } = require("modsecurity");
jest.mock("modsecurity");
process.serverConfig = {
modSecurityRulesPath: "/path/to/rules"
};
const modSecurityIntegration = require("../src/index.js");
const modInfo = require("../modInfo.json");
describe("ModSecurity Integration", () => {
let req, res, logFacilities, config, next;
beforeEach(() => {
req = new EventEmitter();
req.read = jest.fn();
req.unshift = jest.fn();
req.method = "GET";
req.url = "/test";
req.httpVersion = "1.1";
req.rawHeaders = ["Host", "example.com"];
req.socket = {
realRemoteAddress: "127.0.0.1",
realRemotePort: 12345,
localAddress: "127.0.0.1",
localPort: 8080
};
req.headers = {};
req._readableState = {
length: 0,
ended: true
};
res = new EventEmitter();
res.setHeader = jest.fn();
res.removeHeader = jest.fn();
res.writeHead = jest.fn();
res.write = jest.fn();
res.end = jest.fn();
res.error = jest.fn();
res.getHeaders = jest.fn().mockReturnValue({});
logFacilities = {
errmessage: jest.fn()
};
config = {};
next = jest.fn();
jest.clearAllMocks();
Transaction.mockReset();
});
test("should call next if no rules error and no security response", () => {
modSecurityIntegration(req, res, logFacilities, config, next);
expect(next).toHaveBeenCalled();
});
test("should process connection and call next if no security response", () => {
Transaction.prototype.processConnection.mockReturnValue(true);
modSecurityIntegration(req, res, logFacilities, config, next);
expect(Transaction.prototype.processConnection).toHaveBeenCalledWith(
"127.0.0.1",
12345,
"127.0.0.1",
8080
);
expect(next).toHaveBeenCalled();
});
test("should process URI and call next if no security response", () => {
Transaction.prototype.processConnection.mockReturnValue(true);
Transaction.prototype.processURI.mockReturnValue(true);
modSecurityIntegration(req, res, logFacilities, config, next);
expect(Transaction.prototype.processURI).toHaveBeenCalledWith(
"/test",
"GET",
"1.1"
);
expect(next).toHaveBeenCalled();
});
test("should process request headers and call next if no security response", () => {
Transaction.prototype.processConnection.mockReturnValue(true);
Transaction.prototype.processURI.mockReturnValue(true);
Transaction.prototype.processRequestHeaders.mockReturnValue(true);
modSecurityIntegration(req, res, logFacilities, config, next);
expect(Transaction.prototype.processRequestHeaders).toHaveBeenCalled();
expect(next).toHaveBeenCalled();
});
test("should handle security response and call processIntervention", () => {
const securityResponse = { status: 403, log: "Blocked by ModSecurity" };
Transaction.prototype.processConnection.mockReturnValue(securityResponse);
modSecurityIntegration(req, res, logFacilities, config, next);
expect(res.error).toHaveBeenCalledWith(
403,
`modsecurity-integration/${modInfo.version}`,
new Error("Blocked by ModSecurity")
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Request blocked by ModSecurity."
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Blocked by ModSecurity"
);
});
test("should handle unknown ModSecurity error", () => {
Transaction.prototype.processConnection.mockReturnValue(false);
modSecurityIntegration(req, res, logFacilities, config, next);
expect(res.error).toHaveBeenCalledWith(
500,
`modsecurity-integration/${modInfo.version}`,
new Error("Unknown ModSecurity error.")
);
});
test("should handle request body and call next if no security response", () => {
Transaction.prototype.processConnection.mockReturnValue(true);
Transaction.prototype.processURI.mockReturnValue(true);
Transaction.prototype.processRequestHeaders.mockReturnValue(true);
Transaction.prototype.appendRequestBody.mockReturnValue(true);
Transaction.prototype.processRequestBody.mockReturnValue(true);
modSecurityIntegration(req, res, logFacilities, config, next);
expect(Transaction.prototype.processRequestBody).toHaveBeenCalled();
expect(next).toHaveBeenCalled();
});
test("should block request if security response is detected during processResponseHeaders", () => {
const securityResponse = { status: 403, log: "Blocked by ModSecurity" };
Transaction.prototype.processConnection.mockReturnValue(true);
Transaction.prototype.processURI.mockReturnValue(true);
Transaction.prototype.processRequestHeaders.mockReturnValue(true);
Transaction.prototype.processResponseHeaders.mockReturnValue(
securityResponse
);
modSecurityIntegration(req, res, logFacilities, config, next);
res.writeHead(200, "OK");
res.write("Something that will trigger security response.");
res.end();
expect(res.error).toHaveBeenCalledWith(
403,
`modsecurity-integration/${modInfo.version}`,
new Error("Blocked by ModSecurity")
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Request blocked by ModSecurity."
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Blocked by ModSecurity"
);
});
test("should block request if security response is detected during appendResponseBody", () => {
const securityResponse = { status: 403, log: "Blocked by ModSecurity" };
Transaction.prototype.processConnection.mockReturnValue(true);
Transaction.prototype.processURI.mockReturnValue(true);
Transaction.prototype.processRequestHeaders.mockReturnValue(true);
Transaction.prototype.appendResponseBody.mockReturnValue(securityResponse);
modSecurityIntegration(req, res, logFacilities, config, next);
res.writeHead(200, "OK");
res.write("Something that will trigger security response.");
res.end();
expect(res.error).toHaveBeenCalledWith(
403,
`modsecurity-integration/${modInfo.version}`,
new Error("Blocked by ModSecurity")
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Request blocked by ModSecurity."
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Blocked by ModSecurity"
);
});
test("should block request if security response is detected during processResponseBody", () => {
const securityResponse = { status: 403, log: "Blocked by ModSecurity" };
Transaction.prototype.processConnection.mockReturnValue(true);
Transaction.prototype.processURI.mockReturnValue(true);
Transaction.prototype.processRequestHeaders.mockReturnValue(true);
Transaction.prototype.processResponseBody.mockReturnValue(securityResponse);
modSecurityIntegration(req, res, logFacilities, config, next);
res.writeHead(200, "OK");
res.write("Something that will trigger security response.");
res.end();
expect(res.error).toHaveBeenCalledWith(
403,
`modsecurity-integration/${modInfo.version}`,
new Error("Blocked by ModSecurity")
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Request blocked by ModSecurity."
);
expect(logFacilities.errmessage).toHaveBeenCalledWith(
"Blocked by ModSecurity"
);
});
test("should handle maxRequestCheckedSize exceeded and call next", () => {
config.maxRequestCheckedSize = 10;
req.headers["content-length"] = 20;
req._readableState.ended = false;
req._readableState.length = 0;
req.read.mockImplementation((size) => Buffer.alloc(Math.min(20, size)));
modSecurityIntegration(req, res, logFacilities, config, next);
req.emit("readable");
expect(next).toHaveBeenCalled();
});
test("should handle maxRequestCheckedSizeStrict", () => {
config.maxRequestCheckedSizeStrict = true;
config.maxRequestCheckedSize = 10;
req.headers["content-length"] = 20;
req._readableState.ended = false;
req._readableState.length = 0;
modSecurityIntegration(req, res, logFacilities, config, next);
expect(res.error).toHaveBeenCalledWith(
413,
`modsecurity-integration/${modInfo.version}`
);
});
});