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);