diff --git a/manager.js b/manager.js index 5ffac42..70aec4f 100644 --- a/manager.js +++ b/manager.js @@ -11,15 +11,15 @@ var homedir = require('os').homedir(); var path = require('path'); var mkdirp = promisify(require('@root/mkdirp')); -Manage.create = function(opts) { - if (!opts) { - opts = {}; +Manage.create = function(CONF) { + if (!CONF) { + CONF = {}; } - if (!opts.configFile) { - opts.configFile = '~/.config/greenlock/manager.json'; - console.info('Greenlock Manager Config File: ' + opts.configFile); + if (!CONF.configFile) { + CONF.configFile = '~/.config/greenlock/manager.json'; + console.info('Greenlock Manager Config File: ' + CONF.configFile); } - opts.configFile = opts.configFile.replace('~/', homedir + '/'); + CONF.configFile = CONF.configFile.replace('~/', homedir + '/'); var manage = {}; @@ -28,7 +28,7 @@ Manage.create = function(opts) { manage.defaults = manage.config = function(conf) { // get / set default site settings such as // subscriberEmail, store, challenges, renewOffset, renewStagger - return Manage._getLatest(manage, opts).then(function(config) { + return Manage._getLatest(manage, CONF).then(function(config) { if (!conf) { conf = JSON.parse(JSON.stringify(config)); delete conf.sites; @@ -86,7 +86,7 @@ Manage.create = function(opts) { manage.add = function(args) { manage._txPromise = manage._txPromise.then(function() { // if the fs has changed since we last wrote, get the lastest from disk - return Manage._getLatest(manage, opts).then(function(config) { + return Manage._getLatest(manage, CONF).then(function(config) { // TODO move to Greenlock.add var subscriberEmail = args.subscriberEmail; var subject = args.subject || args.domain; @@ -131,7 +131,11 @@ Manage.create = function(opts) { altnames .slice(0) .sort() - .join(' ') !== site.altnames.slice(0).sort().join(' ') + .join(' ') !== + site.altnames + .slice(0) + .sort() + .join(' ') ) { // TODO signal to wait for renewal? // it will definitely be renewed on the first request anyway @@ -180,11 +184,11 @@ Manage.create = function(opts) { manage.find = function(args) { return _find(args).then(function(existing) { - if (!opts.find) { + if (!CONF.find) { return existing; } - return Promise.resolve(opts.find(args)).then(function(results) { + return Promise.resolve(CONF.find(args)).then(function(results) { // TODO also detect and delete stale (just ignoring them for now) var changed = []; var same = []; @@ -192,7 +196,7 @@ Manage.create = function(opts) { // Check lowercase subject names var subject = (_newer.subject || '').toLowerCase(); // Set the default altnames to the subject, just in case - var altnames = _newer.altnames || []; + var altnames = (_newer.altnames || []).slice(0); if (!altnames.includes(subject)) { console.warn( "all site configs should include 'subject' and 'altnames': " + @@ -220,7 +224,6 @@ Manage.create = function(opts) { ) { _older.renewAt = 0; _older.altnames = altnames; - // TODO signal waitForRenewal (although it'll update on the first access automatically) changed.push(_older); } else { same.push(_older); @@ -243,7 +246,7 @@ Manage.create = function(opts) { } // kinda redundant to pull again, but whatever... - return Manage._getLatest(manage, opts).then(function(config) { + return Manage._getLatest(manage, CONF).then(function(config) { changed.forEach(function(site) { config.sites[site.subject] = site; }); @@ -259,27 +262,27 @@ Manage.create = function(opts) { }; function _find(args) { - return Manage._getLatest(manage, opts).then(function(config) { + return Manage._getLatest(manage, CONF).then(function(config) { // i.e. find certs more than 30 days old //args.issuedBefore = Date.now() - 30 * 24 * 60 * 60 * 1000; // i.e. find certs more that will expire in less than 45 days //args.expiresBefore = Date.now() + 45 * 24 * 60 * 60 * 1000; var issuedBefore = args.issuedBefore || Infinity; var expiresBefore = args.expiresBefore || Infinity; //Date.now() + 21 * 24 * 60 * 60 * 1000; - var all = !args.altnames; + var nameKeys = ['subject', 'altnames']; - var altnames = (args.altnames || args.domains || []).slice(0); - if (args.servername && !altnames.includes(args.servername)) { - altnames.push(args.servername); - } - if (args.wildname && !altnames.includes(args.wildname)) { - altnames.push(args.wildname); - } + // if there's anything to match, only return matches + // if there's nothing to match, return everything + var matchAll = !nameKeys.some(function(k) { + return k in args; + }); + + var querynames = (args.altnames || []).slice(0); // TODO match ANY domain on any cert var sites = Object.keys(config.sites || {}) - .filter(function(sub) { - var site = config.sites[sub]; + .filter(function(subject) { + var site = doctor.site(config.sites, subject); if (site.deletedAt) { return false; } @@ -290,20 +293,20 @@ Manage.create = function(opts) { return false; } + // after attribute filtering, before cert filtering + if (matchAll) { + return true; + } + // if subject is specified, don't return anything else - if (args.subject) { - if (site.subject === args.subject) { - return true; - } + if (site.subject === args.subject) { + return true; } // altnames, servername, and wildname all get rolled into one - return ( - all || - (site.altnames || []).some(function(name) { - return altnames.includes(name); - }) - ); + return site.altnames.some(function(altname) { + return querynames.includes(altname); + }); }) .map(function(name) { var site = config.sites[name]; @@ -326,7 +329,7 @@ Manage.create = function(opts) { }); } - manage.notify = opts.notify || _notify; + manage.notify = CONF.notify || _notify; function _notify(ev, args) { if (!args) { args = ev; @@ -378,7 +381,7 @@ Manage.create = function(opts) { manage.update = function(args) { manage._txPromise = manage._txPromise.then(function() { - return Manage._getLatest(manage, opts).then(function(config) { + return Manage._getLatest(manage, CONF).then(function(config) { var site = config.sites[args.subject]; //site.issuedAt = args.issuedAt; //site.expiresAt = args.expiresAt; @@ -394,7 +397,7 @@ Manage.create = function(opts) { throw new Error('should have a subject for sites to remove'); } manage._txPromise = manage._txPromise.then(function() { - return Manage._getLatest(manage, opts).then(function(config) { + return Manage._getLatest(manage, CONF).then(function(config) { var site = config.sites[args.subject]; if (!site) { return {}; @@ -414,22 +417,22 @@ Manage.create = function(opts) { manage._config = {}; manage._save = function(config) { - return mkdirp(path.dirname(opts.configFile)).then(function() { + return mkdirp(path.dirname(CONF.configFile)).then(function() { return sfs .writeFileAsync( - opts.configFile, + CONF.configFile, // pretty-print the config file JSON.stringify(config, null, 2), 'utf8' ) .then(function() { // this file may contain secrets, so keep it safe - return chmodFile(opts.configFile, parseInt('0600', 8)) + return chmodFile(CONF.configFile, parseInt('0600', 8)) .catch(function() { /*ignore for Windows */ }) .then(function() { - return statFile(opts.configFile).then(function( + return statFile(CONF.configFile).then(function( stat ) { manage._lastStat.size = stat.size; @@ -443,8 +446,8 @@ Manage.create = function(opts) { return manage; }; -Manage._getLatest = function(mng, opts) { - return statFile(opts.configFile) +Manage._getLatest = function(mng, CONF) { + return statFile(CONF.configFile) .catch(function(err) { if ('ENOENT' === err.code) { return { @@ -462,10 +465,34 @@ Manage._getLatest = function(mng, opts) { ) { return mng._config; } - return readFile(opts.configFile, 'utf8').then(function(data) { + return readFile(CONF.configFile, 'utf8').then(function(data) { mng._lastStat = stat; mng._config = JSON.parse(data); return mng._config; }); }); }; + +var doctor = {}; +// users muck up config files, so we try to handle it gracefully. +doctor.site = function(sconfs, subject) { + var site = sconfs[subject]; + if (!site) { + delete sconfs[subject]; + site = {}; + } + + // TODO notify on any changes + if ('string' !== typeof site.subject) { + delete sconfs[subject]; + site.subject = 'greenlock-error.example.com'; + } + if (!Array.isArray(site.altnames)) { + site.altnames = [site.subject]; + } + if (!site.renewAt) { + site.renewAt = 1; + } + + return site; +}; diff --git a/package-lock.json b/package-lock.json index df5b77c..68710af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "greenlock-manager-fs", - "version": "0.6.3", + "version": "0.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d492d80..384e9d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "greenlock-manager-fs", - "version": "0.6.5", + "version": "0.7.0", "description": "A simple file-based management strategy for Greenlock", "main": "manager.js", "scripts": { diff --git a/test.js b/test.js new file mode 100644 index 0000000..3639b6b --- /dev/null +++ b/test.js @@ -0,0 +1,52 @@ +'use strict'; + +var Manager = require('./'); +var manager = Manager.create({ + configFile: 'greenlock-manager-test.delete-me.json' +}); +var domains = ['example.com', 'www.example.com']; + +async function run() { + await manager.add({ + subject: domains[0], + altnames: domains + }); + + await manager.find({}).then(function(results) { + if (!results.length) { + console.log(results); + throw new Error('should have found all managed sites'); + } + }); + + await manager.find({ subject: 'www.example.com' }).then(function(results) { + if (results.length) { + console.log(results); + throw new Error( + "shouldn't find what doesn't exist, exactly, by subject" + ); + } + }); + + await manager + .find({ altnames: ['www.example.com'] }) + .then(function(results) { + if (!results.length) { + console.log(results); + throw new Error('should have found sites matching altname'); + } + }); + + await manager.find({ altnames: ['*.example.com'] }).then(function(results) { + if (results.length) { + console.log(results); + throw new Error( + 'should only find an exact (literal) wildcard match' + ); + } + }); + + console.log("PASS"); +} + +run();