Let's Encrypt Improvements

Ylian Saint-Hilaire committed Nov 16, 2019 at 11:21 UTC 889d935d1a13aa5bc9aa5a7608fcc776e493b4be
2 files changed +115 -34
letsEncrypt.js
+114 -33
@@ -37,21 +37,20 @@ module.exports.CreateLetsEncrypt = function (parent) {
37 obj.redirWebServerHooked = false;
38 obj.leDomains = null;
39 obj.leResults = null;
40 - obj.performRestart = false;
40 + obj.leResultsStaging = null;
41 + obj.performRestart = false; // Indicates we need to restart the server
42 + obj.performMoveToProduction = false; // Indicates we just got a staging certificate and need to move to production
43 + obj.runAsProduction = false; // This starts at false and moves to true if staging cert is ok.
44
45 // Setup the certificate storage paths
46 obj.configPath = obj.path.join(obj.parent.datapath, 'letsencrypt3');
47 try { obj.parent.fs.mkdirSync(obj.configPath); } catch (e) { }
48 + obj.configPathStaging = obj.path.join(obj.parent.datapath, 'letsencrypt3-staging');
49 + try { obj.parent.fs.mkdirSync(obj.configPathStaging); } catch (e) { }
50
51 // Setup Let's Encrypt default configuration
47 - obj.leDefaults = {
48 - agreeToTerms: true,
49 - //serverKeyType: 'RSA-2048', // Seems like only "RSA-2048" or "P-256" is supported.
50 - store: {
51 - module: 'greenlock-store-fs',
52 - basePath: obj.configPath
53 - }
54 - };
52 + obj.leDefaults = { agreeToTerms: true, store: { module: 'greenlock-store-fs', basePath: obj.configPath } };
53 + obj.leDefaultsStaging = { agreeToTerms: true, store: { module: 'greenlock-store-fs', basePath: obj.configPathStaging } };
54
55 // Get package and maintainer email
56 const pkg = require('./package.json');
@@ -68,7 +67,7 @@ module.exports.CreateLetsEncrypt = function (parent) {
67 maintainerEmail = pkg.author.email;
68 }
69
71 - // Create the main GreenLock code module.
70 + // Create the main GreenLock code module for production.
71 var greenlockargs = {
72 parent: obj,
73 packageRoot: __dirname,
@@ -76,25 +75,47 @@ module.exports.CreateLetsEncrypt = function (parent) {
75 manager: obj.path.join(__dirname, 'letsencrypt.js'),
76 maintainerEmail: maintainerEmail,
77 notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', ev + ': ' + args); } else { parent.debug('cert', ev + ': ' + JSON.stringify(args)); } },
79 - staging: (obj.parent.config.letsencrypt.production !== true),
78 + staging: false,
79 debug: (obj.parent.args.debug > 0)
80 };
82 -
81 if (obj.parent.args.debug == null) { greenlockargs.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
82 obj.le = greenlock.create(greenlockargs);
83
84 + // Create the main GreenLock code module for staging.
85 + var greenlockargsstaging = {
86 + parent: obj,
87 + packageRoot: __dirname,
88 + packageAgent: pkg.name + '/' + pkg.version,
89 + manager: obj.path.join(__dirname, 'letsencrypt.js'),
90 + maintainerEmail: maintainerEmail,
91 + notify: function (ev, args) { if (typeof args == 'string') { parent.debug('cert', ev + ': ' + args); } else { parent.debug('cert', ev + ': ' + JSON.stringify(args)); } },
92 + staging: true,
93 + debug: (obj.parent.args.debug > 0)
94 + };
95 + if (obj.parent.args.debug == null) { greenlockargsstaging.log = function (debug) { }; } // If not in debug mode, ignore all console output from greenlock (makes things clean).
96 + obj.leStaging = greenlock.create(greenlockargsstaging);
97 +
98 // Hook up GreenLock to the redirection server
99 if (obj.parent.redirserver.port == 80) { obj.redirWebServerHooked = true; }
100
101 // Respond to a challenge
102 obj.challenge = function (token, hostname, func) {
91 - parent.debug('cert', "Challenge " + hostname + "/" + token);
92 - obj.le.challenges.get({ type: 'http-01', servername: hostname, token: token })
93 - .then(function (results) { func(results.keyAuthorization); })
94 - .catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
103 + if (obj.runAsProduction === true) {
104 + // Production
105 + parent.debug('cert', "Challenge " + hostname + "/" + token);
106 + obj.le.challenges.get({ type: 'http-01', servername: hostname, token: token })
107 + .then(function (results) { func(results.keyAuthorization); })
108 + .catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
109 + } else {
110 + // Staging
111 + parent.debug('cert', "Challenge " + hostname + "/" + token);
112 + obj.leStaging.challenges.get({ type: 'http-01', servername: hostname, token: token })
113 + .then(function (results) { func(results.keyAuthorization); })
114 + .catch(function (e) { console.log('LE-ERROR', e); func(null); }); // unexpected error, not related to renewal
115 + }
116 }
117
97 - obj.getCertificate = function (certs, func) {
118 + obj.getCertificate = function(certs, func) {
119 parent.debug('cert', "Getting certs from local store");
120 if (certs.CommonName.indexOf('.') == -1) { console.log("ERROR: Use --cert to setup the default server name before using Let's Encrypt."); func(certs); return; }
121 if (obj.parent.config.letsencrypt == null) { func(certs); return; }
@@ -112,10 +133,47 @@ module.exports.CreateLetsEncrypt = function (parent) {
133 obj.leDomains = obj.parent.config.letsencrypt.names;
134 }
135
136 + if (obj.parent.config.letsencrypt.production !== true) {
137 + // We are in staging mode, just go ahead
138 + obj.getCertificateEx(certs, func);
139 + } else {
140 + // We are really in production mode
141 + if (obj.runAsProduction === true) {
142 + // Staging cert check must have been done already, move to production
143 + obj.getCertificateEx(certs, func);
144 + } else {
145 + // Perform staging certificate check
146 + parent.debug('cert', "Checking staging certificate " + obj.leDomains[0] + "...");
147 + obj.leStaging.get({ servername: obj.leDomains[0] })
148 + .then(function (results) {
149 + if (results != null) {
150 + // We have a staging certificate, move to production for real
151 + parent.debug('cert', "Staging certificate is present, moving to production...");
152 + obj.runAsProduction = true;
153 + obj.getCertificateEx(certs, func);
154 + } else {
155 + // No staging certificate
156 + parent.debug('cert', "No staging certificate present");
157 + func(certs);
158 + setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
159 + }
160 + })
161 + .catch(function (e) {
162 + // No staging certificate
163 + parent.debug('cert', "No staging certificate present");
164 + func(certs);
165 + setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
166 + });
167 + }
168 + }
169 + }
170 +
171 + obj.getCertificateEx = function (certs, func) {
172 // Get the Let's Encrypt certificate from our own storage
116 - obj.le.get({ servername: obj.leDomains[0] })
173 + const xle = (obj.runAsProduction === true)? obj.le : obj.leStaging;
174 + xle.get({ servername: obj.leDomains[0] })
175 .then(function (results) {
118 - // If we already have real certificates, use them.
176 + // If we already have real certificates, use them
177 if (results) {
178 if (results.site.altnames.indexOf(certs.CommonName) >= 0) {
179 certs.web.cert = results.pems.cert;
@@ -130,7 +188,7 @@ module.exports.CreateLetsEncrypt = function (parent) {
188 }
189 }
190 }
133 - parent.debug('cert', "Got certs from local store");
191 + parent.debug('cert', "Got certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
192 func(certs);
193
194 // Check if the Let's Encrypt certificate needs to be renewed.
@@ -139,7 +197,7 @@ module.exports.CreateLetsEncrypt = function (parent) {
197 return;
198 })
199 .catch(function (e) {
142 - parent.debug('cert', "Unable to get certs from local store");
200 + parent.debug('cert', "Unable to get certs from local store (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
201 setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
202 func(certs);
203 });
@@ -147,15 +205,23 @@ module.exports.CreateLetsEncrypt = function (parent) {
205
206 // Check if we need to renew the certificate, call this every day.
207 obj.checkRenewCertificate = function () {
150 - parent.debug('cert', "Checking certs");
208 + parent.debug('cert', "Checking certs (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
209
210 // Setup renew options
211 var renewOptions = { servername: obj.leDomains[0] };
212 if (obj.leDomains.length > 0) { renewOptions.altnames = obj.leDomains; }
155 - obj.le.renew(renewOptions)
213 + const xle = (obj.runAsProduction === true)? obj.le : obj.leStaging;
214 + xle.renew(renewOptions)
215 .then(function (results) {
157 - parent.debug('cert', "Checks completed");
216 + parent.debug('cert', "Checks completed (" + (obj.runAsProduction ? "Production" : "Staging") + ")");
217 if (obj.performRestart === true) { parent.debug('cert', "Certs changed, restarting..."); obj.parent.performServerCertUpdate(); } // Reset the server, TODO: Reset all peers
218 + else if (obj.performMoveToProduction == true) {
219 + parent.debug('cert', "Staging certificate received, moving to production...");
220 + obj.runAsProduction = true;
221 + obj.performMoveToProduction = false;
222 + obj.performRestart = true;
223 + setTimeout(obj.checkRenewCertificate, 10000); // Check the certificate in 10 seconds.
224 + }
225 })
226 .catch(function (e) { console.log(e); });
227 }
@@ -175,25 +241,40 @@ module.exports.create = function (options) {
241
242 manager.set = function (options) {
243 manager.parent.parent.debug('cert', "Certificate has been set");
178 - manager.parent.performRestart = true;
244 + if (manager.parent.parent.config.letsencrypt.production == manager.parent.runAsProduction) { manager.parent.performRestart = true; }
245 + else if ((manager.parent.parent.config.letsencrypt.production === true) && (manager.parent.runAsProduction === false)) { manager.parent.performMoveToProduction = true; }
246 return null;
247 };
248
249 manager.remove = function (options) {
250 manager.parent.parent.debug('cert', "Certificate has been removed");
184 - manager.parent.performRestart = true;
251 + if (manager.parent.parent.config.letsencrypt.production == manager.parent.runAsProduction) { manager.parent.performRestart = true; }
252 + else if ((manager.parent.parent.config.letsencrypt.production === true) && (manager.parent.runAsProduction === false)) { manager.parent.performMoveToProduction = true; }
253 return null;
254 };
255
256 // set the global config
257 manager.defaults = async function (options) {
190 - //console.log('LE-DEFAULTS', options);
191 - if (options != null) { for (var i in options) { if (manager.parent.leDefaults[i] == null) { manager.parent.leDefaults[i] = options[i]; } } }
192 - var r = manager.parent.leDefaults;
193 - var mainsite = { subject: manager.parent.leDomains[0] };
194 - if (manager.parent.leDomains.length > 0) { mainsite.altnames = manager.parent.leDomains; }
195 - r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
196 - r.sites = { mainsite: mainsite };
258 + var r;
259 + if (manager.parent.runAsProduction === true) {
260 + // Production
261 + //console.log('LE-DEFAULTS-Production', options);
262 + if (options != null) { for (var i in options) { if (manager.parent.leDefaults[i] == null) { manager.parent.leDefaults[i] = options[i]; } } }
263 + r = manager.parent.leDefaults;
264 + var mainsite = { subject: manager.parent.leDomains[0] };
265 + if (manager.parent.leDomains.length > 0) { mainsite.altnames = manager.parent.leDomains; }
266 + r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
267 + r.sites = { mainsite: mainsite };
268 + } else {
269 + // Staging
270 + //console.log('LE-DEFAULTS-Staging', options);
271 + if (options != null) { for (var i in options) { if (manager.parent.leDefaultsStaging[i] == null) { manager.parent.leDefaultsStaging[i] = options[i]; } } }
272 + r = manager.parent.leDefaultsStaging;
273 + var mainsite = { subject: manager.parent.leDefaultsStaging[0] };
274 + if (manager.parent.leDefaultsStaging.length > 0) { mainsite.altnames = manager.parent.leDefaultsStaging; }
275 + r.subscriberEmail = manager.parent.parent.config.letsencrypt.email;
276 + r.sites = { mainsite: mainsite };
277 + }
278 return r;
279 };
280
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.4.4-e",
3 + "version": "0.4.4-f",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",