mirror of
				https://git.coolaj86.com/coolaj86/greenlock-store-fs.js.git
				synced 2025-11-04 11:02:47 +00:00 
			
		
		
		
	v1.0.0: update docs, burn the logs, cleanup comments, etc
This commit is contained in:
		
							parent
							
								
									9f61e34a8a
								
							
						
					
					
						commit
						533cb5395d
					
				
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@ -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);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										93
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								index.js
									
									
									
									
									
								
							@ -37,7 +37,7 @@ 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
 | 
				
			||||||
@ -51,9 +51,9 @@ module.exports.create = function (config) {
 | 
				
			|||||||
  //     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
 | 
				
			||||||
 | 
				
			|||||||
@ -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",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user