62 lines
2.8 KiB
JavaScript
62 lines
2.8 KiB
JavaScript
// Single-require chokepoint + hardening WRAPPER for the PINNED ldapjs dependency
|
|
// (an npm package, NOT a vendored source copy/fork -- see VENDORING.md). This is
|
|
// the ONLY file that require()s ldapjs, so it is the one upgrade/audit point.
|
|
// Our hardening lives in OUR code at a layer we control: a
|
|
// per-connection inbound byte cap here (the BER parser has no size limit, so a
|
|
// declared multi-GB message would buffer unbounded), the per-IP bind lockout in
|
|
// harden.js, and the filter-nesting-depth guard in search.js. Re-exporting the
|
|
// error classes keeps the handlers off a direct ldapjs import.
|
|
|
|
const ldap = require("ldapjs");
|
|
|
|
const constants = require("../constants");
|
|
|
|
|
|
// createServer with a connectionRouter that caps inbound bytes PER LDAP MESSAGE
|
|
// and then hands off to ldapjs's own connection setup. ldapjs uses
|
|
// options.connectionRouter in place of its internal newConnection, so the router
|
|
// MUST call server.newConnection(conn) or no parser is wired up. The cap is reset
|
|
// on each parsed-message boundary (conn.parser emits 'message'): the goal is to
|
|
// bound a single unbounded BER message (the parser has no size limit), NOT to
|
|
// impose a connection-lifetime quota -- a per-connection counter would let an
|
|
// attacker kill a legitimate long-lived connection after a few normal operations
|
|
// (sequential binds/searches that each fit the cap but cumulatively exceed it).
|
|
const createHardenedServer = (opts) => {
|
|
let server;
|
|
const options = Object.assign({}, opts, {
|
|
connectionRouter: (conn) => {
|
|
server.newConnection(conn);
|
|
// Idle timeout: drop a connection that goes silent so slow-loris /
|
|
// idle sockets cannot hoard the (capped) connection pool. ldapjs never
|
|
// calls setTimeout itself, so without this connections live forever.
|
|
if (typeof conn.setTimeout === "function") {
|
|
conn.setTimeout(constants.LDAP_IDLE_TIMEOUT_MS);
|
|
conn.on("timeout", () => conn.destroy());
|
|
}
|
|
let total = 0;
|
|
if (conn.parser && typeof conn.parser.on === "function") {
|
|
conn.parser.on("message", () => {
|
|
total = 0;
|
|
});
|
|
}
|
|
conn.on("data", (chunk) => {
|
|
total += chunk.length;
|
|
if (total > constants.LDAP_MAX_MSG_BYTES) {
|
|
conn.destroy();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
server = ldap.createServer(options);
|
|
return server;
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
createHardenedServer,
|
|
InvalidCredentialsError: ldap.InvalidCredentialsError,
|
|
InsufficientAccessRightsError: ldap.InsufficientAccessRightsError,
|
|
OperationsError: ldap.OperationsError,
|
|
ProtocolError: ldap.ProtocolError,
|
|
SizeLimitExceededError: ldap.SizeLimitExceededError
|
|
};
|