1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-03-09 15:40:18 +00:00

Added built-in Let's Encrypt support using GreenLock.

This commit is contained in:
Ylian Saint-Hilaire 2018-01-14 21:01:06 -08:00
parent f6ef228de6
commit 65d6775303
10 changed files with 285 additions and 186 deletions

View file

@ -1,81 +1,118 @@
/**
* @description MeshCentral letsEncrypt module
* @description MeshCentral letsEncrypt module, uses GreenLock to do all the work.
* @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018
* @license Apache-2.0
* @version v0.0.1
* @version v0.0.2
*/
module.exports.CreateLetsEncrypt = function (parent) {
var obj = {};
obj.parent = parent;
obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges');
obj.workPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges', 'work');
obj.logsPath = obj.parent.path.join(obj.parent.datapath, 'acme-challenges', 'logs');
try {
const greenlock = require('greenlock');;
const path = require('path');
try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { }
try { obj.parent.fs.mkdirSync(obj.workPath); } catch (e) { }
try { obj.parent.fs.mkdirSync(obj.logsPath); } catch (e) { }
var obj = {};
obj.parent = parent;
obj.redirWebServerHooked = false;
obj.leDomains = null;
obj.leResults = null;
console.log('CreateLetsEncrypt-1', obj.webrootPath);
console.log('CreateLetsEncrypt-1', obj.workPath);
console.log('CreateLetsEncrypt-1', obj.logsPath);
// Setup the certificate storage paths
obj.configPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt');
obj.webrootPath = obj.parent.path.join(obj.parent.datapath, 'letsencrypt', 'webroot');
try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { }
try { obj.parent.fs.mkdirSync(obj.webrootPath); } catch (e) { }
obj.lex = require('greenlock-express').create({
// Set to https://acme-v01.api.letsencrypt.org/directory in production
server: 'staging'
// Storage Backend, store data in the "meshcentral-data/letencrypt" folder.
var leStore = require('le-store-certbot').create({ configDir: obj.configPath, webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 });
// If you wish to replace the default plugins, you may do so here
, challenges: {
'http-01': require('le-challenge-fs').create({ webrootPath: obj.webrootPath })
// ACME Challenge Handlers
var leHttpChallenge = require('le-challenge-fs').create({ webrootPath: obj.webrootPath, debug: obj.parent.args.debug > 0 });
// Function to agree to terms of service
function leAgree(opts, agreeCb) { agreeCb(null, opts.tosUrl); }
// Create the main GreenLock code module.
var greenlockargs = {
server: (obj.parent.config.letsencrypt.production === true) ? greenlock.productionServerUrl : greenlock.stagingServerUrl,
store: leStore,
challenges: { 'http-01': leHttpChallenge },
challengeType: 'http-01',
agreeToTerms: leAgree,
debug: obj.parent.args.debug > 0
}
, store: require('le-store-certbot').create({
//configDir: '/etc/letsencrypt',
//privkeyPath: ':configDir/live/:hostname/privkey.pem',
//fullchainPath: ':configDir/live/:hostname/fullchain.pem',
//certPath: ':configDir/live/:hostname/cert.pem',
//chainPath: ':configDir/live/:hostname/chain.pem',
workDir: obj.workPath,
logsDir: obj.logsPath,
webrootPath: obj.webrootPath,
debug: false
})
, approveDomains: approveDomains
});
if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { } } // If not in debug mode, ignore all console output from greenlock (makes things clean).
obj.le = greenlock.create(greenlockargs);
console.log('CreateLetsEncrypt-2');
function approveDomains(opts, certs, func) {
console.log('approveDomains', opts, certs);
// Hook up GreenLock to the redirection server
if (obj.parent.redirserver.port == 80) { obj.parent.redirserver.app.use('/', obj.le.middleware()); obj.redirWebServerHooked = true; }
// This is where you check your database and associated
// email addresses with domains and agreements and such
obj.getCertificate = function (certs, func) {
if (certs.CommonName == 'un-configured') { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; }
if (obj.parent.config.letsencrypt == null) { func(certs); return; }
if (obj.parent.config.letsencrypt.email == null) { console.log("ERROR: Let's Encrypt email address not specified."); func(certs); return; }
if ((obj.parent.redirserver == null) || (obj.parent.redirserver.port !== 80)) { console.log("ERROR: Redirection web server must be active on port 80 for Let's Encrypt to work."); func(certs); return; }
if (obj.redirWebServerHooked !== true) { console.log("ERROR: Redirection web server not setup for Let's Encrypt to work."); func(certs); return; }
if ((obj.parent.config.letsencrypt.rsakeysize != null) && (obj.parent.config.letsencrypt.rsakeysize !== 2048) && (obj.parent.config.letsencrypt.rsakeysize !== 3072)) { console.log("ERROR: Invalid Let's Encrypt certificate key size, must be 2048 or 3072."); func(certs); return; }
// Get the list of domains
obj.leDomains = [certs.CommonName];
if (obj.parent.config.letsencrypt.names != null) {
if (typeof obj.parent.config.letsencrypt.names == 'string') { obj.parent.config.letsencrypt.names = obj.parent.config.letsencrypt.names.split(','); }
obj.parent.config.letsencrypt.names.map(function (s) { return s.trim() }); // Trim each name
if ((typeof obj.parent.config.letsencrypt.names != 'object') || (obj.parent.config.letsencrypt.names.length == null)) { console.log("ERROR: Let's Encrypt names must be an array in config.json."); func(certs); return; }
obj.leDomains = obj.parent.config.letsencrypt.names;
obj.leDomains.sort(); // Sort the array so it's always going to be in the same order.
}
// The domains being approved for the first time are listed in opts.domains
// Certs being renewed are listed in certs.altnames
if (certs) {
opts.domains = ['example.com', 'yourdomain.com']
} else {
opts.email = 'john.doe@example.com';
opts.agreeTos = true;
obj.le.check({ domains: obj.leDomains }).then(function (results) {
if (results) {
obj.leResults = results;
// If we already have real certificates, use them.
if (results.altnames.indexOf(certs.CommonName) >= 0) { certs.web.cert = results.cert; certs.web.key = results.privkey; certs.web.ca = [results.chain]; }
for (var i in obj.parent.config.domains) { if ((obj.parent.config.domains[i].dns != null) && (results.altnames.indexOf(obj.parent.config.domains[i].dns) >= 0)) { certs.dns[i].cert = results.cert; certs.dns[i].key = results.privkey; certs.dns[i].ca = [results.chain]; } }
func(certs);
// Check if the Let's Encrypt certificate needs to be renewed.
setTimeout(obj.checkRenewCertificate, 300000); // Check in 5 minutes.
setInterval(obj.checkRenewCertificate, 86400000); // Check again in 24 hours and every 24 hours.
return;
} else {
// Otherwise return default certificates and try to get a real one
func(certs);
}
console.log("Attempting to get Let's Encrypt certificate, may take a few minutes...");
// Figure out the RSA key size
var rsaKeySize = (obj.parent.config.letsencrypt.rsakeysize === 2048) ? 2048 : 3072;
// TODO: Only register on one of the peers if multi-peers are active.
// Register Certificate manually
obj.le.register({
domains: obj.leDomains,
email: obj.parent.config.letsencrypt.email,
agreeTos: true,
rsaKeySize: rsaKeySize,
challengeType: 'http-01'
}).then(function (xresults) {
obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers
}, function (err) {
console.error("ERROR: Let's encrypt error: ", err);
});
});
}
// NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({});
// Check if we need to renew the certificate, call this every day.
obj.checkRenewCertificate = function () {
if (obj.leResults == null) { return; }
// TODO: Only renew on one of the peers if multi-peers are active.
// Check if we need to renew the certificate
obj.le.renew({ duplicate: false }, obj.leResults).then(function (xresults) {
obj.parent.performServerCertUpdate(); // Reset the server, TODO: Reset all peers
}, function (err) { }); // If we can't renew, ignore.
}
func(null, { options: opts, certs: certs });
}
// Handles acme-challenge and redirects to https
require('http').createServer(obj.lex.middleware(require('redirect-https')())).listen(81, function () { console.log("Listening for ACME http-01 challenges on", this.address()); });
var app = require('express')();
app.use('/', function (req, res) { res.end('Hello, World!'); });
// Handles your app
require('https').createServer(obj.lex.httpsOptions, obj.lex.middleware(app)).listen(443, function () { console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); });
console.log('CreateLetsEncrypt-3');
} catch (e) { console.error(e); return null; } // Unable to start Let's Encrypt
return obj;
}