update guide

This commit is contained in:
AJ ONeal 2019-10-29 04:05:54 -06:00
parent b9bcdb5776
commit fc04a0445c
1 changed files with 327 additions and 203 deletions

View File

@ -6,23 +6,48 @@ All options described for `Greenlock.create({...})` also apply to the Greenlock
# Overview of Major Differences # Overview of Major Differences
## Greenlock JavaScript API greatly reduced - Reduced API
- No code in the config
- (config is completely serializable)
- Manager callbacks replace `approveDomains`
- Greenlock Express does more, with less config
- cluster is supported out-of-the-box
- high-performance
- scalable
- ACME challenges are simplified
- init
- zones (dns-01)
- set
- get
- remove
- Store callbacks are simplified
- accounts
- checkKeypairs
- certificates
- checkKeypairs
- check
- set
# Greenlock JavaScript API greatly reduced
Whereas before there were many different methods with nuance differences, Whereas before there were many different methods with nuance differences,
now there's just `create`, `get`, `renew`, and sometimes `add` (). now there's just `create`, `get`, `renew`, and sometimes `add` ().
- Greenlock.create({ maintainerEmail, packageAgent, notify }) - Greenlock.create({ maintainerEmail, packageAgent, notify })
- Greenlock.get({ servername, wildname, duplicate, force }) - Greenlock.get({ servername, wildname, duplicate, force })
- (just a convenience wrapper around renew) - (just a convenience wrapper around renew)
- Greenlock.renew({ subject, altnames, issuedBefore, expiresAfter }) - Greenlock.renew({ subject, altnames, issuedBefore, expiresAfter })
- (retrieves, issues, renews, all-in-one) - (retrieves, issues, renews, all-in-one)
- _optional_ Greenlock.add({ subject, altnames, subscriberEmail }) - _optional_ Greenlock.add({ subject, altnames, subscriberEmail })
- (partially replaces `approveDomains`) - (partially replaces `approveDomains`)
- `domains` was often ambiguous and confusing, it has been replaced by:
- `subject` refers to the subject of a certificate - the primary domain Also, some disambiguation on terms:
- `altnames` refers to the domains in the SAN (Subject Alternative Names) section of the certificate
- `servername` refers to the TLS (SSL) SNI (Server Name Indication) request for a cetificate - `domains` was often ambiguous and confusing, it has been replaced by:
- `wildname` refers to the wildcard version of the servername (ex: `www.example.com => *.example.com`) - `subject` refers to the subject of a certificate - the primary domain
- `altnames` refers to the domains in the SAN (Subject Alternative Names) section of the certificate
- `servername` refers to the TLS (SSL) SNI (Server Name Indication) request for a cetificate
- `wildname` refers to the wildcard version of the servername (ex: `www.example.com => *.example.com`)
When you create an instance of Greenlock, you only supply package and maintainer info. When you create an instance of Greenlock, you only supply package and maintainer info.
@ -35,21 +60,21 @@ var pkg = require('./package.json');
var Greenlock = require('greenlock'); var Greenlock = require('greenlock');
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// used for the ACME client User-Agent string as per RFC 8555 and RFC 7231 // used for the ACME client User-Agent string as per RFC 8555 and RFC 7231
packageAgent: pkg.name + '/' + pkg.version, packageAgent: pkg.name + '/' + pkg.version,
// used as the contact for critical bug and security notices // used as the contact for critical bug and security notices
// should be the same as pkg.author.email // should be the same as pkg.author.email
maintainerEmail: 'jon@example.com' maintainerEmail: 'jon@example.com',
// used for logging background events and errors // used for logging background events and errors
notify: function (ev, args) { notify: function(ev, args) {
if ('error' === ev || 'warning' === ev) { if ('error' === ev || 'warning' === ev) {
console.error(ev, args); console.error(ev, args);
return; return;
}
console.info(ev, args);
} }
console.info(ev, args);
}
}); });
``` ```
@ -64,25 +89,25 @@ When you want to get a single certificate, you use `get`, which will:
```js ```js
greenlock greenlock
.get({ servername: 'www.example.com' }) .get({ servername: 'www.example.com' })
.then(function(result) { .then(function(result) {
if (!result) { if (!result) {
// certificate is not on the approved list // certificate is not on the approved list
return null; return null;
} }
var fullchain = result.pems.cert + '\n' + result.pems.chain + '\n'; var fullchain = result.pems.cert + '\n' + result.pems.chain + '\n';
var privkey = result.pems.privkey; var privkey = result.pems.privkey;
return { return {
fullchain: fullchain, fullchain: fullchain,
privkey: privkey privkey: privkey
}; };
}) })
.catch(function(e) { .catch(function(e) {
// something went wrong in the renew process // something went wrong in the renew process
console.error(e); console.error(e);
}); });
``` ```
By default **no certificates will be issued**. See the _manager_ section. By default **no certificates will be issued**. See the _manager_ section.
@ -95,20 +120,20 @@ When you want to renew certificates, _en masse_, you use `renew`, which will:
```js ```js
greenlock greenlock
.renew({}) .renew({})
.then(function(results) { .then(function(results) {
if (!result.length) { if (!result.length) {
// no certificates found // no certificates found
return null; return null;
} }
// [{ site, error }] // [{ site, error }]
return results; return results;
}) })
.catch(function(e) { .catch(function(e) {
// an unexpected error, not related to renewal // an unexpected error, not related to renewal
console.error(e); console.error(e);
}); });
``` ```
Options: Options:
@ -129,28 +154,39 @@ with a few extra that are specific to Greenlock Express:
```js ```js
require('@root/greenlock-express') require('@root/greenlock-express')
.init(function() { .init(function() {
return { // This object will be passed to Greenlock.create()
cluster: false,
packageAgent: pkg.name + '/' + pkg.version, var options = {
maintainerEmail: 'jon@example.com', // some options, like cluster, are special to Greenlock Express
notify: function(ev, args) {
if ('error' === ev || 'warning' === ev) { cluster: false,
console.error(ev, args);
return; // The rest are the same as for Greenlock
}
console.info(ev, args); packageAgent: pkg.name + '/' + pkg.version,
} maintainerEmail: 'jon@example.com',
}; notify: function(ev, args) {
}) console.info(ev, args);
.serve(function(glx) { }
glx.serveApp(function(req, res) { };
res.end('Hello, Encrypted World!');
}); return options;
}); })
.serve(function(glx) {
// will start servers on port 80 and 443
glx.serveApp(function(req, res) {
res.end('Hello, Encrypted World!');
});
// you can get access to the raw server (i.e. for websockets)
glx.httpsServer(); // returns raw server object
});
``` ```
## _Manager_ replaces `approveDomains` # _Manager_ replaces `approveDomains`
`approveDomains` was always a little confusing. Most people didn't need it. `approveDomains` was always a little confusing. Most people didn't need it.
@ -167,56 +203,58 @@ The config file should look something like this:
```json ```json
{ {
"subscriberEmail": "jon@example.com", "subscriberEmail": "jon@example.com",
"agreeToTerms": true, "agreeToTerms": true,
"sites": { "sites": {
"example.com": { "example.com": {
"subject": "example.com", "subject": "example.com",
"altnames": ["example.com", "www.example.com"] "altnames": ["example.com", "www.example.com"]
} }
} }
} }
``` ```
You can specify a `acme-dns-01-*` or `acme-http-01-*` challenge plugin globally, or per-site. The same is true with `greenlock-store-*` plugins: You can specify a `acme-dns-01-*` or `acme-http-01-*` challenge plugin globally, or per-site.
```json ```json
{ {
"subscriberEmail": "jon@example.com", "subscriberEmail": "jon@example.com",
"agreeToTerms": true, "agreeToTerms": true,
"sites": { "sites": {
"example.com": { "example.com": {
"subject": "example.com", "subject": "example.com",
"altnames": ["example.com", "www.example.com"] "altnames": ["example.com", "www.example.com"],
} "challenges": {
}, "dns-01": {
"store": { "module": "acme-dns-01-digitalocean",
"module": "greenlock-store-fs", "token": "apikey-xxxxx"
"basePath": "~/.config/greenlock" }
} }
}
}
} }
``` ```
The same is true with `greenlock-store-*` plugins:
```json ```json
{ {
"subscriberEmail": "jon@example.com", "subscriberEmail": "jon@example.com",
"agreeToTerms": true, "agreeToTerms": true,
"sites": { "sites": {
"example.com": { "example.com": {
"subject": "example.com", "subject": "example.com",
"altnames": ["example.com", "www.example.com"], "altnames": ["example.com", "www.example.com"]
"challenges": { }
"dns-01": { },
"module": "acme-dns-01-digitalocean", "store": {
"token": "apikey-xxxxx" "module": "greenlock-store-fs",
} "basePath": "~/.config/greenlock"
} }
}
}
} }
``` ```
### Customer Manager ### Customer Manager, the lazy way
At the very least you have to implement `find({ servername })`. At the very least you have to implement `find({ servername })`.
@ -224,29 +262,29 @@ Since this is a very common use case, it's supported out of the box as part of t
```js ```js
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
packageAgent: pkg.name + '/' + pkg.version, packageAgent: pkg.name + '/' + pkg.version,
maintainerEmail: 'jon@example.com', maintainerEmail: 'jon@example.com',
notify: notify, notify: notify,
find: find find: find
}); });
// In the simplest case you can ignore all incoming options // In the simplest case you can ignore all incoming options
// and return a single site config in the same format as the config file // and return a single site config in the same format as the config file
function find(options) { function find(options) {
var servername = options.servername; // www.example.com var servername = options.servername; // www.example.com
var wildname = options.wildname; // *.example.com var wildname = options.wildname; // *.example.com
return Promise.resolve([ return Promise.resolve([
{ subject: 'example.com', altnames: ['example.com', 'www.example.com'] } { subject: 'example.com', altnames: ['example.com', 'www.example.com'] }
]); ]);
} }
function notify(ev, args) { function notify(ev, args) {
if ('error' === ev || 'warning' === ev) { if ('error' === ev || 'warning' === ev) {
console.error(ev, args); console.error(ev, args);
return; return;
} }
console.info(ev, args); console.info(ev, args);
} }
``` ```
@ -254,20 +292,101 @@ If you want to use wildcards or local domains, you must specify the `dns-01` cha
```js ```js
function find(options) { function find(options) {
var servername = options.servername; // www.example.com var subject = options.subject;
var wildname = options.wildname; // *.example.com // may include wildcard
return Promise.resolve([ var altnames = options.altnames;
{ var wildname = options.wildname; // *.example.com
subject: 'example.com', return Promise.resolve([
altnames: ['example.com', 'www.example.com'], {
challenges: { subject: 'example.com',
'dns-01': { module: 'acme-dns-01-namedotcom', apikey: 'xxxx' } altnames: ['example.com', 'www.example.com'],
} challenges: {
} 'dns-01': { module: 'acme-dns-01-namedotcom', apikey: 'xxxx' }
]); }
}
]);
} }
``` ```
### Customer Manager, complete
To use a fully custom manager, you give the npm package name, or absolute path to the file to load
```js
Greenlock.create({
// Greenlock Options
maintainerEmail: 'jon@example.com',
packageAgent: 'my-package/v2.1.1',
notify: notify,
// file path or npm package name
manager: '/path/to/manager.js',
// options that get passed to the manager
myFooOption: 'whatever'
});
```
The manager itself is, again relatively simple:
- find(options)
- add(siteConfig)
- update(updates)
- remove(options)
- defaults(globalOptions) (as setter)
- defaults() => globalOptions (as getter)
`/path/to/manager.js`:
```js
'use strict';
module.exports.create = function() {
var manager = {};
manager.find = async function({ subject, altnames, renewBefore }) {
if (subject) {
return getSiteConfigBySubject(subject);
}
if (altnames) {
// may include wildcards
return getSiteConfigByAnyAltname(altnames);
}
if (renewBefore) {
return getSiteConfigsWhereRenewAtIsLessThan(renewBefore);
}
return [];
};
manage.add = function({ subject, altnames }) {
return setSiteConfig(subject, { subject, altnames });
};
manage.update = function({ subject, renewAt }) {
// update the `renewAt` date of the site by `subject`
return mergSiteConfig(subject, { renewAt });
};
manage.remove = function({ subject, altname }) {
if (subject) {
return removeSiteConfig(subject);
}
return removeFromSiteConfigAndResetRenewAtToZero(altname);
};
// set the global config
manage.defaults = function(options) {
if (!options) {
return getGlobalConfig();
}
return mergeGlobalConfig(options);
};
};
```
# ACME Challenge Plugins # ACME Challenge Plugins
The ACME challenge plugins are just a few simple callbacks: The ACME challenge plugins are just a few simple callbacks:
@ -309,64 +428,69 @@ If you are just implenting in-house and are not going to publish a module, you c
`/path/to/project/my-hacky-store.js`: `/path/to/project/my-hacky-store.js`:
```js ```js
'use strict';
module.exports.create = function(options) { module.exports.create = function(options) {
// ex: /path/to/account.ecdsa.jwk.json // ex: /path/to/account.ecdsa.jwk.json
var accountJwk = require(options.accountJwkPath); var accountJwk = require(options.accountJwkPath);
// ex: /path/to/privkey.rsa.pem // ex: /path/to/privkey.rsa.pem
var serverPem = fs.readFileSync(options.serverPemPath, 'ascii'); var serverPem = fs.readFileSync(options.serverPemPath, 'ascii');
var store = {}; var accounts = {};
var certificates = {};
var store = { accounts, certificates };
// bare essential account callbacks // bare essential account callbacks
store.accounts = { accounts.checkKeypair = function() {
checkKeypair: function() { // ignore all options and just return a single, global keypair
// ignore all options and just return a single, global keypair
return Promise.resolve({
privateKeyJwk: accountJwk
});
},
setKeypair: function() {
// this will never get called if checkKeypair always returns
return Promise.resolve({});
}
};
// bare essential cert and key callbacks return Promise.resolve({
store.certificates = { privateKeyJwk: accountJwk
checkKeypair: function() { });
// ignore all options and just return a global server keypair };
return { accounts.setKeypair = function() {
privateKeyPem: serverPem // this will never get called if checkKeypair always returns
};
},
setKeypair: function() {
// never gets called if checkKeypair always returns an existing key
return Promise.resolve(null);
},
check: function(args) {
var subject = args.subject;
// make a database call or whatever to get a certificate
return goGetCertBySubject(subject).then(function() {
return {
pems: {
chain: '<PEM>',
cert: '<PEM>'
}
};
});
},
set: function(args) {
var subject = args.subject;
var cert = args.pems.cert;
var chain = args.pems.chain;
// make a database call or whatever to get a certificate return Promise.resolve({});
return goSaveCert({ };
subject,
cert, // bare essential cert and key callbacks
chain certificates.checkKeypair = function() {
}); // ignore all options and just return a global server keypair
}
}; return {
privateKeyPem: serverPem
};
};
certificates.setKeypair = function() {
// never gets called if checkKeypair always returns an existing key
return Promise.resolve(null);
};
certificates.check = function(args) {
var subject = args.subject;
// make a database call or whatever to get a certificate
return goGetCertBySubject(subject).then(function() {
return {
pems: {
chain: '<PEM>',
cert: '<PEM>'
}
};
});
};
certificates.set = function(args) {
var subject = args.subject;
var cert = args.pems.cert;
var chain = args.pems.chain;
// make a database call or whatever to get a certificate
return goSaveCert({
subject,
cert,
chain
});
};
}; };
``` ```
@ -379,18 +503,18 @@ Then you could assign it as the default for all of your sites:
```json ```json
{ {
"subscriberEmail": "jon@example.com", "subscriberEmail": "jon@example.com",
"agreeToTerms": true, "agreeToTerms": true,
"sites": { "sites": {
"example.com": { "example.com": {
"subject": "example.com", "subject": "example.com",
"altnames": ["example.com", "www.example.com"] "altnames": ["example.com", "www.example.com"]
} }
}, },
"store": { "store": {
"module": "/path/to/project/my-hacky-store.js", "module": "/path/to/project/my-hacky-store.js",
"accountJwkPath": "/path/to/account.ecdsa.jwk.json", "accountJwkPath": "/path/to/account.ecdsa.jwk.json",
"serverPemPath": "/path/to/privkey.rsa.pem" "serverPemPath": "/path/to/privkey.rsa.pem"
} }
} }
``` ```