From f6d3197ee0ae24ee33007a2c625f740333791df8 Mon Sep 17 00:00:00 2001 From: Dustyn Blackmore Date: Wed, 18 Apr 2018 10:29:11 +1000 Subject: [PATCH] Refactor and Restructure Restructured project for logical reasons. Updated README.md for new launching requirements. Also documented hot loading of rules.json. Tweaked index.js for new paths. Removed some redundant code. Added ability to call callback, if supplied, on rule triggers. Moved output updates to individual function interval of 2.5 seconds This should improve battery usage on laptops on high bandwidth interfacs Added catch for final counters insert failure. --- README.md | 4 +- package.json | 2 +- base.rules => src/config/base.rules | 0 interfaces.json => src/config/interfaces.json | 0 locked.rules => src/config/locked.rules | 0 rules.json => src/config/rules.json | 0 index.js => src/index.js | 43 +++++++++---------- 7 files changed, 25 insertions(+), 24 deletions(-) rename base.rules => src/config/base.rules (100%) rename interfaces.json => src/config/interfaces.json (100%) rename locked.rules => src/config/locked.rules (100%) rename rules.json => src/config/rules.json (100%) rename index.js => src/index.js (81%) diff --git a/README.md b/README.md index 7c6693d..9154d19 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Once running, you must initialize the app with sudo (Due to use of libpcap). I personally use; -```sudo `which node` index.js``` +```sudo `which node` src/index.js``` # Usage You can customize your rules within the *.json configuration files. @@ -52,8 +52,10 @@ is actually functioning. The overall flow is; filters) # Customisation +Configuration files may be found in src/config. * interfaces.json - specify your trusted, and untrusted, interfaces. * rules.json - Specify what ports, in which 'trust' zones you want to allow +* * Note: Changes to this file are 'hot loaded'. Care should be taken. * base.rules - Is the 'initial' template of rules deployed. (Creates the appropriate table, chains) * locked.rules - Is basically what the script 'should' fall back to if there diff --git a/package.json b/package.json index b3cd965..514659a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nftables_firewall.js", "version": "0.0.1", "description": "Is a test / proof of concept firewall using NodeJS and nftables/nfqueue.", - "main": "index.js", + "main": "src/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/base.rules b/src/config/base.rules similarity index 100% rename from base.rules rename to src/config/base.rules diff --git a/interfaces.json b/src/config/interfaces.json similarity index 100% rename from interfaces.json rename to src/config/interfaces.json diff --git a/locked.rules b/src/config/locked.rules similarity index 100% rename from locked.rules rename to src/config/locked.rules diff --git a/rules.json b/src/config/rules.json similarity index 100% rename from rules.json rename to src/config/rules.json diff --git a/index.js b/src/index.js similarity index 81% rename from index.js rename to src/index.js index ca83d15..1efbe49 100644 --- a/index.js +++ b/src/index.js @@ -2,18 +2,18 @@ const sysClassNetInterfaces = '/sys/class/net/'; const fs = require('fs'); const nfq = require('nfqueue'); const IPv4 = require('pcap/decode/ipv4'); -let rules = require('./rules.json').rules; +let rules = require('./config/rules.json').rules; // const rules = require('./rules.json').rules; -const systemInterfaces = require('./interfaces.json').interfaces; +const systemInterfaces = require('./config/interfaces.json').interfaces; const { exec } = require('child_process'); -const nft = require('./src/nftables')({ exec: exec }); +const nft = require('./nftables')({ exec: exec }); -let ruleWatch = fs.watch('./rules.json', 'utf8', () => { setTimeout(loadRules, 500) }); +let ruleWatch = fs.watch('./src/config', () => { setTimeout(loadRules, 500) }); function loadRules (err, filename) { console.log('Detected ' + filename + ' change. Ingesting.'); - fs.readFile('./rules.json', 'utf8', (err, data) => { + fs.readFile('./config/rules.json', 'utf8', (err, data) => { if (err) throw err; let newRules = JSON.parse(data); rules = newRules.rules; @@ -46,18 +46,6 @@ let packetsRejectedOut = 0; // An array to store our interfaces. let interfaces = [] -function execute (command) { - return new Promise(function (resolve, reject) { - exec(command, (err, stdout, stderr) => { - if (err) { - reject(err) - } else { - resolve(stdout); - } - }); - }); -} - // Sets base rules, with default to 'drop', but allows established and related connections. function insertFinalCounters () { return Promise.all([ @@ -101,6 +89,9 @@ function determineVerdict (interface, packet, direction) { if (rules[direction][packet.protocol.toString()].global.ports) { if (rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport]) { thisVerdict = NF_ACCEPT; + if (rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport].callback) { + rules[direction][packet.protocol.toString()].global.ports[packet.payload.dport].callback(); + } return thisVerdict; } } else { @@ -115,6 +106,9 @@ function determineVerdict (interface, packet, direction) { if (rules[direction][packet.protocol.toString()][interface.zone].ports) { if (rules[direction][packet.protocol.toString()][interface.zone].ports[packet.payload.dport]) { thisVerdict = NF_ACCEPT; + 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(); + } } } else { thisVerdict = NF_ACCEPT; @@ -125,6 +119,10 @@ function determineVerdict (interface, packet, direction) { return thisVerdict; } +function updateOutput () { + process.stdout.write('Connections - Accepted: ' + packetsAccepted + ' (I: ' + packetsAcceptedIn + ' O: ' + packetsAcceptedOut + ') - Rejected: ' + packetsRejected + ' (I: ' + packetsRejectedIn + ' O: ' + packetsRejectedOut + ')\r'); +} + function bindQueueHandlers () { interfaces.forEach(interface => { interface.queueIn = nfq.createQueueHandler(parseInt(interface.number), buffer, (nfpacket) => { @@ -140,9 +138,8 @@ function bindQueueHandlers () { packetsAcceptedIn++; nfpacket.setVerdict(NF_REQUEUE, 999); } - - process.stdout.write('Connections - Accepted: ' + packetsAccepted + ' (I: ' + packetsAcceptedIn + ' O: ' + packetsAcceptedOut + ') - Rejected: ' + packetsRejected + ' (I: ' + packetsRejectedIn + ' O: ' + packetsRejectedOut + ')\r'); }); + interface.queueOut = nfq.createQueueHandler(parseInt('100' + interface.number), buffer, (nfpacket) => { let packet = new IPv4().decode(nfpacket.payload, 0); let thisVerdict = determineVerdict(interface, packet, 'outgoing'); @@ -160,14 +157,12 @@ function bindQueueHandlers () { packetsAcceptedOut++; nfpacket.setVerdict(NF_REQUEUE, 999); } - - process.stdout.write('Connections - Accepted: ' + packetsAccepted + ' (I: ' + packetsAcceptedIn + ' O: ' + packetsAcceptedOut + ') - Rejected: ' + packetsRejected + ' (I: ' + packetsRejectedIn + ' O: ' + packetsRejectedOut + ')\r'); }); }) } nft.flush().then( - (resolved) => nft.inject('./base.rules'), + (resolved) => nft.inject('./src/config/base.rules'), (reject) => console.log('failed to flush rules') ).then( (resolved) => setupInterfaces(), @@ -178,4 +173,8 @@ nft.flush().then( ).then( (resolved) => insertFinalCounters(), (reject) => console.log('Failed to bind queue handlers') +).catch( + (err) => console.log('Failed to insert final counters') ); + +const outputInterval = setInterval(updateOutput, 2500);