greenlock.js/README.md

449 lines
12 KiB
Markdown
Raw Permalink Normal View History

2015-12-11 11:23:47 +00:00
letsencrypt
===========
2015-12-14 19:37:22 +00:00
Automatic [Let's Encrypt](https://letsencrypt.org) HTTPS Certificates for node.js
2015-12-11 11:23:47 +00:00
2015-12-13 06:40:03 +00:00
* Automatic HTTPS with ExpressJS
* Automatic live renewal (in-process)
* On-the-fly HTTPS certificates for Dynamic DNS (in-process, no server restart)
* Works with node cluster out of the box
2015-12-13 06:39:37 +00:00
* usable via commandline as well
2015-12-13 06:40:03 +00:00
* Free SSL (HTTPS Certificates for TLS)
* [90-day certificates](https://letsencrypt.org/2015/11/09/why-90-days.html)
2015-12-12 13:11:05 +00:00
2015-12-13 09:04:44 +00:00
**See Also**
* See the node-letsencrypt [Examples](https://github.com/Daplie/node-letsencrypt/tree/master/examples)
* [Let's Encrypt in (exactly) 90 seconds with Caddy](https://daplie.com/articles/lets-encrypt-in-literally-90-seconds/)
* [lego](https://github.com/xenolf/lego): Let's Encrypt for golang
2015-12-12 13:11:05 +00:00
Install
=======
```bash
npm install --save letsencrypt
2015-12-16 13:07:26 +00:00
npm install --global letsencrypt-cli
2015-12-12 13:11:05 +00:00
```
2015-12-13 09:04:44 +00:00
Usage
=====
2015-12-12 22:16:02 +00:00
2015-12-16 13:07:26 +00:00
### letsencrypt-cli
See more at [letsencrypt-cli](https://github.com/Daplie/node-letsencrypt-cli)
```bash
letsencrypt certonly \
--agree-tos --email user@example.com \
--standalone \
--domains example.com,www.example.com \
--config-dir ~/letsencrypt/etc \
--server https://acme-staging.api.letsencrypt.org/directory \
ls ~/letsencrypt/etc/live
```
### letsencrypt-express
TODO
See more at [letsencrypt-express](https://github.com/Daplie/letsencrypt-express)
2015-12-12 22:16:02 +00:00
2015-12-16 13:07:56 +00:00
### letsencrypt (the library)
2015-12-13 11:09:06 +00:00
```javascript
2015-12-13 09:04:44 +00:00
var config = require('./examples/config-minimal');
2015-12-15 02:15:11 +00:00
config.le.webrootPath = __dirname + '/tests/acme-challenge';
2015-12-13 09:04:44 +00:00
2015-12-16 09:19:08 +00:00
var le = require('letsencrypt').create(config.le);
2015-12-12 22:16:02 +00:00
le.register({
2015-12-13 09:04:44 +00:00
agreeTos: true
, domains: ['example.com'] // CHANGE TO YOUR DOMAIN
, email: 'user@email.com' // CHANGE TO YOUR EMAIL
2015-12-16 09:19:08 +00:00
, standalone: true
2015-12-13 09:04:44 +00:00
}, function (err) {
if (err) {
console.error('[Error]: node-letsencrypt/examples/standalone');
console.error(err.stack);
} else {
console.log('success');
}
plainServer.close();
tlsServer.close();
2015-12-12 22:16:02 +00:00
});
2015-12-13 09:04:44 +00:00
// IMPORTANT
2015-12-13 09:11:57 +00:00
// you also need BOTH an http AND https server that serve directly
// from webrootPath, which might as well be a special folder reserved
// only for acme/letsencrypt challenges
2015-12-13 09:04:44 +00:00
//
// app.use('/', express.static(config.le.webrootPath))
2015-12-12 22:16:02 +00:00
```
**However**, due to the nature of what this library does, it has a few more "moving parts"
2015-12-12 22:16:44 +00:00
than what makes sense to show in a minimal snippet.
2015-12-12 22:16:02 +00:00
2015-12-13 09:04:44 +00:00
Examples
========
2015-12-13 09:04:44 +00:00
### One-Time Registration
2015-12-13 06:39:37 +00:00
2015-12-13 09:04:44 +00:00
Register a 90-day certificate manually, on a whim
#### Snippets
2015-12-13 08:30:47 +00:00
[`commandline-minimal`](https://github.com/Daplie/node-letsencrypt/blob/master/examples/commandline-minimal.js):
2015-12-13 09:04:44 +00:00
**Part 1: the Let's Encrypt client**:
```javascript
'use strict';
var LE = require('letsencrypt');
var config = require('./config-minimal');
// Note: you should make this special dir in your product and leave it empty
config.le.webrootPath = __dirname + '/../tests/acme-challenge';
config.le.server = LE.stagingServer;
//
// Manual Registration
//
var le = LE.create(config.backend, config.le);
le.register({
agreeTos: true
, domains: ['example.com'] // CHANGE TO YOUR DOMAIN
, email: 'user@email.com' // CHANGE TO YOUR EMAIL
}, function (err) {
if (err) {
console.error('[Error]: node-letsencrypt/examples/standalone');
console.error(err.stack);
} else {
console.log('success');
}
plainServer.close();
tlsServer.close();
});
2015-12-13 09:04:44 +00:00
```
2015-12-13 09:04:44 +00:00
**Part 2: Express Web Server**:
```javascript
//
// Express App
//
var app = require('express')();
app.use('/', le.middleware());
//
// HTTP & HTTPS servers
// (required for domain validation)
//
var plainServer = require('http').createServer(app).listen(config.plainPort, function () {
console.log('Listening http', this.address());
});
var tlsServer = require('https').createServer({
key: config.tlsKey
, cert: config.tlsCert
, SNICallback: le.sniCallback
}, app).listen(config.tlsPort, function () {
console.log('Listening http', this.address());
});
```
2015-12-13 09:04:44 +00:00
#### Runnable Demo
* [commandline (standalone with "webroot")](https://github.com/Daplie/node-letsencrypt/blob/master/examples/commandline.js)
```bash
# manual standalone registration via commandline
# (runs against testing server on tls port 5001)
node examples/commandline.js example.com,www.example.com user@example.net agree
```
### Express
Fully Automatic HTTPS with ExpressJS using Free SSL certificates from Let's Encrypt
2015-12-13 09:04:44 +00:00
#### Snippets
* [Minimal ExpressJS Example](https://github.com/Daplie/node-letsencrypt/blob/master/examples/express-minimal.js)
```javascript
'use strict';
var LE = require('letsencrypt');
var config = require('./config-minimal');
// Note: you should make this special dir in your product and leave it empty
config.le.webrootPath = __dirname + '/../tests/acme-challenge';
config.le.server = LE.stagingServer;
//
// Automatically Register / Renew Domains
//
var le = LE.create(config.backend, config.le, {
sniRegisterCallback: function (args, expiredCert, cb) {
// Security: check that this is actually a subdomain we allow
// (otherwise an attacker can cause you to rate limit against the LE server)
var hostname = args.domains[0];
if (!/\.example\.com$/.test(hostname)) {
console.error("bad domain '" + hostname + "', not a subdomain of example.com");
cb(nul, null);
}
// agree to the LE TOS for this domain
args.agreeTos = true;
args.email = 'user@example.com';
// use the cert even though it's expired
if (expiredCert) {
cb(null, expiredCert);
cb = function () { /*ignore*/ };
}
// register / renew the certificate in the background
2015-12-13 08:32:25 +00:00
le.register(args, cb);
}
});
//
// Express App
//
var app = require('express')();
app.use('/', le.middleware());
//
// HTTP & HTTPS servers
//
require('http').createServer(app).listen(config.plainPort, function () {
console.log('Listening http', this.address());
});
require('https').createServer({
key: config.tlsKey
, cert: config.tlsCert
, SNICallback: le.sniCallback
}, app).listen(config.tlsPort, function () {
console.log('Listening http', this.address());
});
```
2015-12-13 09:04:44 +00:00
#### Runnable Example
* [Full ExpressJS Example](https://github.com/Daplie/node-letsencrypt/blob/master/examples/express.js)
2015-12-13 06:45:21 +00:00
2015-12-13 06:39:37 +00:00
```bash
2015-12-13 06:45:21 +00:00
# clear out the certificates
rm -rf tests/letsencrypt.*
2015-12-13 06:39:37 +00:00
# automatic registration and renewal (certs install as you visit the site for the first time)
# (runs against testing server on tls port 5001)
2015-12-13 06:45:21 +00:00
node examples/express.js example.com,www.example.com user@example.net agree
2015-12-13 06:39:37 +00:00
```
2015-12-13 06:45:21 +00:00
```bash
# this will take a moment because it won't respond to the tls sni header until it gets the certs
curl https://example.com/
```
2015-12-12 22:16:02 +00:00
2015-12-13 06:00:30 +00:00
### non-root
If you want to run this as non-root, you can.
You just have to set node to be allowed to use root ports
```
# node
sudo setcap cap_net_bind_service=+ep /usr/local/bin/node
```
and then make sure to set all of of the following to a directory that your user is permitted to write to
* `webrootPath`
* `configDir`
2015-12-12 22:16:02 +00:00
API
===
2015-12-12 13:11:05 +00:00
2015-12-13 09:04:44 +00:00
```javascript
2015-12-16 09:11:31 +00:00
LetsEncrypt.init(leConfig, handlers) // wraps a given
LetsEncrypt.create(backend, leConfig, handlers) // wraps a given "backend" (the python or node client)
LetsEncrypt.stagingServer // string of staging server for testing
le.middleware() // middleware for serving webrootPath to /.well-known/acme-challenge
le.sniCallback(hostname, function (err, tlsContext) {}) // uses fetch (below) and formats for https.SNICallback
le.register({ domains, email, agreeTos, ... }, cb) // registers or renews certs for a domain
le.fetch({domains, email, agreeTos, ... }, cb) // fetches certs from in-memory cache, occasionally refreshes from disk
le.validate(domains, cb) // do some sanity checks before attempting to register
le.registrationFailureCallback(err, args, certInfo, cb) // called when registration fails (not implemented yet)
2015-12-13 09:04:44 +00:00
```
2015-12-12 15:38:14 +00:00
2015-12-16 09:11:31 +00:00
### `LetsEncrypt.create(backend, leConfig, handlers)`
2015-12-12 22:06:36 +00:00
2015-12-16 09:11:31 +00:00
#### leConfig
2015-12-12 22:06:36 +00:00
The arguments passed here (typically `webpathRoot`, `configDir`, etc) will be merged with
2015-12-12 22:19:28 +00:00
any `args` (typically `domains`, `email`, and `agreeTos`) and passed to the backend whenever
it is called.
2015-12-12 22:06:36 +00:00
Typically the backend wrapper will already merge any necessary backend-specific arguments.
**Example**:
2015-12-12 13:11:05 +00:00
```javascript
2015-12-12 22:06:36 +00:00
{ webrootPath: __dirname, '/acme-challenge'
2015-12-12 13:11:05 +00:00
, fullchainTpl: '/live/:hostname/fullchain.pem'
, privkeyTpl: '/live/:hostname/fullchain.pem'
, configDir: '/etc/letsencrypt'
2015-12-12 22:06:36 +00:00
}
```
2015-12-12 13:11:05 +00:00
2015-12-13 01:04:12 +00:00
Note: `webrootPath` can be set as a default, semi-locally with `webrootPathTpl`, or per
2015-12-16 09:11:31 +00:00
registration as `webrootPath` (which overwrites `leConfig.webrootPath`).
2015-12-13 01:04:12 +00:00
2015-12-12 22:06:36 +00:00
#### handlers *optional*
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
`h.setChallenge(hostnames, name, value, cb)`:
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
default is to write to fs
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
`h.getChallenge(hostnames, value cb)`
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
default is to read from fs
`h.sniRegisterCallback(args, currentCerts, cb)`
The default is to immediately call `cb(null, null)` and register (or renew) in the background
during the `SNICallback` phase. Right now it isn't reasonable to renew during SNICallback,
but around February when it is possible to use ECDSA keys (as opposed to RSA at present),
registration will take very little time.
This will not be called while another registration is already in progress.
**SECURITY WARNING**: If you use this option with a custom `h.validate()`, make sure that `args.domains`
refers to domains you expect, otherwise an attacker will spoof SNI and cause your server to rate-limit
letsencrypt.org and get blocked. Note that `le.validate()` will check A records before attempting to
register to help prevent such possible attacks.
2015-12-12 13:11:05 +00:00
2015-12-12 22:06:36 +00:00
`h.validate(domains, cb)`
When specified this will override `le.validate()`. You will need to do this if the ip address of this
server is not one specified in the A records for your domain.
### `le.middleware()`
An express handler for `/.well-known/acme-challenge/<challenge>`.
Will call `getChallenge([hostname], key, cb)` if present or otherwise read `challenge` from disk.
Example:
```javascript
app.use('/', le.middleware())
2015-12-12 13:11:05 +00:00
```
2015-12-12 22:06:36 +00:00
### `le.sniCallback(hostname, function (err, tlsContext) {});`
Will call `fetch`. If fetch does not return certificates or returns expired certificates
it will call `sniRegisterCallback(args, currentCerts, cb)` and then return the error,
the new certificates, or call `fetch` a final time.
Example:
```javascript
2015-12-12 22:21:02 +00:00
var server = require('https').createServer({ SNICallback: le.sniCallback, cert: '...', key: '...' });
2015-12-12 22:06:36 +00:00
server.on('request', app);
2015-12-12 13:11:05 +00:00
```
2015-12-12 22:06:36 +00:00
### `le.register({ domains, email, agreeTos, ... }, cb)`
Get certificates for a domain
Example:
2015-12-12 22:17:32 +00:00
```javascript
2015-12-12 22:06:36 +00:00
le.register({
domains: ['example.com', 'www.example.com']
, email: 'user@example.com'
2015-12-13 01:04:12 +00:00
, webrootPath: '/srv/www/example.com/public'
2015-12-12 22:06:36 +00:00
, agreeTos: true
}, function (err, certs) {
// err is some error
console.log(certs);
/*
{ cert: "contents of fullchain.pem"
, key: "contents of privkey.pem"
, renewedAt: <date in milliseconds>
, duration: <duration in milliseconds (90-days)>
2015-12-12 13:11:05 +00:00
}
2015-12-12 22:06:36 +00:00
*/
});
2015-12-12 13:11:05 +00:00
```
2015-12-12 22:06:36 +00:00
### `le.isValidDomain(hostname)`
returns `true` if `hostname` is a valid ascii or punycode domain name.
(also exposed on the main exported module as `LetsEncrypt.isValidDomain()`)
### `le.validate(args, cb)`
Used internally, but exposed for convenience. Checks `LetsEncrypt.isValidDomain()`
2015-12-13 06:40:03 +00:00
and then checks to see that the current server
2015-12-12 22:06:36 +00:00
Called before `backend.register()` to validate the following:
* the hostnames don't use any illegal characters
* the server's actual public ip (via api.apiify.org)
2015-12-13 06:40:03 +00:00
* the A records for said hostnames
2015-12-12 22:06:36 +00:00
### `le.fetch(args, cb)`
Used internally, but exposed for convenience.
Checks in-memory cache of certificates for `args.domains` and calls then calls `backend.fetch(args, cb)`
**after** merging `args` if necessary.
2015-12-13 05:03:48 +00:00
### `le.registrationFailureCallback(err, args, certInfo, cb)`
Not yet implemented
2015-12-12 13:11:05 +00:00
This is what `args` looks like:
```javascript
{ domains: ['example.com', 'www.example.com']
, email: 'user@email.com'
, agreeTos: true
, configDir: '/etc/letsencrypt'
, fullchainTpl: '/live/:hostname/fullchain.pem' // :hostname will be replaced with the domainname
2015-12-13 01:04:12 +00:00
, privkeyTpl: '/live/:hostname/privkey.pem'
, webrootPathTpl: '/srv/www/:hostname/public'
, webrootPath: '/srv/www/example.com/public' // templated from webrootPathTpl
2015-12-12 13:11:05 +00:00
}
```
This is what the implementation should look like:
(it's expected that the client will follow the same conventions as
the python client, but it's not necessary)
2015-12-13 11:09:06 +00:00
Change History
==============
2015-12-16 09:19:08 +00:00
* v1.1.0 Added letiny-core, removed node-letsencrypt-python
* v1.0.2 Works with node-letsencrypt-python
* v1.0.0 Thar be dragons
2015-12-13 11:09:06 +00:00
2015-12-11 11:23:47 +00:00
LICENSE
=======
Dual-licensed MIT and Apache-2.0
See LICENSE