v1.0.0: update docs, burn the logs, cleanup comments, etc

This commit is contained in:
AJ ONeal 2019-04-02 22:24:07 -06:00
parent 9f61e34a8a
commit 533cb5395d
3 changed files with 57 additions and 62 deletions

View File

@ -56,15 +56,19 @@ part of the name of the file storage path where the certificate will be saved (o
## Simple Example ## Simple Example
```js ```js
function approveDomains(opts, certs, cb) { function approveDomains(opts) {
// Allow only example.com and *.example.com (such as foo.example.com)
// foo.example.com => *.example.com // foo.example.com => *.example.com
var wild = '*.' + opts.domain.split('.').slice(1).join('.'); var wild = '*.' + opts.domain.split('.').slice(1).join('.');
if ('*.example.com' !== wild) { cb(new Error(opts.domain + " is not allowed")); } if ('example.com' !== opts.domain && '*.example.com' !== wild) {
cb(new Error(opts.domain + " is not allowed"));
}
opts.subject = '*.example.com'; opts.subject = 'example.com';
opts.domains = ['*.example.com']; opts.domains = [ 'example.com', '*.example.com' ];
cb({ options: opts, certs: certs }); return Promise.resolve(opts);
} }
``` ```

103
index.js
View File

@ -37,23 +37,23 @@ module.exports.create = function (config) {
// // you should set opts.subject as the cert "id" domain // // you should set opts.subject as the cert "id" domain
// // you should set opts.domains as all domains on the cert // // you should set opts.domains as all domains on the cert
// // you should set opts.account.id, otherwise opts.email will be used // // you should set opts.account.id, otherwise opts.email will be used
// greenlock.store.certificates.checkAsync() // on success -> SNI cache, on fail \/ // greenlock.store.certificates.checkAsync() // on success -> SNI cache, on fail -> checkAccount
// greenlock.store.accounts.checkAsync() // optional (you can always return null) // greenlock.store.accounts.checkAsync() // optional (you can always return null)
// greenlock.store.accounts.checkKeypairAsync() // greenlock.store.accounts.checkKeypairAsync()
// greenlock.core.RSA.generateKeypair() // TODO double check name // greenlock.core.RSA.generateKeypair() // TODO double check name
// greenlock.core.accounts.register() // TODO double check name // greenlock.core.accounts.register() // TODO double check name
// greenlock.store.accounts.setKeypairAsync() // TODO make sure this only happens on generate // greenlock.store.accounts.setKeypairAsync() // TODO make sure this only happens on generate
// greenlock.store.accounts.setAsync() // optional // greenlock.store.accounts.setAsync() // optional
// greenlock.store.certificates.checkKeypairAsync() // greenlock.store.certificates.checkKeypairAsync()
// greenlock.core.RSA.generateKeypair() // TODO double check name // greenlock.core.RSA.generateKeypair() // TODO double check name
// greenlock.core.certificates.register() // TODO double check name // greenlock.core.certificates.register() // TODO double check name
// greenlock.store.certificates.setKeypairAsync() // greenlock.store.certificates.setKeypairAsync()
// greenlock.store.certificates.setAsync() // greenlock.store.certificates.setAsync()
// store // store
// Bear in mind that the only time any of this gets called is on first access after startup, new registration, // Bear in mind that the only time any of this gets called is on first access after startup, new registration, and
// and renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however // renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however - if you have
// (if you have more than 10,000 domains, for example). // more than 10,000 domains, for example.
var store = {}; var store = {};
// options: // options:
@ -66,10 +66,6 @@ module.exports.create = function (config) {
// See the note on create() above. // See the note on create() above.
store.options = mergeOptions(config); store.options = mergeOptions(config);
// getOptions():
// This must be implemented for backwards compatibility. That is all.
store.getOptions = function () { return store.options; };
// set and check account keypairs and account data // set and check account keypairs and account data
store.accounts = {}; store.accounts = {};
// set and check domain keypairs and domain certificates // set and check domain keypairs and domain certificates
@ -77,27 +73,17 @@ module.exports.create = function (config) {
// certificates.checkAsync({ subject, ... }): // certificates.checkAsync({ subject, ... }):
// //
// The first check is that a certificate looked for by domain name. // The first check is that a certificate looked for by its subject (primary domain name).
// If that lookup succeeds, then nothing else needs to happen. Otherwise accounts.checkAsync will happen next. // If that lookup succeeds, then nothing else needs to happen. Otherwise accounts.checkAsync will happen next.
// What should happen here is a lookup in a database (or filesystem). Generally the pattern will be to see if the
// domain is an exact match for a single-subject (single domain) or multi-subject (many domains via SANS/AltName)
// and then stripping the first part of the domain to see if there's a wildcard match. If you're clever you could
// also do these checks in parallel, but this only happens at startup and before renewal, so you don't have to get
// unless you want to for fun.
// The only input you need to be concerned with is opts.subject (which falls back to opts.domains[0] if not set). // The only input you need to be concerned with is opts.subject (which falls back to opts.domains[0] if not set).
// However, this is called after `approveDomains)`, so any options that you set there will be available here too, // And since this is called after `approveDomains()`, any options that you set there will be available here too.
// as well as any other config you might need to access from other modules, if you're doing something special.
//
// On Success: Promise.resolve({ ... }) - the pem or jwk for the certificate
// On Failure: Promise.resolve(null) - do not return undefined, do not throw, do not reject
// On Error: Promise.reject(new Error("something descriptive for the user"))
store.certificates.checkAsync = function (opts) { store.certificates.checkAsync = function (opts) {
// { domain, ... } // { certificate.id, subject, domains, ... }
console.log('certificates.checkAsync for', opts.domain, opts.subject, opts.domains); var id = opts.certificate && opts.certificate.id || opts.subject;
console.log(opts); //console.log('certificates.checkAsync for', opts.domain, opts.subject, opts.domains);
console.log(new Error("just for the stack trace:").stack); //console.log(opts);
// Just to show that any options set in approveDomains will be available here // Just to show that any options set in approveDomains() will be available here
// (the same is true for all of the hooks in this file) // (the same is true for all of the hooks in this file)
if (opts.exampleThrowError) { return Promise.reject(new Error("You want an error? You got it!")); } if (opts.exampleThrowError) { return Promise.reject(new Error("You want an error? You got it!")); }
if (opts.exampleReturnNull) { return Promise.resolve(null); } if (opts.exampleReturnNull) { return Promise.resolve(null); }
@ -110,45 +96,46 @@ module.exports.create = function (config) {
var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem'); var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem');
return PromiseA.all([ return PromiseA.all([
readFileAsync(tameWild(privkeyPath, opts.subject), 'ascii') // 0 // all other PEM files are arrangements of these three
, readFileAsync(tameWild(certPath, opts.subject), 'ascii') // 1 readFileAsync(tameWild(privkeyPath, id), 'ascii') // 0
, readFileAsync(tameWild(chainPath, opts.subject), 'ascii') // 2 , readFileAsync(tameWild(certPath, id), 'ascii') // 1
, readFileAsync(tameWild(chainPath, id), 'ascii') // 2
]).then(function (all) { ]).then(function (all) {
// Success
return { return {
privkey: all[0] privkey: all[0]
, cert: all[1] , cert: all[1]
, chain: all[2] , chain: all[2]
// When using a database, these should be retrieved // When using a database, these should be retrieved too
// (as is they'll be read via cert-info) // (when not available they'll be generated from cert-info)
//, subject: certinfo.subject //, subject: certinfo.subject
//, altnames: certinfo.altnames //, altnames: certinfo.altnames
//, issuedAt: certinfo.issuedAt // a.k.a. NotBefore //, issuedAt: certinfo.issuedAt // a.k.a. NotBefore
//, expiresAt: certinfo.expiresAt // a.k.a. NotAfter //, expiresAt: certinfo.expiresAt // a.k.a. NotAfter
}; };
}).catch(function (err) { }).catch(function (err) {
// Non-success
if ('ENOENT' === err.code) { return null; } if ('ENOENT' === err.code) { return null; }
// Failure
throw err; throw err;
}); });
}; };
// accounts.checkAsync({ accountId, email, [...] }): // Optional // accounts.checkAsync({ accountId, email, [...] }): // Optional
// //
// This is where you promise an account corresponding to the given the email and ID. All instance options // This is where you promise an account corresponding to the given the email and ID. All options set in
// (i.e. 'options' above, merged with other "override" or per-use options, such as from 'approveDomains)') // approveDomains() are also available. You can ignore them unless your implementation is using them in some way.
// are also available. You can ignore them unless your implementation is using them in some way. //
// You should error if the account cannot be found (otherwise an unexpected error will be thrown) // Since accounts are based on public key, the act of creating a new account or returning an existing account
// Although you can supply a 'check' thunk (node-style callback) here, it's going to be converted to a proper // are the same in regards to the API and so we don't really need to store the account id or retrieve it.
// promise, so just go ahead and use that from the get-go. // This method only needs to be implemented if you need it for your own purposes
// //
// On Success: Promise.resolve({ id, keypair, ... }) - an id and, for backwards compatibility, the abstract keypair // On Success: Promise.resolve({ id, keypair, ... }) - an id and, for backwards compatibility, the abstract keypair
// On Failure: Promise.resolve(null) - do not return undefined, do not throw, do not reject // On Failure: Promise.resolve(null) - do not return undefined, do not throw, do not reject
// On Error: Promise.reject(new Error("something descriptive for the user")) // On Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.checkAsync = function (opts) { store.accounts.checkAsync = function (/*opts*/) {
var id = opts.account.id || 'single-user'; //var id = opts.account.id || 'single-user';
console.log('accounts.checkAsync for', id); //console.log('accounts.checkAsync for', id);
// Since accounts are based on public key, the act of creating a new account or returning an existing account
// are the same in regards to the API and so we don't really need to store the account id or retrieve it.
// This method only needs to be implemented if you need it for your own purposes
return Promise.resolve(null); return Promise.resolve(null);
}; };
@ -177,7 +164,7 @@ module.exports.create = function (config) {
// accounts.setKeypairAsync({ keypair, email, ... }): // accounts.setKeypairAsync({ keypair, email, ... }):
// //
// The keypair details (RSA, ECDSA, etc) are chosen either by the greenlock defaults, global user defaults, // The keypair details (RSA, ECDSA, etc) are chosen either by the greenlock defaults, global user defaults,
// or whatever you set in approveDomains) // or whatever you set in approveDomains(). This is called *after* the account is successfully created.
// //
// On Success: Promise.resolve(null) - just knowing the operation is successful will do // On Success: Promise.resolve(null) - just knowing the operation is successful will do
// On Error: Promise.reject(new Error("something descriptive for the user")) // On Error: Promise.reject(new Error("something descriptive for the user"))
@ -195,23 +182,26 @@ module.exports.create = function (config) {
// accounts.setAsync({ account, keypair, email, ... }): // accounts.setAsync({ account, keypair, email, ... }):
// //
// The account details, from ACME, if everything is successful. // The account details, from ACME, if everything is successful. Unless you need to do something with those account
// details, this implementation can remain empty.
// //
// On Success: Promise.resolve(null||{ id }) - do not return undefined, do not throw, do not reject // On Success: Promise.resolve(null||{ id }) - do not return undefined, do not throw, do not reject
// On Error: Promise.reject(new Error("something descriptive for the user")) // On Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.setAsync = function (opts, receipt) { store.accounts.setAsync = function (/*opts, receipt*/) {
receipt = opts.receipt || receipt; //receipt = opts.receipt || receipt;
console.log('account.setAsync:', receipt); //console.log('account.setAsync:', receipt);
return Promise.resolve(null); return Promise.resolve(null);
}; };
// certificates.checkKeypairAsync({ subject, ... }): // certificates.checkKeypairAsync({ subject, ... }):
// //
// Same rules as above apply, except for the private key of the certificate, not the public certificate itself. // Same rules as certificates.checkAsync apply, except for the private key of the certificate, not the public
// certificate itself (similar to accounts.checkKeyPairAsync, but for certs).
store.certificates.checkKeypairAsync = function (opts) { store.certificates.checkKeypairAsync = function (opts) {
console.log('certificates.checkKeypairAsync:'); //console.log('certificates.checkKeypairAsync:');
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject); var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem'); var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
return readFileAsync(tameWild(privkeyPath, opts.subject), 'ascii').then(function (key) { return readFileAsync(tameWild(privkeyPath, opts.subject), 'ascii').then(function (key) {
// keypair is normally an opaque object, but here it's a pem for the filesystem // keypair is normally an opaque object, but here it's a pem for the filesystem
return { privateKeyPem: key }; return { privateKeyPem: key };
@ -228,6 +218,7 @@ module.exports.create = function (config) {
keypair = opts.keypair || keypair; keypair = opts.keypair || keypair;
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject); var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem'); var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
// keypair is normally an opaque object, but here it's a PEM for the FS // keypair is normally an opaque object, but here it's a PEM for the FS
return mkdirpAsync(tameWild(path.dirname(privkeyPath), opts.subject)).then(function () { return mkdirpAsync(tameWild(path.dirname(privkeyPath), opts.subject)).then(function () {
return writeFileAsync(tameWild(privkeyPath, opts.subject), keypair.privateKeyPem, 'ascii').then(function () { return writeFileAsync(tameWild(privkeyPath, opts.subject), keypair.privateKeyPem, 'ascii').then(function () {
@ -240,8 +231,8 @@ module.exports.create = function (config) {
// //
// This is where certificates are set, as well as certinfo // This is where certificates are set, as well as certinfo
store.certificates.setAsync = function (opts) { store.certificates.setAsync = function (opts) {
console.log('certificates.setAsync:'); //console.log('certificates.setAsync:');
console.log(opts.domain, '<=', opts.subject); //console.log(opts.domain, '<=', opts.subject);
var pems = { var pems = {
privkey: opts.pems.privkey privkey: opts.pems.privkey
, cert: opts.pems.cert , cert: opts.pems.cert

View File

@ -1,6 +1,6 @@
{ {
"name": "le-store-fs", "name": "le-store-fs",
"version": "0.9.2", "version": "1.0.0",
"description": "A file-based certificate store for greenlock that supports wildcards.", "description": "A file-based certificate store for greenlock that supports wildcards.",
"homepage": "https://git.coolaj86.com/coolaj86/le-store-fs.js", "homepage": "https://git.coolaj86.com/coolaj86/le-store-fs.js",
"main": "index.js", "main": "index.js",