2019-04-16 05:45:12 +00:00
|
|
|
# [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | A [Root](https://rootprojects.org) project
|
2019-04-08 06:14:28 +00:00
|
|
|
|
|
|
|
A keypair and certificate storage strategy for Greenlock v2.7+ (and v3).
|
|
|
|
The (much simpler) successor to le-store-certbot.
|
|
|
|
|
|
|
|
Works with all ACME (Let's Encrypt) SSL certificate sytles:
|
|
|
|
* [x] single domains
|
|
|
|
* [x] multiple domains (SANs, AltNames)
|
|
|
|
* [x] wildcards
|
|
|
|
* [x] private / localhost domains
|
2019-04-01 07:56:41 +00:00
|
|
|
|
|
|
|
# Usage
|
|
|
|
|
|
|
|
```js
|
|
|
|
var greenlock = require('greenlock');
|
|
|
|
var gl = greenlock.create({
|
|
|
|
configDir: '~/.config/acme'
|
2019-04-08 06:14:28 +00:00
|
|
|
, store: require('greenlock-store-fs')
|
2019-04-01 07:56:41 +00:00
|
|
|
, 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
|
|
|
|
|
2019-04-08 06:14:28 +00:00
|
|
|
Working with wildcards and multiple altnames requires greenlock >= v2.7 (or v3).
|
2019-04-01 07:56:41 +00:00
|
|
|
|
2019-04-08 06:14:28 +00:00
|
|
|
To do so you must return `{ subject: '...', altnames: ['...', ...] }` within the `approveDomains()` callback.
|
2019-04-01 07:56:41 +00:00
|
|
|
|
|
|
|
`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).
|
|
|
|
|
2019-04-08 06:14:28 +00:00
|
|
|
`altnames` should be the list of SubjectAlternativeNames (SANs) on the certificate.
|
|
|
|
The subject and the first altname must be an exact match: `subject === altnames[0]`.
|
2019-04-01 07:56:41 +00:00
|
|
|
|
|
|
|
## Simple Example
|
|
|
|
|
|
|
|
```js
|
2019-04-03 04:24:07 +00:00
|
|
|
function approveDomains(opts) {
|
|
|
|
// Allow only example.com and *.example.com (such as foo.example.com)
|
|
|
|
|
2019-04-01 07:56:41 +00:00
|
|
|
// foo.example.com => *.example.com
|
|
|
|
var wild = '*.' + opts.domain.split('.').slice(1).join('.');
|
2019-04-08 06:14:28 +00:00
|
|
|
|
2019-04-03 04:24:07 +00:00
|
|
|
if ('example.com' !== opts.domain && '*.example.com' !== wild) {
|
|
|
|
cb(new Error(opts.domain + " is not allowed"));
|
|
|
|
}
|
2019-04-01 07:56:41 +00:00
|
|
|
|
2019-04-08 06:14:28 +00:00
|
|
|
var result = { subject: 'example.com', altnames: [ 'example.com', '*.example.com' ] };
|
|
|
|
return Promise.resolve(result);
|
2019-04-01 07:56:41 +00:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Realistic Example
|
|
|
|
|
|
|
|
```js
|
|
|
|
function approveDomains(opts, certs, cb) {
|
|
|
|
var related = getRelated(opts.domain);
|
2019-04-01 07:58:47 +00:00
|
|
|
if (!related) { cb(new Error(opts.domain + " is not allowed")); };
|
2019-04-01 07:56:41 +00:00
|
|
|
|
2019-04-01 07:58:47 +00:00
|
|
|
opts.subject = related.subject;
|
|
|
|
opts.domains = related.domains;
|
2019-04-01 07:56:41 +00:00
|
|
|
|
|
|
|
cb({ options: opts, certs: certs });
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
function getRelated(domain) {
|
|
|
|
var related;
|
|
|
|
var wild = '*.' + domain.split('.').slice(1).join('.');
|
2019-04-01 07:58:47 +00:00
|
|
|
if (Object.keys(allAllowedDomains).some(function (k) {
|
|
|
|
return allAllowedDomains[k].some(function (name) {
|
|
|
|
if (domain === name || wild === name) {
|
2019-04-01 07:56:41 +00:00
|
|
|
related = { subject: k, altnames: allAllowedDomains[k] };
|
2019-04-01 07:58:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
2019-04-01 07:56:41 +00:00
|
|
|
})) {
|
|
|
|
return related;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
```js
|
|
|
|
var allAllowedDomains = {
|
|
|
|
'example.com': ['example.com', '*.example.com']
|
|
|
|
, 'example.net': ['example.net', '*.example.net']
|
|
|
|
}
|
|
|
|
```
|