initial commit
This commit is contained in:
commit
2b4b714126
|
@ -0,0 +1,61 @@
|
||||||
|
# ---> Node
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Typescript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
Copyright 2019 AJ ONeal
|
||||||
|
|
||||||
|
This is open source software; you can redistribute it and/or modify it under the
|
||||||
|
terms of either:
|
||||||
|
|
||||||
|
a) the "MIT License"
|
||||||
|
b) the "Apache-2.0 License"
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
Apache-2.0 License Summary
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,107 @@
|
||||||
|
# le-store-fs
|
||||||
|
|
||||||
|
A greenlock keypair and certificate storage strategy with wildcard support (simpler successor to le-store-certbot).
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```js
|
||||||
|
var greenlock = require('greenlock');
|
||||||
|
var gl = greenlock.create({
|
||||||
|
configDir: '~/.config/acme'
|
||||||
|
, store: require('le-store-fs')
|
||||||
|
, approveDomains: approveDomains
|
||||||
|
, ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
# File System
|
||||||
|
|
||||||
|
The default file system layout mirrors that of le-store-certbot in order to make transitioning effortless,
|
||||||
|
in most situations:
|
||||||
|
|
||||||
|
```
|
||||||
|
acme
|
||||||
|
├── accounts
|
||||||
|
│ └── acme-staging-v02.api.letsencrypt.org
|
||||||
|
│ └── directory
|
||||||
|
│ └── sites@example.com.json
|
||||||
|
└── live
|
||||||
|
├── example.com
|
||||||
|
│ ├── bundle.pem
|
||||||
|
│ ├── cert.pem
|
||||||
|
│ ├── chain.pem
|
||||||
|
│ ├── fullchain.pem
|
||||||
|
│ └── privkey.pem
|
||||||
|
└── www.example.com
|
||||||
|
├── bundle.pem
|
||||||
|
├── cert.pem
|
||||||
|
├── chain.pem
|
||||||
|
├── fullchain.pem
|
||||||
|
└── privkey.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
# Wildcards & AltNames
|
||||||
|
|
||||||
|
Working with wildcards and multiple altnames requires greenlock >= v2.7.
|
||||||
|
|
||||||
|
To do so you must set `opts.subject` and `opts.domains` within the `approvedomains()` callback.
|
||||||
|
|
||||||
|
`subject` refers to "the subject of the ssl certificate" as opposed to `domain` which indicates "the domain servername
|
||||||
|
used in the current request". For single-domain certificates they're always the same, but for multiple-domain
|
||||||
|
certificates `subject` must be the name no matter what `domain` is receiving a request. `subject` is used as
|
||||||
|
part of the name of the file storage path where the certificate will be saved (or retrieved).
|
||||||
|
|
||||||
|
`domains` should be the list of "altnames" on the certificate, which should include the `subject`.
|
||||||
|
|
||||||
|
## Simple Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
function approveDomains(opts, certs, cb) {
|
||||||
|
// 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")); }
|
||||||
|
|
||||||
|
opts.subject = '*.example.com';
|
||||||
|
opts.domains = ['*.example.com'];
|
||||||
|
|
||||||
|
cb({ options: opts, certs: certs });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Realistic Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
function approveDomains(opts, certs, cb) {
|
||||||
|
var related = getRelated(opts.domain);
|
||||||
|
if (!related) { cb(new Error(opts.domain + " is not allowed")); };
|
||||||
|
|
||||||
|
opts.subject = related.subject;
|
||||||
|
opts.domains = related.domains;
|
||||||
|
|
||||||
|
cb({ options: opts, certs: certs });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
function getRelated(domain) {
|
||||||
|
var related;
|
||||||
|
var wild = '*.' + domain.split('.').slice(1).join('.');
|
||||||
|
if (Object.keys(allAllowedDomains).some(function (k) {
|
||||||
|
return allAllowedDomains[k].some(function (name) {
|
||||||
|
if (domain === name || wild === name) {
|
||||||
|
related = { subject: k, altnames: allAllowedDomains[k] };
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})) {
|
||||||
|
return related;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var allAllowedDomains = {
|
||||||
|
'example.com': ['example.com', '*.example.com']
|
||||||
|
, 'example.net': ['example.net', '*.example.net']
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,299 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/*global Promise*/
|
||||||
|
var PromiseA;
|
||||||
|
var util = require('util');
|
||||||
|
if (!util.promisify) {
|
||||||
|
try {
|
||||||
|
PromiseA = require('bluebird');
|
||||||
|
util.promisify = PromiseA.promisify;
|
||||||
|
} catch(e) {
|
||||||
|
console.error("Your version of node is missing Promise. Please run `npm install --save bluebird` in your project to fix");
|
||||||
|
process.exit(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ('undefined' !== typeof Promise) { PromiseA = Promise; }
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var readFileAsync = util.promisify(fs.readFile);
|
||||||
|
var writeFileAsync = util.promisify(fs.writeFile);
|
||||||
|
var sfs = require('safe-replace');
|
||||||
|
var mkdirpAsync = util.promisify(require('mkdirp'));
|
||||||
|
var os = require("os");
|
||||||
|
|
||||||
|
// create():
|
||||||
|
// Your storage plugin may take special options, or it may not.
|
||||||
|
// If it does, document to your users that they must call create() with those options.
|
||||||
|
// If you user does not call create(), greenlock will call it for you with the options it has.
|
||||||
|
// It's kind of stupid, but it's done this way so that it can be more convenient for users to not repeat shared options
|
||||||
|
// (such as the config directory), but sometimes configs would clash. I hate having ambiguity, so I may change this in
|
||||||
|
// a future version, but it's very much an issue of "looks cleaner" vs "behaves cleaner".
|
||||||
|
module.exports.create = function (config) {
|
||||||
|
|
||||||
|
// This file has been laid out in the order that options are used and calls are made
|
||||||
|
// greenlock.approveDomains)
|
||||||
|
// greenlock.store.certificates.checkAsync()
|
||||||
|
// greenlock.store.accounts.checkAsync()
|
||||||
|
// greenlock.store.accounts.setKeypairAsync()
|
||||||
|
// greenlock.store.accounts.setAsync()
|
||||||
|
// greenlock.store.certificates.checkKeypairAsync()
|
||||||
|
// 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).
|
||||||
|
var store = {};
|
||||||
|
|
||||||
|
// options:
|
||||||
|
//
|
||||||
|
// If your module requires options (i.e. file paths or database urls) you should check what you get from create()
|
||||||
|
// and copy over the things you'll use into this options object. You should also merge in any defaults for options
|
||||||
|
// that have not been set. This object should not be circular, should not be changed after it is set, and should
|
||||||
|
// contain every property that you can use, using falsey JSON-able values like 0, null, false, or '' for "unset"
|
||||||
|
// values.
|
||||||
|
// 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
|
||||||
|
store.certificates = {};
|
||||||
|
|
||||||
|
// certificates.checkAsync({ subject, ... }):
|
||||||
|
//
|
||||||
|
// The first check is that a certificate looked for by 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"))
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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); }
|
||||||
|
if (opts.exampleReturnCerts) { return Promise.resolve(opts.exampleReturnCerts); }
|
||||||
|
|
||||||
|
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
|
||||||
|
// TODO this shouldn't be necessary here (we should get it from checkKeypairAsync)
|
||||||
|
var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
|
||||||
|
var certPath = opts.certPath || path.join(liveDir, 'cert.pem');
|
||||||
|
var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem');
|
||||||
|
|
||||||
|
return PromiseA.all([
|
||||||
|
readFileAsync(privkeyPath, 'ascii') // 0
|
||||||
|
, readFileAsync(certPath, 'ascii') // 1
|
||||||
|
, readFileAsync(chainPath, 'ascii') // 2
|
||||||
|
]).then(function (all) {
|
||||||
|
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)
|
||||||
|
//, subject: certinfo.subject
|
||||||
|
//, altnames: certinfo.altnames
|
||||||
|
//, issuedAt: certinfo.issuedAt // a.k.a. NotBefore
|
||||||
|
//, expiresAt: certinfo.expiresAt // a.k.a. NotAfter
|
||||||
|
};
|
||||||
|
}).catch(function (err) {
|
||||||
|
if ('ENOENT' === err.code) { return null; }
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
return Promise.resolve(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// accounts.checkKeypairAsync({ email, ... }):
|
||||||
|
//
|
||||||
|
// Same rules as above apply, except for the private key of the account, not the account object itself.
|
||||||
|
//
|
||||||
|
// On Success: Promise.resolve({ ... }) - the abstract object representing the 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.checkKeypairAsync = function (opts) {
|
||||||
|
var id = opts.account.id || 'single-user';
|
||||||
|
console.log('accounts.checkKeypairAsync for', id);
|
||||||
|
if (!opts.account.id) { return Promise.reject(new Error("'account.id' should have been set in approveDomains()")); }
|
||||||
|
|
||||||
|
return readFileAsync(path.join(opts.accountsDir, sanitizeFilename(id) + '.json'), 'utf8').then(function (blob) {
|
||||||
|
// keypair is an opaque object that should be treated as blob
|
||||||
|
return JSON.parse(blob);
|
||||||
|
}).catch(function (err) {
|
||||||
|
if ('ENOENT' === err.code) { return null; }
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
//
|
||||||
|
// On Success: Promise.resolve(null) - just knowing the operation is successful will do
|
||||||
|
// On Error: Promise.reject(new Error("something descriptive for the user"))
|
||||||
|
store.accounts.setKeypairAsync = function (opts, keypair) {
|
||||||
|
var id = opts.account.id || 'single-user';
|
||||||
|
console.log('accounts.setKeypairAsync for', id);
|
||||||
|
keypair = opts.keypair || keypair;
|
||||||
|
if (!opts.account.id) { return Promise.reject(new Error("'account.id' should have been set in approveDomains()")); }
|
||||||
|
return mkdirpAsync(opts.accountsDir).then(function () {
|
||||||
|
// keypair is an opaque object that should be treated as blob
|
||||||
|
return writeFileAsync(path.join(opts.accountsDir, sanitizeFilename(id) + '.json'), JSON.stringify(keypair), 'utf8');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// accounts.setAsync({ account, keypair, email, ... }):
|
||||||
|
//
|
||||||
|
// The account details, from ACME, if everything is successful.
|
||||||
|
//
|
||||||
|
// 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);
|
||||||
|
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.
|
||||||
|
store.certificates.checkKeypairAsync = function (opts) {
|
||||||
|
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(privkeyPath, 'ascii').then(function (key) {
|
||||||
|
// keypair is normally an opaque object, but here it's a pem for the filesystem
|
||||||
|
return { privateKeyPem: key };
|
||||||
|
}).catch(function (err) {
|
||||||
|
if ('ENOENT' === err.code) { return null; }
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// certificates.setKeypairAsync({ domain, keypair, ... }):
|
||||||
|
//
|
||||||
|
// Same as accounts.setKeypairAsync, but by domains rather than email / accountId
|
||||||
|
store.certificates.setKeypairAsync = function (opts, keypair) {
|
||||||
|
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(path.dirname(privkeyPath)).then(function () {
|
||||||
|
return writeFileAsync(privkeyPath, keypair.privateKeyPem, 'ascii').then(function () {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// certificates.setAsync({ domain, certs, ... }):
|
||||||
|
//
|
||||||
|
// 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);
|
||||||
|
var pems = {
|
||||||
|
privkey: opts.pems.privkey
|
||||||
|
, cert: opts.pems.cert
|
||||||
|
, chain: opts.pems.chain
|
||||||
|
};
|
||||||
|
var liveDir = opts.liveDir || path.join(opts.configDir, 'live', opts.subject);
|
||||||
|
var certPath = opts.certPath || path.join(liveDir, 'cert.pem');
|
||||||
|
var fullchainPath = opts.fullchainPath || path.join(liveDir, 'fullchain.pem');
|
||||||
|
var chainPath = opts.chainPath || path.join(liveDir, 'chain.pem');
|
||||||
|
//var privkeyPath = opts.privkeyPath || opts.domainKeyPath || path.join(liveDir, 'privkey.pem');
|
||||||
|
var bundlePath = opts.bundlePath || path.join(liveDir, 'bundle.pem');
|
||||||
|
|
||||||
|
return mkdirpAsync(path.dirname(certPath)).then(function () {
|
||||||
|
return mkdirpAsync(path.dirname(chainPath)).then(function () {
|
||||||
|
return mkdirpAsync(path.dirname(fullchainPath)).then(function () {
|
||||||
|
return mkdirpAsync(path.dirname(bundlePath)).then(function () {
|
||||||
|
return PromiseA.all([
|
||||||
|
sfs.writeFileAsync(certPath, pems.cert, 'ascii')
|
||||||
|
, sfs.writeFileAsync(chainPath, pems.chain, 'ascii')
|
||||||
|
// Most platforms need these two
|
||||||
|
, sfs.writeFileAsync(fullchainPath, [ pems.cert, pems.chain ].join('\n'), 'ascii')
|
||||||
|
//, sfs.writeFileAsync(privkeyPath, pems.privkey, 'ascii')
|
||||||
|
// HAProxy needs "bundle.pem" aka "combined.pem"
|
||||||
|
, sfs.writeFileAsync(bundlePath, [ pems.privkey, pems.cert, pems.chain ].join('\n'), 'ascii')
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function () {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return store;
|
||||||
|
};
|
||||||
|
|
||||||
|
var defaults = {
|
||||||
|
configDir: path.join(os.homedir(), 'acme', 'etc')
|
||||||
|
|
||||||
|
, accountsDir: path.join(':configDir', 'accounts', ':serverDir')
|
||||||
|
, serverDirGet: function (copy) {
|
||||||
|
return (copy.server || '').replace('https://', '').replace(/(\/)$/, '').replace(/\//g, path.sep);
|
||||||
|
}
|
||||||
|
, privkeyPath: path.join(':configDir', 'live', ':hostname', 'privkey.pem')
|
||||||
|
, fullchainPath: path.join(':configDir', 'live', ':hostname', 'fullchain.pem')
|
||||||
|
, certPath: path.join(':configDir', 'live', ':hostname', 'cert.pem')
|
||||||
|
, chainPath: path.join(':configDir', 'live', ':hostname', 'chain.pem')
|
||||||
|
, bundlePath: path.join(':configDir', 'live', ':hostname', 'bundle.pem')
|
||||||
|
};
|
||||||
|
|
||||||
|
function mergeOptions(configs) {
|
||||||
|
if (!configs.domainKeyPath) {
|
||||||
|
configs.domainKeyPath = configs.privkeyPath || defaults.privkeyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(defaults).forEach(function (key) {
|
||||||
|
if (!configs[key]) {
|
||||||
|
configs[key] = defaults[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeFilename(id) {
|
||||||
|
return id.replace(/(\.\.)|\\|\//g, '_').replace(/[^!-~]/g, '_');
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "le-store-json",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"requires": true,
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||||
|
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||||
|
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||||
|
"requires": {
|
||||||
|
"minimist": "0.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe-replace": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"name": "le-store-fs",
|
||||||
|
"version": "0.9.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",
|
||||||
|
"directories": {
|
||||||
|
"test": "tests"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "node tests"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.coolaj86.com/coolaj86/le-store-fs.js.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"greenlock",
|
||||||
|
"json",
|
||||||
|
"keypairs",
|
||||||
|
"certificates",
|
||||||
|
"store",
|
||||||
|
"database"
|
||||||
|
],
|
||||||
|
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
||||||
|
"license": "MPL-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"mkdirp": "^0.5.1",
|
||||||
|
"safe-replace": "^1.1.0"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue