Mise à jour de GitLab prévue ce samedi 23 octobre 2021 à partir de 9h00 CET

Commit 5f21bf5a authored by Cédric Moreau's avatar Cédric Moreau
Browse files

Add Wizard configuration helper

parent f58b1509
#!/bin/bash
gpg --armor --export-secret-key $1
\ No newline at end of file
#!/bin/bash
gpg --list-secret-keys --fingerprint
\ No newline at end of file
......@@ -29,7 +29,10 @@ module.exports.pgp = openpgp;
var privateKey;
module.exports.privateKey = function () {
module.exports.privateKey = function (pvKey) {
if (pvKey) {
privateKey = pvKey;
}
return privateKey;
};
......@@ -38,9 +41,14 @@ module.exports.publicKey = function () {
return privateKey ? privateKey.toPublic().armor() : "";
};
module.exports.fingerprint = function () {
module.exports.cert = function () {
var ascciiPubkey = module.exports.publicKey();
return ascciiPubkey ? jpgp().certificate(ascciiPubkey).fingerprint : '';
return ascciiPubkey ? jpgp().certificate(ascciiPubkey) : null;
};
module.exports.fingerprint = function () {
var cert = module.exports.cert();
return cert ? cert.fingerprint : '';
};
module.exports.sign = function (message, done) {
......@@ -161,6 +169,68 @@ module.exports.services = {
init: initServices
};
module.exports.checkConf = function (conf, loggingMethod) {
var errors = [];
var privateKey = openpgp.key.readArmored(conf.pgpkey).keys[0];
if (conf.pgppasswd == null) {
conf.pgppasswd = "";
}
if (!privateKey) {
errors.push('This node requires a private key to work.');
}
try {
if(privateKey && !privateKey.decrypt(conf.pgppasswd)) {
errors.push('Wrong private key password.');
}
} catch(ex) {
errors.push('Not a valid private key, message was: "' + ex.message + '"');
}
if (!conf.currency) {
errors.push('No currency name was given.');
}
if(!conf.ipv4 && !conf.ipv6){
errors.push("No interface to listen to.");
}
if(!conf.remoteipv4 && !conf.remoteipv6){
errors.push('No interface for remote contact.');
}
if (!conf.remoteport) {
errors.push('No port for remote contact.');
}
if (conf.sync.AMDaemon == "ON") {
if (!conf.sync.AMStart) {
errors.push('Autovoting enabled but starting date not given');
}
if (!conf.sync.AMFreq) {
errors.push('Autovoting enabled but amendment frequency not given');
}
if (!conf.sync.UDFreq) {
errors.push('Autovoting enabled but dividend frequency not given');
}
if (!conf.sync.UD0) {
errors.push('Autovoting enabled but initial dividend not given');
}
if (!conf.sync.UDPercent) {
errors.push('Autovoting enabled but %dividend not given');
}
if (!conf.sync.Consensus) {
errors.push('Autovoting enabled but %required votes not given');
}
if (!conf.sync.MSExpires) {
errors.push('Autovoting enabled but membership validity not given');
}
if (!conf.sync.VTExpires) {
errors.push('Autovoting enabled but voting validity not given');
}
}
if (typeof loggingMethod == 'function') {
errors.forEach(loggingMethod);
}
return errors;
}
module.exports.express = {
app: function (conf, onLoaded) {
......@@ -170,73 +240,15 @@ module.exports.express = {
var currency = conf.currency;
// Checking configuration
privateKey = openpgp.key.readArmored(conf.pgpkey).keys[0];
if (conf.pgppasswd == null) {
conf.pgppasswd = "";
}
if (!privateKey) {
onLoaded('A private key for this node is mandatory. Relaunch with --pgpkey <pathToKey>.');
var confErrors = module.exports.checkConf(conf);
if (confErrors.length > 0) {
onLoaded(confErrors[0]);
return;
}
try {
if(!privateKey.decrypt(conf.pgppasswd)) {
onLoaded('Wrong private key password. Relaunch with --pgppasswd <password>.');
return;
}
} catch(ex) {
onLoaded('Not a valid private key, message was: "' + ex.message + '"');
return;
}
if (!conf.currency) {
onLoaded('No currency name was given. Relaunch with --currency <currencyName> parameter.');
return;
}
if(!conf.ipv4 && !conf.ipv6){
onLoaded("No interface to listen to. Relaunch with either --ipv4 or --ipv6 parameters.");
return;
}
if(!conf.remoteipv4 && !conf.remoteipv6){
onLoaded('Either --remote4 or --remote6 must be given');
return;
}
if (!conf.remoteport) {
onLoaded('--remotep is mandatory');
return;
}
if (conf.sync.AMDaemon == "ON") {
if (!conf.sync.AMStart) {
onLoaded('--amstart is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.AMFreq) {
onLoaded('--amfreq is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.UDFreq) {
onLoaded('--udfreq is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.UD0) {
onLoaded('--ud0 is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.UDPercent) {
onLoaded('--udpercent is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.Consensus) {
onLoaded('--consensus is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.MSExpires) {
onLoaded('--msvalidity is mandatory when --amdaemon is set to ON');
return;
}
if (!conf.sync.VTExpires) {
onLoaded('--vtvalidity is mandatory when --amdaemon is set to ON');
return;
}
}
// Private key extraction
privateKey = openpgp.key.readArmored(conf.pgpkey).keys[0];
privateKey.decrypt(conf.pgppasswd);
// all environments
app.set('conf', conf);
......@@ -351,6 +363,7 @@ module.exports.express = {
logger.info('Connecting on interface %s...', conf.ipv6);
http.createServer(app).listen(conf.port, conf.ipv6, function(){
logger.info('uCoin server listening on ' + conf.ipv6 + ' port ' + conf.port);
next();
});
}
else next();
......
var server = require('./server');
var jpgp = require('./jpgp');
var wizard = require('./wizard');
var os = require('os');
var async = require('async');
var _ = require('underscore');
var inquirer = require('inquirer');
var openpgp = require('openpgp');
module.exports = function () {
return new Wizard();
}
var IPV4_REGEXP = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/;
var IPV6_REGEXP = /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(([0-9A-Fa-f]{1,4}:){0,5}:((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|(::([0-9A-Fa-f]{1,4}:){0,5}((b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b).){3}(b((25[0-5])|(1d{2})|(2[0-4]d)|(d{1,2}))b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/;
function Wizard () {
this.configAll = function (conf, done) {
doTasks(['currency', 'network', 'key', 'autovote'], conf, done);
};
this.configCurrency = function (conf, done) {
doTasks(['currency'], conf, done);
};
this.configNetwork = function (conf, done) {
doTasks(['network'], conf, done);
};
this.configKey = function (conf, done) {
doTasks(['key'], conf, done);
};
this.configAutovote = function (conf, done) {
doTasks(['autovote'], conf, done);
};
}
function doTasks (todos, conf, done) {
async.forEachSeries(todos, function(task, callback){
tasks[task] && tasks[task](conf, callback);
}, done);
}
var tasks = {
currency: function (conf, done) {
inquirer.prompt([{
type: "input",
name: "currency",
message: "Currency name",
default: conf.currency,
validate: function (input) {
return input.match(/^[a-zA-Z0-9-_ ]+$/) ? true : false;
}
}], function (answers) {
conf.currency = answers.currency;
done();
});
},
network: function (conf, done) {
var noInterfaceListened = true;
if (conf.ipv4 || conf.ipv6) {
noInterfaceListened = false;
}
async.waterfall([
function (next){
var osInterfaces = os.networkInterfaces();
var interfaces = [{ name: "None", value: null }];
_(osInterfaces).keys().forEach(function(interfaceName){
var addresses = osInterfaces[interfaceName];
var filtered = _(addresses).where({family: 'IPv4'});
filtered.forEach(function(addr){
interfaces.push({
name: [interfaceName, addr.address].join(' '),
value: addr.address
});
});
});
inquirer.prompt([{
type: "list",
name: "ipv4",
message: "IPv4 interface",
default: conf.ipv4,
choices: interfaces
}], function (answers) {
conf.ipv4 = answers.ipv4;
next();
});
},
function (next){
var osInterfaces = os.networkInterfaces();
var interfaces = [{ name: "None", value: null }];
_(osInterfaces).keys().forEach(function(interfaceName){
var addresses = osInterfaces[interfaceName];
var filtered = _(addresses).where({family: 'IPv6'});
filtered.forEach(function(addr){
interfaces.push({
name: [interfaceName, addr.address].join(' '),
value: addr.address
});
});
});
inquirer.prompt([{
type: "list",
name: "ipv6",
message: "IPv6 interface",
default: conf.ipv6,
choices: interfaces
}], function (answers) {
conf.ipv6 = answers.ipv6;
next();
});
},
async.apply(simpleInteger, "Port", "port", conf),
function (next){
var choices = [{ name: "None", value: null }];
if (conf.remoteipv4) {
choices.push({ name: conf.remoteipv4, value: conf.remoteipv4 });
}
choices.push({ name: "Enter new one", value: "new" });
inquirer.prompt([{
type: "list",
name: "remoteipv4",
message: "Remote IPv4",
default: conf.remoteipv4 || null,
choices: choices,
validate: function (input) {
return input && input.toString().match(IPV4_REGEXP) ? true : false;
}
}], function (answers) {
if (answers.remoteipv4 == "new") {
inquirer.prompt([{
type: "input",
name: "remoteipv4",
message: "Remote IPv4",
default: conf.remoteipv4 || conf.ipv4,
validate: function (input) {
return input && input.toString().match(IPV4_REGEXP) ? true : false;
}
}], async.apply(next, null));
} else {
next(null, answers);
}
});
},
function (answers, next){
conf.remoteipv4 = answers.remoteipv4;
var choices = [{ name: "None", value: null }];
if (conf.remoteipv6) {
choices.push({ name: conf.remoteipv6, value: conf.remoteipv6 });
}
choices.push({ name: "Enter new one", value: "new" });
inquirer.prompt([{
type: "list",
name: "remoteipv6",
message: "Remote IPv6",
default: conf.remoteipv6 || null,
choices: choices,
validate: function (input) {
return input && input.toString().match(IPV6_REGEXP) ? true : false;
}
}], function (answers) {
if (answers.remoteipv6 == "new") {
inquirer.prompt([{
type: "input",
name: "remoteipv6",
message: "Remote IPv6",
default: conf.remoteipv6 || conf.ipv6,
validate: function (input) {
return input && input.toString().match(IPV6_REGEXP) ? true : false;
}
}], function (answers) {
conf.remoteipv6 = answers.remoteipv6;
next();
});
} else {
next();
}
});
},
async.apply(simpleInteger, "Remote port", "remoteport", conf),
], done);
},
key: function (conf, done) {
var privateKey = conf.pgpkey && openpgp.key.readArmored(conf.pgpkey).keys[0]
var fingerprint = server.privateKey(privateKey) && server.fingerprint();
var privateKeys = [];
async.waterfall([
function (next){
var gpglistkeys = __dirname + '/gnupg/gpg-list-secret-keys.sh';
var exec = require('child_process').exec;
exec(gpglistkeys, next);
},
function (stdout, stderr, next){
var lines = stdout.split('\n');
var keys = {};
var sec = null;
lines.forEach(function(line){
var fpr = line.match(/(([A-F0-9]{4}[ ]*){10})/);
if (fpr) {
sec = fpr[1].replace(/\s/g, "");
}
var uid = line.match(/^uid[ ]+(.*)/);
if (uid) {
keys[sec] = uid[1];
}
});
_(keys).keys().forEach(function(fpr){
privateKeys.push({
name: keys[fpr],
value: fpr
});
});
inquirer.prompt([{
type: "list",
name: "pgpkey",
message: "Private key",
default: fingerprint,
choices: privateKeys
}], function (answers) {
next(null, answers.pgpkey);
});
},
function (chosenFPR, next) {
if (fingerprint == chosenFPR) {
next();
} else {
async.waterfall([
function (next){
var gpgexportsecret = __dirname + '/gnupg/gpg-export-secret-key.sh';
var exec = require('child_process').exec;
exec(gpgexportsecret + ' ' + chosenFPR, next);
},
function (stdout, stderr, next){
conf.pgpkey = stdout;
conf.pgppasswd = "";
next();
},
], next);
}
},
function (next) {
var privateKey = openpgp.key.readArmored(conf.pgpkey).keys[0];
if(privateKey && !privateKey.decrypt(conf.pgppasswd)) {
inquirer.prompt([{
type: "password",
name: "pgppasswd",
message: "Key\'s passphrase",
validate: function (input) {
return privateKey.decrypt(input);
}
}], function (answers) {
conf.pgppasswd = answers.pgppasswd;
next();
});
} else {
next();
}
}
], done);
},
autovote: function (conf, done) {
choose("Autovoting", conf.sync.AMDaemon ? conf.sync.AMDaemon == "ON" : false,
function enabled () {
conf.sync.AMDaemon = "ON";
async.waterfall([
async.apply(simpleInteger, "Amendment start", "AMStart", conf.sync),
async.apply(simpleInteger, "Amendment frequency", "AMFreq", conf.sync),
async.apply(simpleInteger, "Dividend frequency", "UDFreq", conf.sync),
async.apply(simpleFloat, "Consensus %required", "Consensus", conf.sync),
async.apply(simpleInteger, "Initial dividend", "UD0", conf.sync),
async.apply(simpleFloat, "Universal Dividend %growth", "UDPercent", conf.sync),
async.apply(simpleInteger, "Membership validity duration", "MSExpires", conf.sync),
async.apply(simpleInteger, "Voting request validity duration", "VTExpires", conf.sync),
], done);
},
function disabled () {
conf.sync.AMDaemon = "OFF";
done();
});
}
};
function choose (question, defaultValue, ifOK, ifNotOK) {
inquirer.prompt([{
type: "confirm",
name: "q",
message: question,
default: defaultValue,
}], function (answer) {
answer.q ? ifOK() : ifNotOK();
});
}
function simpleValue (question, property, defaultValue, conf, validation, done) {
inquirer.prompt([{
type: "input",
name: property,
message: question,
default: conf[property],
validate: validation
}], function (answers) {
conf[property] = answers[property];
done();
});
}
function simpleInteger (question, property, conf, done) {
simpleValue(question, property, conf[property], conf, function (input) {
return input && input.toString().match(/^[0-9]+$/) ? true : false;
}, done);
}
function simpleFloat (question, property, conf, done) {
simpleValue(question, property, conf[property], conf, function (input) {
return input && input.toString().match(/^[0-9]+(\.[0-9]+)?$/) ? true : false;
}, done);
}
\ No newline at end of file
#!/usr/bin/env node
var server = require('../app/lib/server');
var jpgp = require('../app/lib/jpgp');
var wizard = require('../app/lib/wizard');
var fs = require('fs');
var os = require('os');
var async = require('async');
var _ = require('underscore');
var program = require('commander');
var mongoose = require('mongoose');
var moment = require('moment');
var inquirer = require('inquirer');
var openpgp = require('openpgp');
var logger = require('../app/lib/logger')('ucoind');
var service = require('../app/service');
......@@ -43,6 +47,41 @@ program
.option('--vtvalidity <timestamp>', 'Duration of a valid voter, in seconds', parseInt)
;
program
.command('wizard [step]')
.description('Launch the configuration Wizard')
.action(connect(function (step, conf) {
var wiz = wizard();
var task = {
'currency': wiz.configCurrency,
'network': wiz.configNetwork,
'key': wiz.configKey,
'autovote': wiz.configAutovote
};
var wizDo = task[step] || wiz.configAll;
async.waterfall([
function (next){
wizDo(conf, next);
},
function (next){
conf.save(function (err) {
!err && logger.debug("Configuration saved.");
next(err);
});
},
function (next) {
server.checkConf(conf, function (err) {
logger.warn(err);
});
next();
}
], function (err, result) {
err && logger.error(err);
server.database.disconnect();
process.exit();
});
}));
program
.command('sync [host] [port]')
.description('Tries to synchronise data with remote uCoin node')
......@@ -129,6 +168,18 @@ function handleKey (conf, key, isManaged, message) {
});
}
program
.command('check-config')
.description('Checks the node\'s configuration')
.action(connect(function (conf) {
server.checkConf(conf, function (err) {
logger.warn(err);
});
server.database.disconnect();
process.exit();
return;
}));
program
.command('config')
.description('Register configuration in database')
......@@ -246,10 +297,10 @@ function overrideConf(conf) {
conf.port = cli.server.port || conf.port;
conf.pgpkey = cli.server.pgp.key || conf.pgpkey;
conf.pgppasswd = cli.server.pgp.password != undefined ? cli.server.pgp.password : conf.pgppasswd;
conf.remotehost = cli.server.remote.host != undefined ? cli.server.remote.host : (conf.remotehost