more passing
This commit is contained in:
		
							parent
							
								
									3f1c669ef7
								
							
						
					
					
						commit
						f227a3ea6c
					
				
							
								
								
									
										18
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								README.md
									
									
									
									
									
								
							| @ -102,6 +102,24 @@ LEX.createSniCallback(opts)     // this will call letsencrypt.renew and letsencr | ||||
|   , memorizeFor: <1 day>        // how long to wait before checking the disk for updated certificates | ||||
|   , renewWithin: <3 days>       // the first possible moment the certificate staggering should begin | ||||
|   , failedWait:  <5 minutes>    // how long to wait before trying again if the certificate registration failed | ||||
| 
 | ||||
| 
 | ||||
|                                 // registrations are NOT approved automatically by default due to security concerns | ||||
|   , approveRegistration: func   // (someone can spoof servername indication to your server and cause you to be rate-limited) | ||||
|                                 // but you can implement handling of them if you wish | ||||
|                                 // (note that you should probably call the callback immediately with a tlsContext) | ||||
|                                 // | ||||
|                                 // default    function (hostname, cb) { cb(null, null); } | ||||
|                                 // | ||||
|                                 // example    function (hostname, cb) { | ||||
|                                 //              cb(null, { domains: [hostname], agreeTos: true, email: 'user@example.com' }); | ||||
|                                 //            } | ||||
| 
 | ||||
| 
 | ||||
|   , handleRenewFailure: func    // renewals are automatic, but sometimes they may fail. If that happens, you should handle it | ||||
|                                 // (note that renewals happen in the background) | ||||
|                                 // | ||||
|                                 // default    function (err, letsencrypt, hostname, certInfo) {} | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| //var le = require('letsencrypt-express');
 | ||||
| var le = require('../'); | ||||
| var le = require('../').testing(); | ||||
| var express = require('express'); | ||||
| var app = express(); | ||||
| 
 | ||||
| @ -9,4 +9,4 @@ app.use(function (req, res) { | ||||
|   res.send({ success: true }); | ||||
| }); | ||||
| 
 | ||||
| le.create(app).listen([80], [443, 5001]); | ||||
| le.create('./letsencrypt.config', app).listen([80], [443, 5001]); | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| var fs = require('fs'); | ||||
| var path = require('path'); | ||||
| var mkdirp = require('mkdirp'); | ||||
| 
 | ||||
| // TODO handle templating :hostname in letsencrypt proper
 | ||||
| 
 | ||||
| @ -10,15 +11,36 @@ var path = require('path'); | ||||
| 
 | ||||
| module.exports = { | ||||
|   set: function setChallenge(args, hostname, key, value, cb) { | ||||
|     var keyfile = path.join((args.webrootPath || args.webrootTpl).replace(':hostname', hostname), key); | ||||
|     var webrootPath = (args.webrootPath || args.webrootTpl).replace(':hostname', hostname); | ||||
|     var keyfile = path.join(webrootPath, key); | ||||
| 
 | ||||
|     fs.writeFile(keyfile, value, 'utf8', cb); | ||||
|     if (args.debug) { | ||||
|       console.log('[LEX] write file', hostname, webrootPath, key); | ||||
|     } | ||||
|     fs.writeFile(keyfile, value, 'utf8', function (err) { | ||||
|       if (!err) { cb(null); return; } | ||||
| 
 | ||||
| 
 | ||||
|       if (args.debug) { | ||||
|         console.log('[LEX] mkdirp', webrootPath); | ||||
|       } | ||||
|       mkdirp(webrootPath, function () { | ||||
|         if (err) { cb(err); return; } | ||||
| 
 | ||||
|         fs.writeFile(keyfile, value, 'utf8', cb); | ||||
|       }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , get: function getChallenge(args, hostname, key, cb) { | ||||
|     var keyfile = path.join((args.webrootPath || args.webrootTpl).replace(':hostname', hostname), key); | ||||
| 
 | ||||
|     fs.readFile(keyfile, 'utf8', cb); | ||||
|     if (args.debug) { | ||||
|       console.log('[LEX] getChallenge', hostname, key); | ||||
|     } | ||||
|     fs.readFile(keyfile, 'utf8', function (err, text) { | ||||
|       cb(null, text); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| , remove: function removeChallenge(args, hostname, key, cb) { | ||||
| @ -26,6 +48,12 @@ module.exports = { | ||||
| 
 | ||||
|     // Note: it's not actually terribly important that we wait for the unlink callback
 | ||||
|     // but it's a polite thing to do - and we're polite people!
 | ||||
|     fs.unlink(keyfile, cb); | ||||
|     if (args.debug) { | ||||
|       console.log('[LEX] removeChallenge', hostname, key); | ||||
|     } | ||||
|     fs.unlink(keyfile, function (err) { | ||||
|       if (err) { console.warn(err.stack); } | ||||
|       cb(null); | ||||
|     }); | ||||
|   } | ||||
| }; | ||||
|  | ||||
| @ -3,19 +3,22 @@ | ||||
| var crypto = require('crypto'); | ||||
| var tls = require('tls'); | ||||
| 
 | ||||
| module.exports.create = function (memos) { | ||||
| module.exports.create = function (opts) { | ||||
|   if (opts.debug) { | ||||
|     console.log("[LEX] creating sniCallback", JSON.stringify(opts, null, '  ')); | ||||
|   } | ||||
|   var ipc = {}; // in-process cache
 | ||||
| 
 | ||||
|   if (!memos) { throw new Error("requires opts to be an object"); } | ||||
|   if (!memos.letsencrypt) { throw new Error("requires opts.letsencrypt to be a letsencrypt instance"); } | ||||
|   if (!opts) { throw new Error("requires opts to be an object"); } | ||||
|   if (!opts.letsencrypt) { throw new Error("requires opts.letsencrypt to be a letsencrypt instance"); } | ||||
| 
 | ||||
|   if (!memos.lifetime) { memos.lifetime = 90 * 24 * 60 * 60 * 1000; } | ||||
|   if (!memos.failedWait) { memos.failedWait = 5 * 60 * 1000; } | ||||
|   if (!memos.renewWithin) { memos.renewWithin = 3 * 24 * 60 * 60 * 1000; } | ||||
|   if (!memos.memorizeFor) { memos.memorizeFor = 1 * 24 * 60 * 60 * 1000; } | ||||
|   if (!opts.lifetime) { opts.lifetime = 90 * 24 * 60 * 60 * 1000; } | ||||
|   if (!opts.failedWait) { opts.failedWait = 5 * 60 * 1000; } | ||||
|   if (!opts.renewWithin) { opts.renewWithin = 3 * 24 * 60 * 60 * 1000; } | ||||
|   if (!opts.memorizeFor) { opts.memorizeFor = 1 * 24 * 60 * 60 * 1000; } | ||||
| 
 | ||||
|   if (!memos.handleRegistration) { memos.handleRegistration = function (args, cb) { cb(null, null); }; } | ||||
|   if (!memos.handleRenewFailure) { memos.handleRenewFailure = function () {}; } | ||||
|   if (!opts.approveRegistration) { opts.approveRegistration = function (hostname, cb) { cb(null, null); }; } | ||||
|   if (!opts.handleRenewFailure) { opts.handleRenewFailure = function (/*err, hostname, certInfo*/) {}; } | ||||
| 
 | ||||
|   function assignBestByDates(now, certInfo) { | ||||
|     certInfo = certInfo || { loadedAt: now, expiresAt: 0, issuedAt: 0, lifetime: 0 }; | ||||
| @ -26,10 +29,10 @@ module.exports.create = function (memos) { | ||||
|     var rnd3 = ((rnds[2] + 1) / 257); | ||||
| 
 | ||||
|     // Stagger randomly by plus 0% to 25% to prevent all caches expiring at once
 | ||||
|     var memorizeFor = Math.floor(memos.memorizeFor + ((memos.memorizeFor / 4) * rnd1)); | ||||
|     var memorizeFor = Math.floor(opts.memorizeFor + ((opts.memorizeFor / 4) * rnd1)); | ||||
|     // Stagger randomly to renew between n and 2n days before renewal is due
 | ||||
|     // this *greatly* reduces the risk of multiple cluster processes renewing the same domain at once
 | ||||
|     var bestIfUsedBy = certInfo.expiresAt - (memos.renewWithin + Math.floor(memos.renewWithin * rnd2)); | ||||
|     var bestIfUsedBy = certInfo.expiresAt - (opts.renewWithin + Math.floor(opts.renewWithin * rnd2)); | ||||
|     // Stagger randomly by plus 0 to 5 min to reduce risk of multiple cluster processes
 | ||||
|     // renewing at once on boot when the certs have expired
 | ||||
|     var renewTimeout = Math.floor((5 * 60 * 1000) * rnd3); | ||||
| @ -41,7 +44,7 @@ module.exports.create = function (memos) { | ||||
|   } | ||||
| 
 | ||||
|   function renewInBackground(now, hostname, certInfo) { | ||||
|     if ((now - certInfo.loadedAt) < memos.failedWait) { | ||||
|     if ((now - certInfo.loadedAt) < opts.failedWait) { | ||||
|       // wait a few minutes
 | ||||
|       return; | ||||
|     } | ||||
| @ -53,11 +56,16 @@ module.exports.create = function (memos) { | ||||
|         certInfo.renewTimeout = Math.floor(certInfo.renewTimeout / 2); | ||||
|       } | ||||
| 
 | ||||
|       if (opts.debug) { | ||||
|         console.log("[LEX] skipping stagger '" + certInfo.renewTimeout + "' and renewing '" + hostname + "' now"); | ||||
|         certInfo.renewTimeout = 500; | ||||
|       } | ||||
| 
 | ||||
|       certInfo.timeout = setTimeout(function () { | ||||
|         var opts = { domains: [ hostname ], duplicate: false }; | ||||
|         le.renew(opts, function (err, certInfo) { | ||||
|         var args = { domains: [ hostname ], duplicate: false }; | ||||
|         opts.letsencrypt.renew(args, function (err, certInfo) { | ||||
|           if (err || !certInfo) { | ||||
|             memos.handleRenewFailure(err, certInfo, opts); | ||||
|             opts.handleRenewFailure(err, hostname, certInfo); | ||||
|           } | ||||
|           ipc[hostname] = assignBestByDates(now, certInfo); | ||||
|         }); | ||||
| @ -66,36 +74,70 @@ module.exports.create = function (memos) { | ||||
|   } | ||||
| 
 | ||||
|   function fetch(hostname, cb) { | ||||
|     le.fetch({ domains: [hostname] }, function (err, certInfo) { | ||||
|       var now = Date.now(); | ||||
| 
 | ||||
|       ipc[hostname] = assignBestByDates(now, certInfo); | ||||
|       if (!certInfo) { | ||||
|         // handles registration
 | ||||
|         memos.handleRegistration(hostname, cb); | ||||
|         return; | ||||
|     opts.letsencrypt.fetch({ domains: [hostname] }, function (err, certInfo) { | ||||
|       if (opts.debug) { | ||||
|         console.log("[LEX] fetch result '" + hostname + "':"); | ||||
|         console.log(err, certInfo); | ||||
|       } | ||||
| 
 | ||||
|       // handles renewals
 | ||||
|       renewInBackground(now, hostname, certInfo); | ||||
| 
 | ||||
|       if (err) { | ||||
|         cb(err); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       try { | ||||
|         certInfo.tlsContext = tls.createSecureContext({ | ||||
|           key: certInfo.key                             // privkey.pem
 | ||||
|         , cert: certInfo.cert                           // fullchain.pem (cert.pem + '\n' + chain.pem)
 | ||||
|       var now = Date.now(); | ||||
| 
 | ||||
|       if (!certInfo) { | ||||
|         // handles registration
 | ||||
|         if (opts.debug) { | ||||
|           console.log("[LEX] '" + hostname + "' is not registered, requesting approval"); | ||||
|         } | ||||
|         opts.approveRegistration(hostname, function (err, args) { | ||||
|           if (opts.debug) { | ||||
|             console.log("[LEX] '" + hostname + "' registration approved, attempting register"); | ||||
|           } | ||||
|           if (err || !(args && args.agreeTos)) { | ||||
|             done(err, certInfo); | ||||
|             return; | ||||
|           } | ||||
|           opts.letsencrypt.register(args, function (err, certInfo) { | ||||
|             if (opts.debug) { | ||||
|               console.log("[LEX] '" + hostname + "' register completed", err, certInfo); | ||||
|             } | ||||
|             done(err, certInfo); | ||||
|           }); | ||||
|         }); | ||||
|       } catch(e) { | ||||
|         console.warn("[Sanity Check Fail]: a weird object was passed back through le.fetch to lex.fetch"); | ||||
|         cb(e); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       cb(null, certInfo.tlsContext); | ||||
|       done(err, certInfo); | ||||
| 
 | ||||
|       function done(err, certInfo) { | ||||
|         ipc[hostname] = assignBestByDates(now, certInfo); | ||||
| 
 | ||||
|         // handles renewals
 | ||||
|         renewInBackground(now, hostname, certInfo); | ||||
| 
 | ||||
|         if (err) { | ||||
|           cb(err); | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         if (!certInfo.tlsContext && null !== certInfo.tlsContext) { | ||||
|           try { | ||||
|             certInfo.tlsContext = tls.createSecureContext({ | ||||
|               key: certInfo.key                             // privkey.pem
 | ||||
|             , cert: certInfo.cert                           // fullchain.pem (cert.pem + '\n' + chain.pem)
 | ||||
|             }); | ||||
|           } catch(e) { | ||||
|             certInfo.tlsContext = null; | ||||
|             console.warn("[Sanity Check Fail]: a weird object was passed back through le.fetch to lex.fetch"); | ||||
|             cb(e); | ||||
|             return; | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         cb(null, certInfo.tlsContext); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -105,24 +147,36 @@ module.exports.create = function (memos) { | ||||
| 
 | ||||
|     // TODO once ECDSA is available, wait for cert renewal if its due
 | ||||
|     if (!certInfo) { | ||||
|       if (opts.debug) { | ||||
|         console.log("[LEX] no certs loaded for '" + hostname + "'"); | ||||
|       } | ||||
|       fetch(hostname, cb); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (certInfo.context) { | ||||
|       cb(null, certInfo.context); | ||||
|     if (certInfo.tlsContext) { | ||||
|       cb(null, certInfo.tlsContext); | ||||
| 
 | ||||
|       if ((now - certInfo.loadedAt) < (certInfo.memorizeFor)) { | ||||
|         // these aren't stale, so don't fall through
 | ||||
|         if (opts.debug) { | ||||
|           console.log("[LEX] certs for '" + hostname + "' are fresh from disk"); | ||||
|         } | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     else if ((now - certInfo.loadedAt) < memos.failedWait) { | ||||
|     else if ((now - certInfo.loadedAt) < opts.failedWait) { | ||||
|       if (opts.debug) { | ||||
|         console.log("[LEX] certs for '" + hostname + "' recently failed and are still in cool down"); | ||||
|       } | ||||
|       // this was just fetched and failed, wait a few minutes
 | ||||
|       cb(null, null); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     fetch({ domains: [hostname] }, cb); | ||||
|     if (opts.debug) { | ||||
|       console.log("[LEX] certs for '" + hostname + "' are stale on disk and should be will be fetched again"); | ||||
|     } | ||||
|     fetch(hostname, cb); | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| var path = require('path'); | ||||
| var challengeStore = require('./lib/challange-handlers'); | ||||
| var createSniCallback = require('./lib/sni-callback').create; | ||||
| var challengeStore = require('./challenge-handlers'); | ||||
| var createSniCallback = require('./sni-callback').create; | ||||
| var LE = require('letsencrypt'); | ||||
| 
 | ||||
| function LEX(obj, app) { | ||||
| @ -26,6 +26,8 @@ function LEX(obj, app) { | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   obj.debug = LEX.debug; | ||||
|    | ||||
|   if ('function' === typeof app) { | ||||
|     obj.onRequest = obj.onRequest || app; | ||||
|   } | ||||
| @ -63,6 +65,9 @@ function LEX(obj, app) { | ||||
|   } | ||||
| 
 | ||||
|   function acmeResponder(req, res) { | ||||
|     if (LEX.debug) { | ||||
|       console.log('[LEX] ', req.method, req.headers.host, req.url); | ||||
|     } | ||||
|     var acmeChallengePrefix = '/.well-known/acme-challenge/'; | ||||
| 
 | ||||
|     if (0 !== req.url.indexOf(acmeChallengePrefix)) { | ||||
| @ -73,6 +78,9 @@ function LEX(obj, app) { | ||||
|     var key = req.url.slice(acmeChallengePrefix.length); | ||||
| 
 | ||||
|     obj.getChallenge(obj, req.headers.host, key, function (err, val) { | ||||
|       if (LEX.debug) { | ||||
|         console.log('[LEX] challenge response:', key, err, val); | ||||
|       } | ||||
|       res.end(val || '_'); | ||||
|     }); | ||||
|   } | ||||
| @ -98,6 +106,20 @@ function LEX(obj, app) { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (!obj.approveRegistration && LEX.defaultApproveRegistration) { | ||||
|     obj.approveRegistration = function (domain, cb) { | ||||
|       if (LEX.debug) { | ||||
|         console.log('[LEX] auto register against staging server'); | ||||
|       } | ||||
|       cb(null, { | ||||
|         email: 'example@gmail.com' | ||||
|       , domains: [domain] | ||||
|       , agreeTos: true | ||||
|       , server: LEX.stagingServerUrl | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   if (obj.sniCallback) { | ||||
|     if (sniCallback) { | ||||
|       console.warn("You specified both args.sniCallback and args.httpsOptions.SNICallback," | ||||
| @ -108,6 +130,7 @@ function LEX(obj, app) { | ||||
|   else if (sniCallback) { | ||||
|     obj._sniCallback = createSniCallback(obj); | ||||
|     httpsOptions.SNICallback = function (domain, cb) { | ||||
|       console.log('[LEX] auto register against staging server'); | ||||
|       sniCallback(domain, function (err, context) { | ||||
|         if (context) { | ||||
|           cb(err, context); | ||||
| @ -146,7 +169,6 @@ function LEX(obj, app) { | ||||
|       console.info('Listening ' + protocol + '://' + addr.address + port + '/'); | ||||
|     } | ||||
| 
 | ||||
|     console.log(plainPorts); | ||||
|     plainPorts.forEach(function (addr) { | ||||
|       var port = addr.port || addr; | ||||
|       var address = addr.address || ''; | ||||
| @ -203,6 +225,12 @@ LEX.stagingServerUrl = LE.stagingServerUrl; | ||||
| LEX.productionServerUrl = LE.productionServerUrl || LE.liveServerUrl; | ||||
| LEX.defaultServerUrl = LEX.productionServerUrl; | ||||
| LEX.testing = function () { | ||||
|   LEX.debug = true; | ||||
|   LEX.defaultServerUrl = LEX.stagingServerUrl; | ||||
|   return module.expotrs; | ||||
|   LEX.defaultApproveRegistration = true; | ||||
|   console.log('[LEX] testing mode turned on'); | ||||
|   console.log('[LEX] default server: ' + LEX.defaultServerUrl); | ||||
|   console.log('[LEX] automatic registration handling turned on for testing.'); | ||||
| 
 | ||||
|   return module.exports; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user