121 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			121 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | module.exports.create = function (lebinpath, defaults, options) { | ||
|  |   var PromiseA = require('bluebird'); | ||
|  |   var tls = require('tls'); | ||
|  |   var fs = PromiseA.promisifyAll(require('fs')); | ||
|  |   var letsencrypt = PromiseA.promisifyAll(require('./le-exec-wrapper')); | ||
|  | 
 | ||
|  |   //var attempts = {};  // should exist in master process only
 | ||
|  |   var ipc = {};       // in-process cache
 | ||
|  |   var count = 0; | ||
|  | 
 | ||
|  |   //var certTpl = "/live/:hostname/cert.pem";
 | ||
|  |   var certTpl = "/live/:hostname/fullchain.pem"; | ||
|  |   var privTpl = "/live/:hostname/privkey.pem"; | ||
|  | 
 | ||
|  |   options.cacheContextsFor = options.cacheContextsFor || (1 * 60 * 60 * 1000); | ||
|  | 
 | ||
|  |   defaults.webroot = true; | ||
|  |   defaults.webrootPath = '/srv/www/acme-challenge'; | ||
|  | 
 | ||
|  |   return letsencrypt.optsAsync(lebinpath).then(function (keys) { | ||
|  |     var now; | ||
|  |     var le; | ||
|  | 
 | ||
|  |     le = { | ||
|  |       validate: function () { | ||
|  |       } | ||
|  |     , argnames: keys | ||
|  |     , readCerts: function (hostname) { | ||
|  |         var crtpath = defaults.configDir + certTpl.replace(/:hostname/, hostname); | ||
|  |         var privpath = defaults.configDir + privTpl.replace(/:hostname/, hostname); | ||
|  | 
 | ||
|  |         return PromiseA.all([ | ||
|  |           fs.readFileAsync(privpath, 'ascii') | ||
|  |         , fs.readFileAsync(crtpath, 'ascii') | ||
|  |           // stat the file, not the link
 | ||
|  |         , fs.statAsync(crtpath, 'ascii') | ||
|  |         ]).then(function (arr) { | ||
|  | 
 | ||
|  | 
 | ||
|  |           return arr; | ||
|  |         }); | ||
|  |       } | ||
|  |     , cacheCerts: function (hostname, certs) { | ||
|  |         // assume 90 day renewals based on stat time, for now
 | ||
|  |         ipc[hostname] = { | ||
|  |           context: tls.createSecureContext({ | ||
|  |             key: certs[0]  // privkey.pem
 | ||
|  |           , cert: certs[1] // fullchain.pem
 | ||
|  |           //, ciphers // node's defaults are great
 | ||
|  |           }) | ||
|  |         , updated: Date.now() | ||
|  |         }; | ||
|  | 
 | ||
|  |         return ipc[hostname]; | ||
|  |       } | ||
|  |     , readAndCacheCerts: function (hostname) { | ||
|  |         return le.readCerts(hostname).then(function (certs) { | ||
|  |           return le.cacheCerts(hostname, certs); | ||
|  |         }); | ||
|  |       } | ||
|  |     , get: function (hostname, args, opts, cb) { | ||
|  |         count += 1; | ||
|  | 
 | ||
|  |         if (count >= 1000) { | ||
|  |           now = Date.now(); | ||
|  |           count = 0; | ||
|  |         } | ||
|  | 
 | ||
|  |         var cached = ipc[hostname]; | ||
|  |         // TODO handle www and no-www together
 | ||
|  |         if (cached && ((now - cached.updated) < options.cacheContextsFor)) { | ||
|  |           cb(null, cached.context); | ||
|  |           return; | ||
|  |         } | ||
|  | 
 | ||
|  |         return le.readCerts(hostname).then(function (cached) { | ||
|  |           cb(null, cached.context); | ||
|  |         }, function (/*err*/) { | ||
|  |           var copy = {}; | ||
|  |           var arr; | ||
|  | 
 | ||
|  |           // TODO validate domains and such
 | ||
|  |           Object.keys(defaults).forEach(function (key) { | ||
|  |             copy[key] = defaults[key]; | ||
|  |           }); | ||
|  |           Object.keys(args).forEach(function (key) { | ||
|  |             copy[key] = args[key]; | ||
|  |           }); | ||
|  | 
 | ||
|  |           arr = letsencrypt.objToArr(keys, copy); | ||
|  |           // TODO validate domains empirically before trying le
 | ||
|  |           return letsencrypt.execAsync(lebinpath, arr, opts).then(function () { | ||
|  |             // wait at least n minutes
 | ||
|  |             return le.readCerts(hostname).then(function (cached) { | ||
|  |               // success
 | ||
|  |               cb(null, cached.context); | ||
|  |             }, function (err) { | ||
|  |               // still couldn't read the certs after success... that's weird
 | ||
|  |               cb(err); | ||
|  |             }); | ||
|  |           }, function (err) { | ||
|  |             console.error("[Error] Let's Encrypt failed:"); | ||
|  |             console.error(err.stack || new Error(err.message || err.toString())); | ||
|  | 
 | ||
|  |             // wasn't successful with lets encrypt, don't try again for n minutes
 | ||
|  |             ipc[hostname] = { | ||
|  |               context: null | ||
|  |             , updated: Date.now() | ||
|  |             }; | ||
|  |             cb(null, ipc[hostname]); | ||
|  |           }); | ||
|  |         }); | ||
|  |       } | ||
|  |     }; | ||
|  | 
 | ||
|  |     return le; | ||
|  |   }); | ||
|  | }; |