beame-insta-ssl
Version:
Quick TLS (SSL) security for you server by Beame.io
374 lines (332 loc) • 11.9 kB
JavaScript
;
const fs = require('fs');
const path = require('path');
const argv = require('minimist')(process.argv.slice(2));
const _ = require('underscore');
const beameSDK = require('beame-sdk');
const BeameStore = beameSDK.BeameStore;
const BeameLogger = beameSDK.Logger;
const logger = new BeameLogger("BeameInstaSSL");
const pem = require('pem');
let commands = {};
['creds', 'tunnel', 'system', 'tunnelClient'].forEach(cmdName => {
commands[cmdName] = require('./' + cmdName + '.js');
});
const parametersSchema = {
'data': {required: true},
'developerEmail': {required: true},
'developerFqdn': {required: true},
'developerName': {required: true},
'format': {required: false, options: ['text', 'json'], default: 'text'},
'fqdn': {required: false},
'signingFqdn': {required: false},
'signature': {required: true},
'regex': {required: false},
'uid': {required: true},
'targetFqdn': {required: true},
'file': {required: false},
'signerFqdn': {required: true},
'authorizationFqdn': {required: false},
'authenticationFqdn': {required: false},
'pk': {required: true},
'requiredLevel': {required: false, options: ['Default', 'AuthenticationServer', 'AuthorizationServer']},
'count': {required: false, default: 1},
'sharedFolder': {required: false},
'localIp': {required: false},
'localPort': {required: true},
'sharedSecret': {required: false},
'secret': {required: false},
// createWithToken
'signWithFqdn': {required: false},
'parent_fqdn': {required: false},
'dataToSign': {required: false},
'authSrvFqdn': {required: false},
'authToken': {required: true, base64: true},
'signerAuthToken': {required: false, base64: true},
'regToken': {required: false, base64: true},
'name': {required: false},
'email': {required: false},
'encryptedData': {required: true, base64: true, json: true},
'signedData': {required: true, base64: true, json: true},
'ttl': {required: false, default: 60 * 60 * 24 * 2},
'src': {required: false},
'serviceName': {required: false},
'matchingFqdn': {required: false},
'serviceId': {required: false},
'userId': {required: false},
// CLI branch new keys
'dir': {required: true},
'hostname': {required: false},
'dst': {required: true},
'proto': {required: true},
'token': {required: true, base64: true, json: true},
'value': {required: false},
'useBestProxy': {required: false},
'validityPeriod': {required: false},
'dnsFqdn': {required: false},
'forceCheck': {required: false},
'highestFqdn': {required: false},
'trustDepth': {required: false},
'noAuth': {required: false},
'filter': {required: false},
'hasPrivateKey': {required: false, default: 'true'},
'expiration': {required: false},
'anyParent': {required: false}
};
function InvalidArgv(message) {
this.name = 'InvalidArgv';
this.message = message;
}
InvalidArgv.prototype = Error.prototype;
function getHelpMessage(fileName) {
return fs.readFileSync(path.join(__dirname, '..', 'help-messages', fileName), {'encoding': 'utf-8'});
}
function defaultTheOnlyFqdn(args) {
let cert, fqdn;
if (args.fqdn) {
return args.fqdn;
}
let allCerts = require('./creds').list(null, 'true');
if (allCerts.length > 1) {
console.log("--fqdn parameter is required because you have more than one certificate");
console.log("Possible FQDNs are:");
allCerts.forEach(cred => {
console.log(` ${cred.fqdn}`);
});
process.exit(2);
}
args.fqdn = allCerts[0].fqdn;
}
function getParamsNames(fun) {
const names = fun.toString().match(/^[\s(]*function[^(]*\(([^)]*)\)/)[1]
.replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
.replace(/\s+/g, '').split(',');
let ret = (names.length == 1 && !names[0] ? [] : names),
useCallback = false;
ret = ret.filter(paramName => {
if (paramName == 'callback') {
useCallback = true;
return false;
} else {
return true;
}
});
ret.hasFormat = !!fun.toText;
ret.useCallback = useCallback;
return ret;
}
function main() {
let cmdName, subCmdName, cmd;
if (pem.version) {
pem.config({sync: true});
pem.version((err, data) => {
if (err) {
throw Error("Could not run openssl. beame-insta-ssl requires openssl. Please make sure openssl is installed and is in PATH");
}
});
pem.config({sync: false});
} else {
if (!process.env.BEAME_NO_PEM_WARNING) {
logger.info("WARNING: Using 'pem' module without 'config' function. Not checking for openssl availability.");
}
}
// getHelpMessage() is only defined in this file.
commands.creds.getCreds.toText = (metadata) => `Certificate created! Certificate FQDN is ${metadata.fqdn}\n\n` + getHelpMessage('certificate-created.txt');
// Old CLI compatibility - start
let do_warn = false, orig_command = argv._[0];
if (argv._[0] == 'list') {
do_warn = true;
argv._ = ['creds', 'list'];
commands.creds.list.toText = (list) => list.map(cred => cred.fqdn).join('\n');
}
if (argv._[0] == 'syncmeta') {
do_warn = true;
argv._ = ['creds', 'syncmeta'];
defaultTheOnlyFqdn(argv);
}
if (argv._[0] == 'export') {
do_warn = true;
argv.fqdn = argv._[1];
argv.dir = argv._[2];
argv._ = ['creds', 'exportCred'];
}
if (argv._[0] == 'tunnel') {
if (argv._[1] && ((typeof argv._[1] == 'number') || argv._[1].indexOf(':') > -1)) {
do_warn = true;
argv.dst = argv._[1];
argv.proto = argv._[2];
argv.highestFqdn = argv._[3];
argv._ = ['tunnel', 'make'];
}
}
if (do_warn) {
console.warn(`+---------------------------------------------------------------------`)
console.warn(`| Warning: you are using deprecated command "${orig_command}". Use "${argv._.join(' ')}" instead.`)
console.warn(`| Run beame-insta-ssl without any arguments to see all the commands and swithces.`);
console.warn(`+---------------------------------------------------------------------`)
}
// Old CLI compatibility - end
cmdName = argv._[0];
subCmdName = argv._[1];
cmd = commands[cmdName];
if (`${cmdName} ${subCmdName}` != 'creds getCreds' && argv.format != 'json') {
let credsCount = require('./creds').list().length;
if (!credsCount) {
console.log(getHelpMessage('no-certificates.txt'));
process.exit(1);
}
}
if (argv._.length < 2) {
usage();
process.exit(1);
}
if (`${cmdName} ${subCmdName}` == 'tunnel make') {
defaultTheOnlyFqdn(argv);
}
/*
if (argv._[0] == 'complete') {
if (argv._[1] == 'commands') {
console.log(_.keys(commands).join(' '));
process.exit(0);
}
if (argv._[1] == 'sub-commands') {
console.log(_.keys(commands[argv._[2]]).join(' '));
process.exit(0);
}
if (argv._[1] == 'switches') {
let f = commands[argv._[2]][argv._[3]],
paramsNames = getParamsNames(f);
if (paramsNames.hasFormat) {
paramsNames.push('format');
}
let switches = paramsNames.map(function (p) {
return "--" + p;
}).join(' ');
console.log(switches);
process.exit(0);
}
if (argv._[1] == 'switch-value') {
let sw = argv._[2];
if (sw == 'fqdn') {
let store = new BeameStore();
let results = store.list();
console.log(_.map(results, r => r.fqdn).join(' '));
process.exit(0);
}
if (parametersSchema[sw].options) {
console.log(parametersSchema[sw].options.join(' '));
process.exit(0);
}
process.exit(0);
}
process.exit(1);
}
*/
if (!cmd) {
logger.fatal("Command '" + cmdName + "' not found. Valid top-level commands are: " + Object.keys(commands));
}
if (!commands[cmdName][subCmdName]) {
logger.fatal("Sub-command '" + subCmdName + "' for command '" + cmdName + "' not found. Valid sub-commands are: " + Object.keys(commands[cmdName]));
}
// TODO: handle boolean such as in "--fqdn --some-other-switch" or "--no-fqdn"
// Validate argv and build arguments for the function
let paramsNames = getParamsNames(commands[cmdName][subCmdName]),
args = _.map(paramsNames, function (paramName) {
// Required parameter missing
if (parametersSchema[paramName].required && !_.has(argv, paramName)) {
logger.fatal("Command '" + cmdName + ' ' + subCmdName + "' - required argument '" + paramName + "' is missing.");
}
// Optional parameter missing
if (!parametersSchema[paramName].required && !_.has(argv, paramName)) {
if (parametersSchema[paramName].default) {
return parametersSchema[paramName].default;
}
return null;
}
// Parameter must be one of the specified values ("options")
if (parametersSchema[paramName].options) {
if (_.indexOf(parametersSchema[paramName].options, argv[paramName]) == -1) {
logger.fatal("Command '" + cmdName + ' ' + subCmdName + "' - argument '" + paramName + "' must be one of: " + parametersSchema[paramName].options.join(','));
}
}
let arg = argv[paramName];
// Optionally decode base64-encoded argument.
// Do not decode what appears to be JSON.
if (parametersSchema[paramName].base64 && arg[0] != '{' && arg[0] != '"' && arg[0] != '[') {
arg = new Buffer(arg, 'base64').toString();
}
if (parametersSchema[paramName].json) {
//noinspection ES6ModulesDependencies,NodeModulesDependencies
arg = JSON.parse(arg);
}
return arg;
});
/**
*
* @param {Object} error
* @param {Object} output
*/
function commandResultsReady(error, output) {
if (error) {
if (typeof error == 'string') {
logger.fatal(error);
}
if (error instanceof Error) {
logger.error(error.stack);
}
logger.fatal(error.message, error.data, error.module);
}
if (output === undefined) {
return;
}
if (argv.format == 'json' || !commands[cmdName][subCmdName].toText) {
//noinspection ES6ModulesDependencies,NodeModulesDependencies
output = JSON.stringify(output);
} else {
output = commands[cmdName][subCmdName].toText(output).toString();
}
console.log(output);
}
// Run the command
if (paramsNames.useCallback) {
args.push(commandResultsReady);
commands[cmdName][subCmdName].apply(null, args);
} else {
let output = commands[cmdName][subCmdName].apply(null, args);
commandResultsReady(null, output);
}
}
function usage() {
const path = require('path');
const myname = 'beame-insta-ssl';
console.log("Usage:");
_.each(commands, function (subCommands, cmdName) {
_.each(subCommands, function (subCmdFunc, subCmdName) {
let paramsNames = getParamsNames(subCmdFunc);
if (paramsNames.hasFormat) {
paramsNames.push('format');
}
let params = paramsNames.map(function (paramName) {
let ret = '--' + paramName;
if (!parametersSchema[paramName]) {
console.log(`Internal coding error: missing ${paramName} in parametersSchema`);
throw new Error(`Internal coding error: missing ${paramName} in parametersSchema`);
}
if (parametersSchema[paramName].options) {
ret = ret + ' {' + parametersSchema[paramName].options.join('|') + '}';
} else {
ret = ret + ' ' + paramName;
}
if (!parametersSchema[paramName].required) {
ret = '[' + ret + ']';
}
return ret;
});
console.log(' ' + myname + ' ' + cmdName + ' ' + subCmdName + ' ' + params.join(' '));
});
});
console.log(getHelpMessage('no-command.txt'));
process.exit(1);
}
module.exports = main;