UNPKG

js-zrim-netfilter-manager

Version:
445 lines (368 loc) 20.5 kB
const SimpleInitializableObject = require('js-zrim-core').SimpleInitializableObject, _ = require('lodash'), Joi = require('joi'), jsErrors = require('js-zrim-errors'), commonErrors = jsErrors.common; /** * Job to initialize netfilter for our purpose * @implements {SimpleInitializableObject} * @constructor */ function PrepareNetfilterJob() { if (!(this instanceof PrepareNetfilterJob)) { return new (Function.prototype.bind.apply(PrepareNetfilterJob, Array.prototype.concat.apply([null], arguments)))(); } SimpleInitializableObject.apply(this, arguments); } SimpleInitializableObject._applyPrototypeTo(PrepareNetfilterJob); /** * Execute the job * @param {BaseJob~ExecutionContext} context the context * @return {Promise} {@link BaseJob~ExecutionOnResolve} on resolve */ PrepareNetfilterJob.prototype.execute = function (context) { return new Promise((resolve, reject) => { const logger = context.logger.of({ prefixes: ['main'] }); _.set(context, 'commands.install', []); _.set(context, 'commands.unInstall', []); const availableSteps = this.execute.Steps; const steps = [ availableSteps.validateConfiguration ]; if (context.jobCommand.type.toLowerCase() === 'install') { steps.push(availableSteps.generateVitalAccessChain); steps.push(availableSteps.generateBlockNetworkChain); steps.push(availableSteps.generateServiceAccessChain); steps.push(availableSteps.generateTrustedNetworkChain); steps.push(availableSteps.generateRootAccessChains); } else if (context.jobCommand.type.toLowerCase() === 'uninstall') { steps.push(availableSteps.generateRootAccessChains); steps.push(availableSteps.generateTrustedNetworkChain); steps.push(availableSteps.generateServiceAccessChain); steps.push(availableSteps.generateBlockNetworkChain); steps.push(availableSteps.generateVitalAccessChain); } else { return reject(new commonErrors.IllegalArgumentError(`Invalid command ${context.jobCommand.type}`)); } let workflowPromise = Promise.resolve({}); _.each(steps, step => { workflowPromise = workflowPromise.then(() => step.call(this, context)); }); workflowPromise .then(() => { const response = { securityCommands: [] }; if (context.jobCommand.type === 'install') { response.securityCommands = context.commands.install; } else { response.securityCommands = context.commands.unInstall; } resolve(response); }) .catch(error => { logger.error("Error while executing the workflow: %s\n%s", error.message, error.stack); reject(error); }); }); }; /** * Make this string command as a {@link SecurityCommand} iptables ipv4 * @param {string} command The command to make * @return {SecurityCommand} The command created * @private */ PrepareNetfilterJob.prototype._asIptables4Command = function (command) { return { type: 'iptables-4', value: command }; }; /** * Make this string command as a {@link SecurityCommand} ipset * @param {string} command The command to make * @return {SecurityCommand} The command created * @private */ PrepareNetfilterJob.prototype._asIpSetCommand = function (command) { return { type: 'ipset', value: command }; }; PrepareNetfilterJob.prototype.execute.Steps = { /** * Validate the job configuration * @param {BaseJob~ExecutionContext} context the context * @return {Promise} */ validateConfiguration: function (context) { return new Promise((resolve, reject) => { const schema = Joi.object().keys({ network: Joi.object().keys({ trustedItems: Joi.array().items( Joi.object().keys({ value: Joi.string().ip({ cidr: 'required' }).required(), description: Joi.string() }).unknown() ), primaryInterfaces: Joi.array().items( Joi.object().keys({ name: Joi.string().required(), networks: Joi.array().items( Joi.object().keys({ value: Joi.string().ip({ cidr: 'required' }).required(), description: Joi.string() }).unknown().required() ).required(), rules: Joi.object().keys({ input: Joi.object().keys({ defaultAction: Joi.string().required() }).unknown().required(), output: Joi.object().keys({ defaultAction: Joi.string().required() }).unknown().required() }).unknown().required() }).unknown().required() ).required() }).unknown().required() }).unknown().required(); Joi.validate(context.jobConfiguration, schema, (error, configValidated) => { if (error) { return reject(new commonErrors.IllegalArgumentError(`Invalid configuration: ${error.message}`), error); } context.rawJobConfiguration = context.jobConfiguration; context.jobConfiguration = configValidated; resolve(); }); }); }, /** * Generate the vital access chains * @param {BaseJob~ExecutionContext} context the context * @return {Promise} */ generateVitalAccessChain: function (context) { return new Promise(resolve => { const installCommands = [], unInstallCommands = []; const logger = context.logger.of({ prefixes: ['generateVitalAccessChain'] }); const chainName = "vital_access_0"; // Clean up & create installCommands.push(this._asIptables4Command(`-N FWD_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F FWD_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X FWD_${chainName}`)); installCommands.push(this._asIptables4Command(`-N IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X IN_${chainName}`)); installCommands.push(this._asIptables4Command(`-N OUT_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F OUT_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X OUT_${chainName}`)); const primaryInterfaces = context.jobConfiguration.network.primaryInterfaces; _.each(primaryInterfaces, primaryInterface => { logger.info("Adding DHCP"); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface ${primaryInterface.name} -p udp --dport 67:68 --sport 67:68 -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface ${primaryInterface.name} -p udp --dport 67:68 --sport 67:68 -j ACCEPT`)); logger.info("Adding dns client"); // UDP installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface ${primaryInterface.name} -p udp --sport 1024:65535 --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface ${primaryInterface.name} -p udp --sport 53 --dport 1024:65535 -m state --state ESTABLISHED,RELATED -j ACCEPT`)); // TCP installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface ${primaryInterface.name} -p tcp --sport 1024:65535 --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface ${primaryInterface.name} -p tcp --sport 53 --dport 1024:65535 -m state --state ESTABLISHED,RELATED -j ACCEPT`)); logger.info("Adding icmp"); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} -p icmp --in-interface ${primaryInterface.name} -d 0.0.0.0/0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -p icmp --out-interface ${primaryInterface.name} -s 0.0.0.0/0 -m state --state ESTABLISHED,RELATED -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -p icmp --icmp-type 8 --in-interface ${primaryInterface.name} -s 0.0.0.0/0 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} -p icmp --out-interface ${primaryInterface.name} -d 0.0.0.0/0 -m state --state ESTABLISHED,RELATED -j ACCEPT`)); logger.info("Adding ntp"); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface ${primaryInterface.name} -p udp --sport 1024:65535 --dport 123 -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface ${primaryInterface.name} -p udp --sport 123 --dport 1024:65535 -m state --state ESTABLISHED,RELATED -j ACCEPT`)); logger.info("Adding root access"); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface ${primaryInterface.name} -m owner --uid-owner 0 -j ACCEPT`)); logger.info("Adding accept known packets"); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface ${primaryInterface.name} -m state --state ESTABLISHED,RELATED -j ACCEPT`)); _.each(primaryInterface.networks, interfaceNetwork => { logger.info(`Adding lo for network ${interfaceNetwork.value}`); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface lo -s ${interfaceNetwork.value} -d ${interfaceNetwork.value} -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} --in-interface lo -s ${interfaceNetwork.value} -d 127.0.0.0/8 -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface lo -s ${interfaceNetwork.value} -d ${interfaceNetwork.value} -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface lo -s ${interfaceNetwork.value} -d 127.0.0.0/8 -j ACCEPT`)); }); }); installCommands.push(this._asIptables4Command(`-A IN_${chainName} ! --in-interface lo -d 127.0.0.0/8 -j REJECT`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} --out-interface lo -d 127.0.0.0/8 -j ACCEPT`)); logger.info("Jump forward to input"); installCommands.push(this._asIptables4Command(`-A FWD_${chainName} -j IN_${chainName}`)); logger.info("Last step RETURN"); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -j RETURN`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} -j RETURN`)); installCommands.push(this._asIptables4Command(`-A FWD_${chainName} -j RETURN`)); context.commands.install = _.concat(context.commands.install, installCommands); context.commands.unInstall = _.concat(context.commands.unInstall, unInstallCommands); resolve(); }); }, /** * Generate block chain access * @param {BaseJob~ExecutionContext} context the context * @return {Promise} */ generateBlockNetworkChain: function (context) { return new Promise(resolve => { const installCommands = [], unInstallCommands = []; const logger = context.logger.of({ prefixes: ['blockNetworkChain'] }); const chainName = "block_access_0"; logger.debug("Create the chain 'IN_%s'", chainName); installCommands.push(this._asIptables4Command(`-N IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X IN_${chainName}`)); logger.debug("Add create ipset net '%s'", 'block_net'); installCommands.push(this._asIpSetCommand(`-! create block_net hash:net`)); unInstallCommands.push(this._asIpSetCommand(`-! destroy block_net`)); logger.debug("Configure chain 'IN_%s'", chainName); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -j DROP`)); context.commands.install = _.concat(context.commands.install, installCommands); context.commands.unInstall = _.concat(context.commands.unInstall, unInstallCommands); resolve(); }); }, /** * Generate block chain access * @param {BaseJob~ExecutionContext} context the context * @return {Promise} */ generateServiceAccessChain: function (context) { return new Promise(resolve => { const installCommands = [], unInstallCommands = []; const logger = context.logger.of({ prefixes: ['serviceAccessChain'] }); const chainName = "services_access_0"; logger.debug("Create the chain 'IN_%s'", chainName); installCommands.push(this._asIptables4Command(`-N IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X IN_${chainName}`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -j RETURN`)); logger.debug("Create the chain 'OUT_%s'", chainName); installCommands.push(this._asIptables4Command(`-N OUT_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F OUT_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X OUT_${chainName}`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} -j RETURN`)); logger.debug("Create the chain 'FWD_%s'", chainName); installCommands.push(this._asIptables4Command(`-N FWD_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F FWD_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X FWD_${chainName}`)); installCommands.push(this._asIptables4Command(`-A FWD_${chainName} -j RETURN`)); context.commands.install = _.concat(context.commands.install, installCommands); context.commands.unInstall = _.concat(context.commands.unInstall, unInstallCommands); resolve(); }); }, /** * Generate trusted chain access * @param {BaseJob~ExecutionContext} context the context * @return {Promise} */ generateTrustedNetworkChain: function (context) { return new Promise(resolve => { const installCommands = [], unInstallCommands = []; const logger = context.logger.of({ prefixes: ['trustedNetworkChain'] }); const chainName = "trusted_access_0"; const globalTrustedNetworks = _.get(context, 'configuration.global.network.trustedItems', []), jobTrustedNetworks = _.get(context, 'jobConfiguration.network.trustedItems', []), trustedNetworks = _.concat([], globalTrustedNetworks, jobTrustedNetworks); logger.debug("Add create ipset net '%s'", 'trusted_net'); installCommands.push(this._asIpSetCommand(`-! create trusted_net hash:net`)); _.each(trustedNetworks, network => { logger.debug("Add trusted network '%s'", network.value); installCommands.push(this._asIpSetCommand(`-! add trusted_net ${network.value}`)); }); logger.debug("Create the chain 'IN_%s'", chainName); installCommands.push(this._asIptables4Command(`-N IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F IN_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X IN_${chainName}`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -m set --match-set trusted_net src -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A IN_${chainName} -j RETURN`)); logger.debug("Create the chain 'OUT_%s'", chainName); installCommands.push(this._asIptables4Command(`-N OUT_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F OUT_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X OUT_${chainName}`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} -m set --match-set trusted_net dst -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A OUT_${chainName} -j RETURN`)); logger.debug("Create the chain 'FWD_%s'", chainName); installCommands.push(this._asIptables4Command(`-N FWD_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-F FWD_${chainName}`)); unInstallCommands.push(this._asIptables4Command(`-X FWD_${chainName}`)); installCommands.push(this._asIptables4Command(`-A FWD_${chainName} -m set --match-set trusted_net src -j ACCEPT`)); installCommands.push(this._asIptables4Command(`-A FWD_${chainName} -j RETURN`)); // Delete set unInstallCommands.push(this._asIpSetCommand(`-! destroy trusted_net`)); context.commands.install = _.concat(context.commands.install, installCommands); context.commands.unInstall = _.concat(context.commands.unInstall, unInstallCommands); resolve(); }); }, /** * Generate root chain access * @param {BaseJob~ExecutionContext} context the context * @return {Promise} */ generateRootAccessChains: function (context) { return new Promise(resolve => { const installCommands = [], unInstallCommands = []; const logger = context.logger.of({ prefixes: ['rootAccessChains'] }); installCommands.push(this._asIptables4Command(`-I INPUT 1 -j IN_trusted_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D INPUT -j IN_trusted_access_0`)); installCommands.push(this._asIptables4Command(`-I INPUT 2 -j IN_vital_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D INPUT -j IN_vital_access_0`)); installCommands.push(this._asIptables4Command(`-I INPUT 3 -m set --match-set block_net src -j IN_block_access_0`)); // Chain for block input unInstallCommands.push(this._asIptables4Command(`-D INPUT -m set --match-set block_net src -j IN_block_access_0`)); installCommands.push(this._asIptables4Command(`-I INPUT 4 -j IN_services_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D INPUT -j IN_services_access_0`)); const primaryInterfaces = context.jobConfiguration.network.primaryInterfaces; _.each(primaryInterfaces, primaryInterface => { installCommands.push(this._asIptables4Command(`-A INPUT --in-interface ${primaryInterface.name} -j ${primaryInterface.rules.input.defaultAction}`)); unInstallCommands.push(this._asIptables4Command(`-D INPUT --in-interface ${primaryInterface.name} -j ${primaryInterface.rules.input.defaultAction}`)); }); installCommands.push(this._asIptables4Command(`-I OUTPUT 1 -j OUT_trusted_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D OUTPUT -j OUT_trusted_access_0`)); installCommands.push(this._asIptables4Command(`-I OUTPUT 2 -j OUT_vital_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D OUTPUT -j OUT_vital_access_0`)); installCommands.push(this._asIptables4Command(`-I OUTPUT 3 -j OUT_services_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D OUTPUT -j OUT_services_access_0`)); // FORWARD installCommands.push(this._asIptables4Command(`-I FORWARD 1 -j FWD_trusted_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D FORWARD -j FWD_trusted_access_0`)); installCommands.push(this._asIptables4Command(`-I FORWARD 2 -j FWD_vital_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D FORWARD -j FWD_vital_access_0`)); installCommands.push(this._asIptables4Command(`-I FORWARD 3 -m set --match-set block_net src -j IN_block_access_0`)); // Chain for block input unInstallCommands.push(this._asIptables4Command(`-D FORWARD -m set --match-set block_net src -j IN_block_access_0`)); installCommands.push(this._asIptables4Command(`-I FORWARD 4 -j FWD_services_access_0`)); unInstallCommands.push(this._asIptables4Command(`-D FORWARD -j FWD_services_access_0`)); context.commands.install = _.concat(context.commands.install, installCommands); context.commands.unInstall = _.concat(context.commands.unInstall, unInstallCommands); resolve(); }); } }; exports.PrepareNetfilterJob = module.exports.PrepareNetfilterJob = PrepareNetfilterJob; exports.Job = module.exports.Job = PrepareNetfilterJob;