236 lines
8.1 KiB
JavaScript
236 lines
8.1 KiB
JavaScript
|
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}`
|
||
|
);
|
||
|
});
|
||
|
});
|