diff --git a/CHANGELOG.md b/CHANGELOG.md index 45b76771..b1bedb14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! This project adheres to [Semantic Versioning](http://semver.org/). +## v7.10.1 - 2018-06-11 + +* Convert iptables module to typescript #641 [Cameron Diver] +* Convert errors module to typescript #641 [Cameron Diver] + ## v7.10.0 - 2018-06-06 * Move config backend code out to classes which implement a common base #672 [Cameron Diver] diff --git a/package.json b/package.json index f54a5914..ba1c5cf7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "resin-supervisor", "description": "This is resin.io's Supervisor, a program that runs on IoT devices and has the task of running user Apps (which are Docker containers), and updating them as Resin's API informs it to.", - "version": "7.10.0", + "version": "7.10.1", "license": "Apache-2.0", "repository": { "type": "git", @@ -16,16 +16,17 @@ "coverage": "istanbul report text && istanbul report html" }, "dependencies": { - "@types/lodash": "^4.14.108", - "@types/mz": "0.0.32", "mkfifo": "^0.1.5", - "mz": "^2.7.0", "sqlite3": "^3.1.0" }, "engines": { "node": "^6.13.1" }, "devDependencies": { + "@types/bluebird": "^3.5.20", + "@types/lodash": "^4.14.109", + "@types/mz": "0.0.32", + "@types/node": "^10.3.1", "JSONStream": "^1.1.2", "blinking": "~0.0.2", "bluebird": "^3.5.0", @@ -53,6 +54,7 @@ "mkdirp": "^0.5.1", "mocha": "^5.1.1", "mochainon": "^2.0.0", + "mz": "^2.7.0", "network-checker": "~0.0.5", "node-loader": "^0.6.0", "null-loader": "^0.1.1", diff --git a/src/lib/errors.coffee b/src/lib/errors.coffee deleted file mode 100644 index bd174ef5..00000000 --- a/src/lib/errors.coffee +++ /dev/null @@ -1,6 +0,0 @@ -{ checkInt } = require './validation' - -exports.NotFoundError = (err) -> checkInt(err.statusCode) is 404 -exports.ENOENT = (err) -> err.code is 'ENOENT' -exports.EEXIST = (err) -> err.code is 'EEXIST' -exports.UnitNotLoadedError = (err) -> err[0]?.endsWith?('not loaded.') diff --git a/src/lib/errors.ts b/src/lib/errors.ts new file mode 100644 index 00000000..9edc86e5 --- /dev/null +++ b/src/lib/errors.ts @@ -0,0 +1,19 @@ +import { endsWith } from 'lodash'; + +import { checkInt } from './validation'; + +export function NotFoundError(err: { statusCode?: string }): boolean { + return checkInt(err.statusCode) === 404; +} + +export function ENOENT(err: { code: string, [key: string]: any }): boolean { + return err.code === 'ENOENT'; +} + +export function EEXISTS(err: { code: string, [key: string]: any }): boolean { + return err.code === 'EEXISTS'; +} + +export function UnitNotLoadedError(err: string[]): boolean { + return endsWith(err[0], 'not loaded.'); +} 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()