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
```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
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.domains = ['*.example.com'];
opts.subject = '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.domains as all domains on the cert
// // 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.checkKeypairAsync()
// greenlock.core.RSA.generateKeypair() // 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.core.RSA.generateKeypair() // 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.setAsync() // optional
// greenlock.store.certificates.checkKeypairAsync()
// greenlock.core.RSA.generateKeypair() // TODO double check name
// greenlock.core.certificates.register() // TODO double check name
// greenlock.core.RSA.generateKeypair() // TODO double check name
// greenlock.core.certificates.register() // TODO double check name
// greenlock.store.certificates.setKeypairAsync()
// greenlock.store.certificates.setAsync()
// store
// Bear in mind that the only time any of this gets called is on first access after startup, new registration,
// and renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however
// (if you have more than 10,000 domains, for example).
// Bear in mind that the only time any of this gets called is on first access after startup, new registration, and
// renewal - so none of this needs to be particularly fast. It may need to be memory efficient, however - if you have
// more than 10,000 domains, for example.
var store = {};
// options:
@ -66,10 +66,6 @@ module.exports.create = function (config) {
// See the note on create() above.
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
store.accounts = {};
// set and check domain keypairs and domain certificates
@ -77,27 +73,17 @@ module.exports.create = function (config) {
// 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.
// 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).
// However, this is called after `approveDomains)`, so 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"))
// And since this is called after `approveDomains()`, any options that you set there will be available here too.
store.certificates.checkAsync = function (opts) {
// { domain, ... }
console.log('certificates.checkAsync for', opts.domain, opts.subject, opts.domains);
console.log(opts);
console.log(new Error("just for the stack trace:").stack);
// { certificate.id, subject, domains, ... }
var id = opts.certificate && opts.certificate.id || opts.subject;
//console.log('certificates.checkAsync for', opts.domain, opts.subject, opts.domains);
//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)
if (opts.exampleThrowError) { return Promise.reject(new Error("You want an error? You got it!")); }
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');
return PromiseA.all([
readFileAsync(tameWild(privkeyPath, opts.subject), 'ascii') // 0
, readFileAsync(tameWild(certPath, opts.subject), 'ascii') // 1
, readFileAsync(tameWild(chainPath, opts.subject), 'ascii') // 2
// all other PEM files are arrangements of these three
readFileAsync(tameWild(privkeyPath, id), 'ascii') // 0
, readFileAsync(tameWild(certPath, id), 'ascii') // 1
, readFileAsync(tameWild(chainPath, id), 'ascii') // 2
]).then(function (all) {
// Success
return {
privkey: all[0]
, cert: all[1]
, chain: all[2]
// When using a database, these should be retrieved
// (as is they'll be read via cert-info)
// When using a database, these should be retrieved too
// (when not available they'll be generated from cert-info)
//, subject: certinfo.subject
//, altnames: certinfo.altnames
//, issuedAt: certinfo.issuedAt // a.k.a. NotBefore
//, expiresAt: certinfo.expiresAt // a.k.a. NotAfter
};
}).catch(function (err) {
// Non-success
if ('ENOENT' === err.code) { return null; }
// Failure
throw err;
});
};
// accounts.checkAsync({ accountId, email, [...] }): // Optional
//
// This is where you promise an account corresponding to the given the email and ID. All instance options
// (i.e. 'options' above, merged with other "override" or per-use options, such as from 'approveDomains)')
// 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)
// Although you can supply a 'check' thunk (node-style callback) here, it's going to be converted to a proper
// promise, so just go ahead and use that from the get-go.
// This is where you promise an account corresponding to the given the email and ID. All options set in
// approveDomains() are also available. You can ignore them unless your implementation is using them in some way.
//
// 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
//
// 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 Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.checkAsync = function (opts) {
var id = opts.account.id || 'single-user';
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
store.accounts.checkAsync = function (/*opts*/) {
//var id = opts.account.id || 'single-user';
//console.log('accounts.checkAsync for', id);
return Promise.resolve(null);
};
@ -177,7 +164,7 @@ module.exports.create = function (config) {
// accounts.setKeypairAsync({ keypair, email, ... }):
//
// 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 Error: Promise.reject(new Error("something descriptive for the user"))
@ -195,23 +182,26 @@ module.exports.create = function (config) {
// 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 Error: Promise.reject(new Error("something descriptive for the user"))
store.accounts.setAsync = function (opts, receipt) {
receipt = opts.receipt || receipt;
console.log('account.setAsync:', receipt);
store.accounts.setAsync = function (/*opts, receipt*/) {
//receipt = opts.receipt || receipt;
//console.log('account.setAsync:', receipt);
return Promise.resolve(null);
};
// 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) {
console.log('certificates.checkKeypairAsync:');
//console.log('certificates.checkKeypairAsync:');
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
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
return { privateKeyPem: key };
@ -228,6 +218,7 @@ module.exports.create = function (config) {
keypair = opts.keypair || keypair;
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
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
return mkdirpAsync(tameWild(path.dirname(privkeyPath), opts.subject)).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
store.certificates.setAsync = function (opts) {
console.log('certificates.setAsync:');
console.log(opts.domain, '<=', opts.subject);
//console.log('certificates.setAsync:');
//console.log(opts.domain, '<=', opts.subject);
var pems = {
privkey: opts.pems.privkey
, cert: opts.pems.cert

View File

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