js-zrim-netfilter-manager
Version:
Application to manage my personnal firewall
341 lines (299 loc) • 9.8 kB
JavaScript
const _ = require('lodash'),
util = require('util'),
defaultLogger = require('js-zrim-proxy-logger').defaultLogger,
ArgumentParser = require('argparse').ArgumentParser,
WinstonProxyLogger = require('js-zrim-proxy-logger').WinstonProxyLogger,
jsErrors = require('js-zrim-errors'),
commonErrors = jsErrors.common,
Joi = require('joi');
const APP_VERSION = require('./../../package.json').version;
const argsParser = new ArgumentParser({
version: APP_VERSION,
addHelp: true,
description: 'Generic Netfilter Genenrator'
});
argsParser.addArgument(
[ '-i', '--id' ],
{
help: 'The configuration id to use',
required: true,
dest: 'configurationId',
action: 'store',
metavar: 'configuration_id'
}
);
argsParser.addArgument(
[ '-c', '--config-file' ],
{
help: 'Configuration file',
required: true,
dest: 'configurationFilePath',
action: 'store',
metavar: 'configuration_file_path'
}
);
argsParser.addArgument(
[ '-a', '--action' ],
{
help: 'Action to perform',
choices: ['install', 'uninstall'],
required: true,
dest: 'action',
action: 'store'
}
);
argsParser.addArgument(
[ '-j', '--job' ],
{
help: 'The job to use',
required: true,
dest: 'jobName',
action: 'store'
}
);
argsParser.addArgument(
[ '-p', '--print' ],
{
help: 'Print rules',
dest: 'printRules',
action: 'storeTrue',
defaultValue: false
}
);
argsParser.addArgument(
[ '-s', '--apply' ],
{
help: 'Apply the action',
dest: 'apply',
action: 'storeTrue',
defaultValue: false
}
);
const usageArgs = argsParser.parseArgs();
const applicationWorkflow = {
instance: {},
context: {
logger: new WinstonProxyLogger({
target: defaultLogger.getDefaultLogger(null),
prefixes: [`Netfilter:${APP_VERSION}`]
}),
usageArgs: usageArgs,
configuration: {}
}
};
Promise.resolve()
.then(() => {
return new Promise((resolve, reject) => {
const context = applicationWorkflow.context;
const logger = context.logger.of({
prefixes: ['LoadConfig']
});
const fse = require('fs-extra');
fse.readFile(context.usageArgs.configurationFilePath)
.then(data => {
const yamlParser = require('js-yaml');
const config = yamlParser.safeLoad(data);
context.rawConfiguration = config;
})
.then(() => {
return new Promise((resolve, reject) => {
const schema = require('./generic-main-config-schema').configurationSchema;
Joi.validate(context.rawConfiguration, schema, (error, configuration) => {
if (error) {
logger.error(`Invalid configuration: ${error.message}\n${error.stack}`);
return reject(error);
}
context.configuration = configuration;
resolve();
});
});
})
.then(() => {
// Check if configuration exists
return new Promise((resolve, reject) => {
const configurationId = context.usageArgs.configurationId;
const configurationItem = _.find(context.configuration.configurations, {
id: configurationId
});
if (!configurationItem) {
logger.error(`Cannot find the configuration id ${configurationId}`);
return reject(new Error(`Cannot find the configuration item ${configurationId}`));
}
context.configurationItem = configurationItem;
// Search the job
const jobName = context.usageArgs.jobName;
const configurationJob = _.find(context.configurationItem.jobs, {
name: jobName
});
if (!configurationJob) {
logger.error(`Cannot find the configuration job ${jobName}`);
return reject(new Error(`Cannot find the configuration job ${jobName}`));
}
context.configurationJob = configurationJob;
context.jobConfiguration = context.configurationJob.configuration;
resolve();
});
})
.then(resolve)
.catch(reject);
});
})
.then(() => {
return new Promise((resolve, reject) => {
const context = applicationWorkflow.context;
const logger = context.logger.of({
prefixes: ['CreateJobInstance']
});
let jobModule = undefined;
try {
jobModule = require(`./../jobs/engines/${context.configurationJob.engine.name}`);
} catch (error) {
logger.error("Cannot require the job %s.\n%s\n%s", context.configurationJob.name, error.message, error.stack);
return reject(new commonErrors.NotFoundError(`Job ${context.configurationJob.name} not found`));
}
context.jobInstance = new jobModule.Job();
context.jobInstance.initialize({})
.then(() => {
logger.debug("Job '%s' initialized", context.configurationJob.name);
return resolve();
})
.catch(error => {
logger.error("Initialization job %s failed.\n%s\n%s", context.configurationJob.name, error.message, error.stack);
return reject(error);
});
});
})
.then(() => {
const context = applicationWorkflow.context;
const logger = context.logger.of({
prefixes: ['ExecuteJob']
});
context.jobCommand = {
type: context.usageArgs.action
};
return context.jobInstance.execute(context);
})
.then(executionResponse => {
const context = applicationWorkflow.context;
const logger = context.logger.of({
prefixes: ['HandleExecutionResponse']
});
context.jobResponse = executionResponse;
logger.debug("Execution of '%s' done", context.configurationJob.name);
})
.then(() => {
return new Promise(resolve => {
const context = applicationWorkflow.context;
const logger = context.logger.of({
prefixes: ['Print']
});
if (context.usageArgs.printRules !== true) {
return;
}
let data = "--------------------\n";
data += "--------------------\n";
_.each(context.jobResponse.securityCommands, command => {
switch (command.type) {
case 'iptables-4':
data += 'iptables ';
break;
case 'ipset':
data += 'ipset ';
break;
default:
return;
}
data += `${command.value}\n`;
});
data += "--------------------\n";
data += "--------------------\n";
process.stdout.write(data);
resolve();
});
})
.then(() => {
return new Promise((resolve, reject) => {
const context = applicationWorkflow.context;
const logger = context.logger.of({
prefixes: ['Apply']
});
if (context.usageArgs.apply !== true) {
return;
}
let shFileData = "#! /bin/sh\n\n";
_.each(context.jobResponse.securityCommands, command => {
switch (command.type) {
case 'iptables-4':
shFileData += 'iptables ';
break;
case 'ipset':
shFileData += 'ipset ';
break;
default:
return;
}
shFileData += `${command.value}\n`;
});
const generateUuid = require('uuid/v4'),
fse = require('fs-extra');
const temporaryFilePath = '/tmp/netfilter-apply-' + Date.now() + '-' + generateUuid() + '.sh';
const removeTemporaryFile = () => {
logger.info(`Remove temporary file ${temporaryFilePath}`);
fse.pathExists(temporaryFilePath)
.then(exists => {
if (exists) {
return fse.remove(temporaryFilePath);
}
})
.then(() => {
logger.debug(`Temporary file ${temporaryFilePath} removed`);
})
.catch(error => {
logger.error(`Failed to remove temporary file ${temporaryFilePath}: ${error.message}\n${error.stack}`);
});
};
logger.info(`Write temporary file ${temporaryFilePath}`);
return fse.outputFile(temporaryFilePath, shFileData, {
mode: 0o740
})
.then(() => {
logger.info(`Execute temporary file ${temporaryFilePath}`);
return new Promise((resolve, reject) => {
const child_process = require('child_process');
child_process.exec(temporaryFilePath, function (error, stdout, stderr) {
if (error) {
logger.error(`Error while executing ${temporaryFilePath}: ${error.message}\n${error.stack}`);
logger.error(`---- STDOUT BEGIN ----`);
logger.error(stdout);
logger.error(`---- STDOUT END ----`);
logger.error(`---- STDERR BEGIN ----`);
logger.error(stderr);
logger.error(`---- STDERR END ----`);
return reject(error);
}
logger.info(`Execution of ${temporaryFilePath} done with success`);
logger.debug(`---- STDOUT BEGIN ----`);
logger.debug(stdout);
logger.debug(`---- STDOUT END ----`);
logger.debug(`---- STDERR BEGIN ----`);
logger.debug(stderr);
logger.debug(`---- STDERR END ----`);
return resolve();
});
});
})
.then(() => {
removeTemporaryFile();
resolve();
})
.catch(error => {
removeTemporaryFile();
reject(error);
});
});
})
.catch(error => {
process.stderr.write(`Failed to process: ${error.message}\n${error.stack}\n`);
process.exit(1);
});