From 6444da571cab2f2e90aba7fac296c6d771b36f98 Mon Sep 17 00:00:00 2001 From: Dustyn Blackmore Date: Sat, 21 Apr 2018 10:36:19 +1000 Subject: [PATCH] Messy Refactor Updated index.js Now supports hotloading of multiple configuration files. Now evals callbacks on port triggers (from rules.json) This feature will soon be removed; risky. Now setTimeout to 2 seconds to load final counters into rules. Minor changes to layout of rules.json. --- src/config/rules.json | 151 +++++++++++++++++++++++++++------------- src/index.js | 88 ++++++++++++++++------- src/nfpacket/actions.js | 26 +++++++ src/nfpacket/enums.js | 15 ++++ src/nfpacket/index.js | 17 +++++ 5 files changed, 224 insertions(+), 73 deletions(-) create mode 100644 src/nfpacket/actions.js create mode 100644 src/nfpacket/enums.js create mode 100644 src/nfpacket/index.js diff --git a/src/config/rules.json b/src/config/rules.json index 470fa59..1adc762 100644 --- a/src/config/rules.json +++ b/src/config/rules.json @@ -4,49 +4,73 @@ "_1": "ICMP", "1": { "global": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "trusted": { - "allowed": true + "allowed": true, + "acceptAction": null, + "rejectAction": null }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } }, "_2": "IGMP", "2": { "global": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "trusted": { - "allowed": true + "allowed": true, + "acceptAction": null, + "rejectAction": null }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } }, "_6": "TCP", "6": { "global": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "trusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } }, "_17": "UDP", "17": { "global": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "trusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } } }, @@ -54,120 +78,153 @@ "_1": "ICMP", "1": { "global": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "trusted": { - "allowed": true + "allowed": true, + "acceptAction": null, + "rejectAction": null }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } }, "_2": "IGMP", "2": { "global": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null }, "trusted": { - "allowed": true + "allowed": true, + "acceptAction": null, + "rejectAction": null }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } }, "_6": "TCP", "6": { "global": { "allowed": true, + "acceptAction": null, + "rejectAction": null, "ports": { - "22": { - "callback": null - }, - "80": { - "callback": null - }, "443": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "993": { - "callback": null - }, - "9092": { - "callback": null + "acceptAction": null, + "rejectAction": null } } }, "trusted": { "allowed": true, + "acceptAction": null, + "rejectAction": null, "ports": { "22": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "80": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "139": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "445": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "465": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "1900": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "9092": { - "callback": null + "acceptAction": null, + "rejectAction": null } } }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } }, "_17": "UDP", "17": { "global": { "allowed": true, + "acceptAction": null, + "rejectAction": null, "ports": { "53": { - "callback": null + "acceptAction": "((packet) => { console.log(packet) })", + "rejectAction": null }, "500": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "1701": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "4500": { - "callback": null + "acceptAction": null, + "rejectAction": null } } }, "trusted": { "allowed": true, + "acceptAction": null, + "rejectAction": null, "ports": { "123": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "137": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "138": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "1900": { - "callback": null + "acceptAction": null, + "rejectAction": null }, "5353": { - "callback": null + "acceptAction": null, + "rejectAction": null } } }, "untrusted": { - "allowed": false + "allowed": false, + "acceptAction": null, + "rejectAction": null } } } diff --git a/src/index.js b/src/index.js index b2cff90..9380c10 100644 --- a/src/index.js +++ b/src/index.js @@ -2,25 +2,12 @@ const sysClassNetInterfaces = '/sys/class/net/'; const fs = require('fs'); const nfq = require('nfqueue'); const IPv4 = require('pcap/decode/ipv4'); -let rules = require('./config/rules.json').rules; -const systemInterfaces = require('./config/interfaces.json').interfaces; +const pcap = require('pcap'); const { exec } = require('child_process'); +const nfpacket = require('./nfpacket')({ nfq: nfq, pcap: pcap }) const nft = require('./nftables')({ exec: exec }); -process.stdout.write('\x1Bc'); - -let ruleWatch = fs.watch('./src/config', () => { setTimeout(loadRules, 500) }); - -function loadRules (err, filename) { - console.log('Detected ' + filename + ' change. Ingesting.'); - fs.readFile('./src/config/rules.json', 'utf8', (err, data) => { - if (err) throw err; - let newRules = JSON.parse(data); - rules = newRules.rules; - }); -} - // These are the NFQUEUE result handler options. const NF_REJECT = 0; const NF_ACCEPT = 1; // Accept packet (but no longer seen / disowned by conntrack) @@ -36,6 +23,38 @@ const PC_UDP = 17; // The buffer size we will use binding to nfqueues. const buffer = 131070; +process.stdout.write('\x1Bc'); + +let rules = require('./config/rules.json').rules; +let systemInterfaces = require('./config/interfaces.json').interfaces; + +let configWatch = fs.watch('./src/config', checkConfig); + +function checkConfig (err, filename) { + setTimeout(() => { + console.log(filename); + console.log(err) + switch (filename) { + case 'rules.json': + console.log('Rules Configuration Changed - Reloding..') + fs.readFile('./src/config/rules.json', 'utf8', (err, data) => { + if (err) throw err; + let newRules = JSON.parse(data); + rules = newRules.rules; + }); + break; + case 'interfaces.json': + console.log('Interfaces Configuration Changed - Reloding..') + fs.readFile('./src/config/interfaces.json', 'utf8', (err, data) => { + if (err) throw err; + let newInterfaces = JSON.parse(data); + systemInterfaces = newInterfaces.interfaces; + }); + break; + } + }, 500) +} + // Some counters for connection analysis (Used for stdio) let packetsAccepted = 0; let packetsAcceptedIn = 0; @@ -70,11 +89,20 @@ function getInterfaces (path) { : []; } -const promiseSerial = funcs => - funcs.reduce((promise, func) => - promise.then(result => - func().then(Array.prototype.concat.bind(result))), - Promise.resolve([])) +/** + * Runs promises from promise array in chained manner + * + * @param {array} arr - promise arr + * @return {Object} promise object + */ +function runPromiseInSequense(arr) { + return arr.reduce((promiseChain, currentPromise) => { + return promiseChain.then((chainedResult) => { + return currentPromise(chainedResult) + .then((res) => res) + }) + }, Promise.resolve()); +} function setupInterfaces () { let interfacePromises = []; @@ -89,7 +117,7 @@ function setupInterfaces () { interfaces.push(newInterface); }); - return promiseSerial(interfacePromises) + return runPromiseInSequense(interfacePromises) }; function determineVerdict (interface, packet, direction) { @@ -99,14 +127,18 @@ function determineVerdict (interface, packet, direction) { if (rules[direction][packet.protocol.toString()]) { // Check if the global (blanket) rule applies if (rules[direction][packet.protocol.toString()].global.allowed) { + // Trigger the callback, if it exists.. + if (rules[direction][packet.protocol.toString()].global.acceptCallback) { + eval(rules[direction][packet.protocol.toString()].global.acceptCallback)(packet); + } // Check if the global setting has any specific ports if (rules[direction][packet.protocol.toString()].global.ports) { // Check, if there are ports, if the port is allowed. if (rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport]) { thisVerdict = NF_ACCEPT; // Finally - if the port is allowed, check if there's a callback to trigger. - if (rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport].callback) { - rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport].callback(); + if (rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport].acceptCallback) { + eval(rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport].acceptCallback)(packet); } return thisVerdict; } @@ -120,14 +152,18 @@ function determineVerdict (interface, packet, direction) { } // Check if the protocol is zone allowed. if (rules[direction][packet.protocol.toString()][interface.zone].allowed) { + // Trigger the protocol zone callback, if it exists. + if (rules[direction][packet.protocol.toString()][interface.zone].acceptCallback) { + eval(rules[direction][packet.protocol.toString()][interface.zone].acceptCallback)(packet); + } // Check if the protocol's zone setting has any specific ports if (rules[direction][packet.protocol.toString()][interface.zone].ports) { // Check, if there are ports, if the port is allowed. if (rules[direction][packet.protocol.toString()][interface.zone].ports[packet.payload.dport]) { thisVerdict = NF_ACCEPT; // Finally - if the port is allowed, check if there's a callback to trigger. - if (rules[direction][packet.protocol.toString()][interface.zone].ports[packet.payload.dport].callback) { - rules[direction][packet.protocol.toString()][interface.zone].ports[packet.payload.dport].callback(); + if (rules[direction][packet.protocol.toString()][interface.zone].ports[packet.payload.dport].acceptCallback) { + eval(rules[direction][packet.protocol.toString()][interface.zone].ports[packet.payload.dport].acceptCallback)(packet); } } // The global default is enabled, yet there are no ports.. which likely @@ -206,7 +242,7 @@ nft.flush().then( ).then( (resolved) => { console.log('Inserting final (counter) rules...'); - insertFinalCounters(); + setTimeout(insertFinalCounters, 2000); }, (reject) => console.log('Failed to bind queue handlers: ' + reject) ).catch( diff --git a/src/nfpacket/actions.js b/src/nfpacket/actions.js new file mode 100644 index 0000000..8ae53bc --- /dev/null +++ b/src/nfpacket/actions.js @@ -0,0 +1,26 @@ +const actions = (depedencies) => ({ + accept: (nfpacket) => { + return nfpacket.setVedrict(0, 'add ' + rule); + }, + decode: (nfpacket) => { + let IPv4 = dependencies + ? dependencies.pcap + ? dependencies.pcap.decode + ? depdencencies.pcap.decode.ipv4 || null + : null + : null + : null; + + return IPv4 + ? new IPv4().decode(nfpacket.payload, 0) + : nfpacket; + }, + reject: (nfpacket) => { + return execute(exec, 'flush ruleset'); + }, + requeue: (filename) => { + return execute(exec, '-f ' + filename); + } +}) + +module.exports = actions; diff --git a/src/nfpacket/enums.js b/src/nfpacket/enums.js new file mode 100644 index 0000000..caa88f5 --- /dev/null +++ b/src/nfpacket/enums.js @@ -0,0 +1,15 @@ +const netfilterVerdict = { + // These are the NFQUEUE result handler options. + NF_REJECT: 0, + NF_ACCEPT: 1, // Accept packet (but no longer seen / disowned by conntrack, + NF_REQUEUE: 4, // Requeue packet (Which we then use a mark to determine the action, +} + +const protocols = { + // Protocol Numbers can be found here, however; libpcap has limited support.. + // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml + PC_ICMP: 1, + PC_IGMP: 2, + PC_TCP: 6, + PC_UDP: 17, +} \ No newline at end of file diff --git a/src/nfpacket/index.js b/src/nfpacket/index.js new file mode 100644 index 0000000..6959d6a --- /dev/null +++ b/src/nfpacket/index.js @@ -0,0 +1,17 @@ +const actions = require('./actions'); +const enums = require('./enums.js'); + +const nfpacket = (dependencies) => { + if (Object.keys(dependencies).includes(['pcap', 'nfq'])) { + return Object.assign( + {}, + nfpacket, + enums, + actions(dependencies) + ) + } + + return false; +} + +module.exports = nfpacket;