UNPKG

@timo972/ufw

Version:

ufw <-> nodejs app communication

333 lines (323 loc) 9.99 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var child_process = require('child_process'); var path = require('path'); const ipv4Regex = /^([0-9]{1,3}\.){3}[0-9]{1,3}(\/([0-9]|[1-2][0-9]|3[0-2]))?$/; const ipv6Regex = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/; function isIPv4Valid(ip) { return ipv4Regex.test(ip); } function isIPv6Valid(ip) { return ipv6Regex.test(ip); } function isProtocolValid(proto) { return proto === "any" || proto === "tcp" || proto === "udp"; } function isPortValid(port) { return port >= 0 && port < 65536; } exports.Protocol = void 0; (function (Protocol) { Protocol["TCP"] = "tcp"; Protocol["UDP"] = "udp"; Protocol["ANY"] = "any"; })(exports.Protocol || (exports.Protocol = {})); /** * @class Rule * @author @Timo972 */ class Rule { _id; _from; _to; _port; _proto; /** * @param {number} _id WARNING: Do not pass something here!!! Only used internally. */ constructor(_id) { this._id = _id; } /** * * @param {string} ip ipv4 or ipv6 address * @returns {Rule} */ to(ip) { if (!isIPv6Valid(ip) && !isIPv4Valid(ip)) throw new Error("invalid ip address"); this._to = ip; return this; } /** * * @param {string} ip ipv4 or ipv6 address * @returns {Rule} */ from(ip) { if (!isIPv6Valid(ip) && !isIPv4Valid(ip)) throw new Error("invalid ip address"); this._from = ip; return this; } /** * * @param {Protocol} protocol can be any, tcp, udp * @returns {Rule} */ proto(proto) { if (!isProtocolValid(proto)) throw new Error("invalid protocol"); this._proto = proto; return this; } /** * * @param {number} port from 0-65535 * @returns {Rule} */ port(port) { if (!isPortValid(port)) throw new Error("invalid port"); this._port = port; return this; } /** * * @returns {number} */ getPort() { return this._port; } /** * * @returns {Protocol} */ getProtocol() { return this._proto ?? exports.Protocol.ANY; } /** * * @returns {string} */ getFrom() { return this._from; } /** * * @returns {string} */ getTo() { return this._to; } /** * function returns undefined if you have created this rule * @returns {number} rule id */ getId() { return this._id; } toJSON() { const { from, getFrom, getId, getPort, getProtocol, getTo, port, proto, to, toJSON, ...data } = this; let formatted = {}; for (const key in data) { const value = data[key]; if (!value) continue; const escapedKey = key.substring(1, key.length); formatted[escapedKey] = value; } return JSON.stringify(formatted); } buildUfwCommand() { let command = []; if (this._proto === exports.Protocol.ANY) { command.push("proto any"); } if (this._proto === exports.Protocol.TCP) { command.push("proto tcp"); } if (this._proto === exports.Protocol.UDP) { command.push("proto udp"); } if (this._from && (isIPv4Valid(this._from) || isIPv6Valid(this._from) || this._from === "any")) { command.push(`from ${this._from}`); } if (this._to && (isIPv4Valid(this._to) || isIPv6Valid(this._to) || this._to === "any")) { command.push(`to ${this._to}`); } if (this._port && isPortValid(this._port)) command.push(`port ${this._port}`); return command.join(" "); } } var UFWOperation; (function (UFWOperation) { UFWOperation[UFWOperation["AddRule"] = 0] = "AddRule"; UFWOperation[UFWOperation["DeleteRule"] = 1] = "DeleteRule"; UFWOperation[UFWOperation["SetDefault"] = 2] = "SetDefault"; UFWOperation[UFWOperation["GetStatus"] = 3] = "GetStatus"; UFWOperation[UFWOperation["GetRules"] = 4] = "GetRules"; UFWOperation[UFWOperation["GetListening"] = 5] = "GetListening"; UFWOperation[UFWOperation["Enable"] = 6] = "Enable"; UFWOperation[UFWOperation["Disable"] = 7] = "Disable"; UFWOperation[UFWOperation["Reset"] = 8] = "Reset"; UFWOperation[UFWOperation["Reload"] = 9] = "Reload"; })(UFWOperation || (UFWOperation = {})); class UFWRequest { _operation; data; returnJson = false; get operation() { return this._operation; } set operation(op) { this._operation = op; if (this.operation == UFWOperation.GetListening || this.operation == UFWOperation.GetRules || this.operation == UFWOperation.GetStatus) this.returnJson = true; else this.returnJson = false; } pass(data) { this.data = JSON.stringify(data); } } function execute(req) { return new Promise((resolve, reject) => { const data = `{"operation":${req.operation}, "data": ${req.data ?? '{}'}}`.replace(/"/g, '\\"'); console.log(data); child_process.exec(`python3 ${path.join(__dirname, "..", "python", "ufw-bridge.py")} ${data}`, (error, stdout) => { if (error) { reject(error); return; } console.log(stdout); try { if (req.returnJson) resolve(JSON.parse(stdout)); else resolve(stdout); } catch (e) { reject(e); } }); }); } async function addRule(command) { const req = new UFWRequest(); req.pass({ command }); req.operation = UFWOperation.AddRule; await execute(req); return; } /** * * @param {Rule} rule */ async function allow(rule) { if (!(rule instanceof Rule)) throw new Error("invalid rule"); return addRule(`allow ` + rule.buildUfwCommand()); } /** * * @param {Rule} rule */ async function deny(rule) { if (!(rule instanceof Rule)) throw new Error("invalid rule"); return addRule(`deny ` + rule.buildUfwCommand()); } /** * * @param {Rule} rule */ async function limit(rule) { if (!(rule instanceof Rule)) throw new Error("invalid rule"); return addRule(`limit ` + rule.buildUfwCommand()); } /** * * @param {Rule} rule */ async function reject(rule) { if (!(rule instanceof Rule)) throw new Error("invalid rule"); return addRule(`reject ` + rule.buildUfwCommand()); } async function deleteRule(rule) { if (!(rule instanceof Rule) && !(typeof rule === "number")) throw new Error("invalid rule / id"); const id = rule instanceof Rule ? rule.getId() : rule; if (!id) throw new Error("invalid id"); const req = new UFWRequest(); req.pass({ id }); req.operation = UFWOperation.DeleteRule; return execute(req); } async function getRules() { const req = new UFWRequest(); req.operation = UFWOperation.GetRules; return execute(req); } async function setDefaults(incoming, outgoing, routed) { const req = new UFWRequest(); req.operation = UFWOperation.SetDefault; req.pass({ incoming, outgoing, routed, }); return execute(req); } async function enable() { const req = new UFWRequest(); req.operation = UFWOperation.Enable; return execute(req); } async function disable() { const req = new UFWRequest(); req.operation = UFWOperation.Disable; return execute(req); } async function getListening() { const req = new UFWRequest(); req.operation = UFWOperation.GetListening; return; } async function getStatus() { const req = new UFWRequest(); req.operation = UFWOperation.GetStatus; return execute(req); } async function reset() { const req = new UFWRequest(); req.operation = UFWOperation.Reset; return execute(req); } async function reload() { const req = new UFWRequest(); req.operation = UFWOperation.Reload; return execute(req); } exports.Rule = Rule; exports.allow = allow; exports.deleteRule = deleteRule; exports.deny = deny; exports.disable = disable; exports.enable = enable; exports.getListening = getListening; exports.getRules = getRules; exports.getStatus = getStatus; exports.limit = limit; exports.reject = reject; exports.reload = reload; exports.reset = reset; exports.setDefaults = setDefaults;