From 5622ddce940c29c9c02c1099a690063a11fa586d Mon Sep 17 00:00:00 2001 From: Cameron Diver Date: Thu, 3 May 2018 12:42:51 +0100 Subject: [PATCH] Convert iptables module to typescript Change-type: patch Signed-off-by: Cameron Diver --- src/lib/iptables.coffee | 25 --------------------- src/lib/iptables.ts | 32 +++++++++++++++++++++++++++ test/06-iptables.spec.coffee | 42 +++++++++++++++++------------------- 3 files changed, 52 insertions(+), 47 deletions(-) delete mode 100644 src/lib/iptables.coffee create mode 100644 src/lib/iptables.ts diff --git a/src/lib/iptables.coffee b/src/lib/iptables.coffee deleted file mode 100644 index 18844e73..00000000 --- a/src/lib/iptables.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Promise = require 'bluebird' -childProcess = Promise.promisifyAll(require('child_process')) - -clearAndAppendIptablesRule = (rule) -> - childProcess.execAsync("iptables -D #{rule}") - .catchReturn() - .then -> - childProcess.execAsync("iptables -A #{rule}") - -clearAndInsertIptablesRule = (rule) -> - childProcess.execAsync("iptables -D #{rule}") - .catchReturn() - .then -> - childProcess.execAsync("iptables -I #{rule}") - -exports.rejectOnAllInterfacesExcept = (allowedInterfaces, port) -> - # We delete each rule and create it again to ensure ordering (all ACCEPTs before the REJECT/DROP). - # This is especially important after a supervisor update. - Promise.each allowedInterfaces, (iface) -> - clearAndInsertIptablesRule("INPUT -p tcp --dport #{port} -i #{iface} -j ACCEPT") - .then -> - clearAndAppendIptablesRule("INPUT -p tcp --dport #{port} -j REJECT") - .catch -> - # On systems without REJECT support, fall back to DROP - clearAndAppendIptablesRule("INPUT -p tcp --dport #{port} -j DROP") diff --git a/src/lib/iptables.ts b/src/lib/iptables.ts new file mode 100644 index 00000000..4162a920 --- /dev/null +++ b/src/lib/iptables.ts @@ -0,0 +1,32 @@ +import * as Promise from 'bluebird'; +import * as childProcess from 'child_process'; + +// The following is exported so that we stub it in the tests +export const execAsync = Promise.promisify(childProcess.exec); + +function clearAndAppendIptablesRule(rule: string): Promise { + return execAsync(`iptables -D ${rule}`) + .catchReturn(null) + .then(() => execAsync(`iptables -A ${rule}`)) + .return(); +} + +function clearAndInsertIptablesRule(rule: string): Promise { + return execAsync(`iptables -D ${rule}`) + .catchReturn(null) + .then(() => execAsync(`iptables -I ${rule}`)) + .return(); +} + +export function rejectOnAllInterfacesExcept( + allowedInterfaces: string[], + port: number, +): Promise { + // We delete each rule and create it again to ensure ordering (all ACCEPTs before the REJECT/DROP). + // This is especially important after a supervisor update. + return Promise.each(allowedInterfaces, (iface) => clearAndInsertIptablesRule(`INPUT -p tcp --dport ${port} -i ${iface} -j ACCEPT`)) + .then(() => clearAndAppendIptablesRule(`INPUT -p tcp --dport ${port} -j REJECT`)) + // On systems without REJECT support, fall back to DROP + .catch(() => clearAndAppendIptablesRule(`INPUT -p tcp --dport ${port} -j DROP`)) + .return(); +} diff --git a/test/06-iptables.spec.coffee b/test/06-iptables.spec.coffee index f9b3ea76..be8777d4 100644 --- a/test/06-iptables.spec.coffee +++ b/test/06-iptables.spec.coffee @@ -1,43 +1,41 @@ Promise = require 'bluebird' iptables = require '../src/lib/iptables' -childProcess = require('child_process') - m = require 'mochainon' { stub } = m.sinon { expect } = m.chai describe 'iptables', -> it 'calls iptables to delete and recreate rules to block a port', -> - stub(childProcess, 'execAsync').returns(Promise.resolve()) + stub(iptables, 'execAsync').returns(Promise.resolve()) iptables.rejectOnAllInterfacesExcept(['foo', 'bar'], 42) .then -> - expect(childProcess.execAsync.callCount).to.equal(6) - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j REJECT') - expect(childProcess.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j REJECT') + expect(iptables.execAsync.callCount).to.equal(6) + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j REJECT') + expect(iptables.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j REJECT') .then -> - childProcess.execAsync.restore() + iptables.execAsync.restore() it "falls back to blocking the port with DROP if there's no REJECT support", -> - stub(childProcess, 'execAsync').callsFake (cmd) -> + stub(iptables, 'execAsync').callsFake (cmd) -> if /REJECT$/.test(cmd) Promise.reject() else Promise.resolve() iptables.rejectOnAllInterfacesExcept(['foo', 'bar'], 42) .then -> - expect(childProcess.execAsync.callCount).to.equal(8) - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT') - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j REJECT') - expect(childProcess.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j REJECT') - expect(childProcess.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j DROP') - expect(childProcess.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j DROP') + expect(iptables.execAsync.callCount).to.equal(8) + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i foo -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i foo -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -i bar -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -I INPUT -p tcp --dport 42 -i bar -j ACCEPT') + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j REJECT') + expect(iptables.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j REJECT') + expect(iptables.execAsync).to.be.calledWith('iptables -D INPUT -p tcp --dport 42 -j DROP') + expect(iptables.execAsync).to.be.calledWith('iptables -A INPUT -p tcp --dport 42 -j DROP') .then -> - childProcess.execAsync.restore() \ No newline at end of file + iptables.execAsync.restore()