greenlock-express.js/README.md

479 lines
17 KiB
Markdown
Raw Normal View History

2016-04-22 18:13:15 +00:00
[![Join the chat at https://gitter.im/Daplie/letsencrypt-express](https://badges.gitter.im/Daplie/letsencrypt-express.svg)](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2016-04-22 18:19:41 +00:00
| [letsencrypt (library)](https://github.com/Daplie/node-letsencrypt)
| [letsencrypt-cli](https://github.com/Daplie/letsencrypt-cli)
2016-04-22 18:21:05 +00:00
| **letsencrypt-express**
2016-04-22 18:19:41 +00:00
| [letsencrypt-koa](https://github.com/Daplie/letsencrypt-koa)
| [letsencrypt-hapi](https://github.com/Daplie/letsencrypt-hapi)
|
2016-07-15 17:10:05 +00:00
# HELP WANTED
There are a number of easy to fix bugs (the most important of which is basically requires tracing some functions, doing some console.log-ing and returning the right type of object).
If you've got some free cycles to help, I can guide you through the process, I'm just still too busy to fix them right now and the workarounds work mentioned in the comments work.
Email me coolaj86@gmail.com if you want to help.
2015-12-17 10:00:30 +00:00
# LetsEncrypt Express
2015-12-17 00:51:37 +00:00
2016-04-22 18:06:07 +00:00
[![Join the chat at https://gitter.im/Daplie/letsencrypt-express](https://badges.gitter.im/Daplie/letsencrypt-express.svg)](https://gitter.im/Daplie/letsencrypt-express?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2016-02-16 00:48:54 +00:00
Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.
* Automatic Registration via SNI (`httpsOptions.SNICallback`)
* **registrations** require an **approval callback** in *production*
* Automatic Renewal (around 80 days)
* **renewals** are *fully automatic* and happen in the *background*, with **no downtime**
2016-02-16 01:11:48 +00:00
* Automatic vhost / virtual hosting
2016-02-16 00:48:54 +00:00
All you have to do is start the webserver and then visit it at it's domain name.
2015-12-17 10:27:13 +00:00
2015-12-17 10:29:00 +00:00
## Install
```
npm install --save letsencrypt-express
```
## Usage
2016-02-16 01:11:48 +00:00
* standalone
* express
* http / https
2016-04-06 21:24:53 +00:00
* http / http2 / spdy
2016-02-16 01:11:48 +00:00
* koa
2016-02-16 01:16:37 +00:00
### Setup (same for all examples)
2016-02-16 01:11:48 +00:00
2015-12-17 10:29:00 +00:00
```javascript
'use strict';
/* Note: using staging server url, remove .testing() for production
Using .testing() will overwrite the debug flag with true */
2016-02-16 01:11:48 +00:00
var LEX = require('letsencrypt-express').testing();
2015-12-17 10:29:00 +00:00
// Change these two lines!
var DOMAIN = 'myservice.example.com';
var EMAIL = 'user@example.com';
2016-02-16 01:11:48 +00:00
var lex = LEX.create({
configDir: require('os').homedir() + '/letsencrypt/etc'
, approveRegistration: function (hostname, approve) { // leave `null` to disable automatic registration
if (hostname === DOMAIN) { // Or check a database or list of allowed domains
approve(null, {
domains: [DOMAIN]
, email: EMAIL
, agreeTos: true
});
}
2016-02-16 01:29:23 +00:00
}
2016-02-16 00:48:54 +00:00
});
```
2015-12-17 10:27:13 +00:00
2016-02-16 01:16:37 +00:00
WARNING: If you don't do any checks and simply complete `approveRegistration` callback, an attacker will spoof SNI packets with bad hostnames and that will cause you to be rate-limited and or blocked from the ACME server. Alternatively, You can run registration *manually*:
2015-12-17 10:27:13 +00:00
```bash
npm install -g letsencrypt-cli
2015-12-17 10:39:54 +00:00
letsencrypt certonly --standalone \
--config-dir ~/letsencrypt/etc \
--agree-tos --domains example.com --email user@example.com
2016-02-16 01:16:37 +00:00
# Note: the '--webrootPath' option is also available if you don't want to shut down your webserver to get the cert.
2015-12-17 10:27:13 +00:00
```
2016-02-16 01:11:48 +00:00
### Standalone
2015-12-17 00:51:37 +00:00
2016-02-16 01:11:48 +00:00
```javascript
lex.onRequest = function (req, res) {
res.end('Hello, World!');
};
lex.listen([80], [443, 5001], function () {
console.log("ENCRYPT __ALL__ THE DOMAINS!");
});
// NOTE:
// `~/letsencrypt/etc` is the default `configDir`
// ports 80, 443, and 5001 are the default ports to listen on.
```
## Express
```bash
2016-04-06 21:24:53 +00:00
npm install --save spdy
```
2016-02-16 01:11:48 +00:00
```javascript
// A happy little express app
2016-06-09 15:15:41 +00:00
var express = require('express');
var app = express();
2016-02-16 01:11:48 +00:00
app.use(function (req, res) {
res.send({ success: true });
});
lex.onRequest = app;
lex.listen([80], [443, 5001], function () {
var protocol = ('requestCert' in this) ? 'https': 'http';
console.log("Listening at " + protocol + '://localhost:' + this.address().port);
});
```
2016-02-16 00:28:45 +00:00
2016-02-15 16:12:22 +00:00
### Use with raw http / https modules
Let's say you want to redirect all http to https.
```javascript
2016-02-15 17:50:07 +00:00
var http = require('http');
2016-04-06 21:24:53 +00:00
var https = require('spdy');
2016-02-15 17:50:07 +00:00
// NOTE: you could use the old https module if for some reason you don't want to support modern browsers
function redirectHttp() {
http.createServer(LEX.createAcmeResponder(lex, function redirectHttps(req, res) {
res.setHeader('Location', 'https://' + req.headers.host + req.url);
res.statusCode = 302; // use 307 if you want to redirect requests with POST, DELETE or PUT action.
2016-02-15 17:50:07 +00:00
res.end('<!-- Hello Developer Person! Please use HTTPS instead -->');
})).listen(80);
}
2016-02-15 16:12:22 +00:00
2016-02-15 17:50:07 +00:00
function serveHttps() {
var app = require('express')();
app.use('/', function (req, res) {
res.end('Hello!');
});
https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app)).listen(443);
}
2016-02-15 16:12:22 +00:00
2016-02-15 17:50:07 +00:00
redirectHttp();
serveHttps();
2016-02-15 16:12:22 +00:00
```
2016-02-16 01:11:48 +00:00
### Let's Encrypt with Koa
2015-12-17 08:44:55 +00:00
```javascript
2016-04-06 15:51:36 +00:00
var http = require('http');
var https = require('spdy'); // Note: some have reported trouble with `http2` and success with `spdy`
2016-02-16 00:28:45 +00:00
var koa = require('koa');
var app = koa();
2016-05-23 01:35:27 +00:00
var redirectHttps = koa().use(require('koa-sslify')()).callback();
2015-12-17 08:44:55 +00:00
2016-02-16 01:11:48 +00:00
app.use(function *() {
2016-02-16 00:28:45 +00:00
this.body = 'Hello World';
2015-12-17 08:44:55 +00:00
});
2016-04-06 15:51:36 +00:00
var server = https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app.callback()));
var redirectServer = http.createServer(LEX.createAcmeResponder(lex, redirectHttps)));
2016-02-16 01:11:48 +00:00
server.listen(443, function () {
console.log('Listening at https://localhost:' + this.address().port);
2015-12-17 00:51:37 +00:00
});
2016-04-06 15:51:36 +00:00
redirectServer.listen(80, function () {
console.log('Redirecting insecure traffic from http://localhost:' + this.address().port + ' to https');
});
2015-12-17 00:51:37 +00:00
```
### WebSockets with Let's Encrypt
Note: you don't need to create websockets for the plain ports.
2015-12-17 10:32:43 +00:00
```javascript
var WebSocketServer = require('ws').Server;
2016-04-06 21:24:53 +00:00
var https = require('spdy');
2016-02-16 01:11:48 +00:00
var server = https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, app));
var wss = new WebSocketServer({ server: server });
2015-12-17 10:32:43 +00:00
2016-02-16 01:11:48 +00:00
wss.on('connection', onConnection);
server.listen(443);
2015-12-17 10:32:43 +00:00
function onConnection(ws) {
var location = url.parse(ws.upgradeReq.url, true);
// you might use location.query.access_token to authenticate or share sessions
// or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312)
2016-02-13 01:04:40 +00:00
2015-12-17 10:32:43 +00:00
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
ws.send('something');
}
2015-12-17 00:51:37 +00:00
```
2015-12-17 03:03:07 +00:00
## API
```
2015-12-17 03:15:20 +00:00
// checks options and sets up defaults. returns object with `listen`
LEX.create(options) // (it was really just done this way to appeal to what people are used to seeing)
2015-12-17 03:03:07 +00:00
lex.listen(plain, tls, fn) // actually creates the servers and causes them to listen
2015-12-17 03:15:20 +00:00
// receives an instance of letsencrypt, returns an SNICallback handler for https.createServer()
LEX.createSniCallback(opts) // this will call letsencrypt.renew and letsencrypt.register as appropriate
// it will randomly stagger renewals such that they don't all happen at once on boot
// or at any other time. registrations will be handled as per `handleRegistration`
opts = {
letsencrypt: <obj> // letsencrypt instance
, memorizeFor: <1 day> // how long to wait before checking the disk for updated certificates
, renewWithin: <3 days> // the first possible moment the certificate staggering should begin
, failedWait: <5 minutes> // how long to wait before trying again if the certificate registration failed
2015-12-17 05:08:14 +00:00
// registrations are NOT approved automatically by default due to security concerns
, approveRegistration: func // (someone can spoof servername indication to your server and cause you to be rate-limited)
// but you can implement handling of them if you wish
// (note that you should probably call the callback immediately with a tlsContext)
//
// default function (hostname, cb) { cb(null, null); }
//
// example function (hostname, cb) {
// cb(null, { domains: [hostname], agreeTos: true, email: 'user@example.com' });
// }
, handleRenewFailure: func // renewals are automatic, but sometimes they may fail. If that happens, you should handle it
// (note that renewals happen in the background)
//
// default function (err, letsencrypt, hostname, certInfo) {}
2015-12-17 03:15:20 +00:00
}
2015-12-17 03:03:07 +00:00
2015-12-17 03:15:20 +00:00
// uses `opts.webrootPath` to read from the filesystem
2016-02-13 01:04:40 +00:00
LEX.getChallenge(opts, hostname, key cb)
LEX.createAcmeResponder(opts, fn) // this will return the necessary request handler for /.well-known/acme-challenges
// which then calls `fn` (such as express app) to complete the request
//
// opts lex instance created with LEX.create(opts)
// more generally, any object with a compatible `getChallenge` will work:
// `lex.getChallenge(opts, domain, key, function (err, val) {})`
//
// fn function (req, res) {
// console.log(req.method, req.url);
//
// res.end('Hello!');
// }
2015-12-17 03:03:07 +00:00
```
2015-12-16 23:06:00 +00:00
## Options
If any of these values are `undefined` or `null` the will assume use reasonable defaults.
Partially defined values will be merged with the defaults.
Setting the value to `false` will, in many cases (as documented), disable the defaults.
```
2015-12-17 01:20:56 +00:00
configDir: string // string the letsencrypt configuration path (de facto /etc/letsencrypt)
//
// default os.homedir() + '/letsencrypt/etc'
2015-12-17 00:51:37 +00:00
2015-12-16 23:06:00 +00:00
webrootPath: string // string a path to a folder where temporary challenge files will be stored and read
2015-12-17 01:20:56 +00:00
//
// default os.tmpdir() + '/acme-challenge'
2015-12-16 23:06:00 +00:00
getChallenge: func | false // false do not handle getChallenge
//
// func Example:
//
// default function (defaults, hostname, key, cb) {
// var filename = path.join(defaults.webrootPath.replace(':hostname', hostname), key);
// fs.readFile(filename, 'ascii', function (cb, text) {
// cb(null, text);
// })
// }
httpsOptions: object // object will be merged with internal defaults and passed to https.createServer()
// { pfx, key, cert, passphrase, ca, ciphers, rejectUnauthorized, secureProtocol }
// See https://nodejs.org/api/https.html
// Note: if SNICallback is specified, it will be run *before*
// the internal SNICallback that manages automated certificates
//
// default uses a localhost cert and key to prevent https.createServer() from throwing an error
// and also uses our SNICallback, which manages certificates
sniCallback: func // func replace the default sniCallback handler (which manages certificates) with your own
2015-12-17 00:51:37 +00:00
letsencrypt: object // object configure the letsencrypt object yourself and pass it in directly
//
// default we create the letsencrypt object using parameters you specify
2015-12-17 01:20:56 +00:00
server: url // url use letsencrypt.productionServerUrl (i.e. https://acme-v01.api.letsencrypt.org/directory)
// or letsencrypt.stagingServerUrl (i.e. https://acme-staging.api.letsencrypt.org/directory)
//
// default production
2015-12-16 23:06:00 +00:00
```
2015-12-17 00:51:37 +00:00
2016-02-16 00:48:54 +00:00
## More Examples
### < 140 Characters
Let's Encrypt in 128 characters, with spaces!
```
node -e 'require("letsencrypt-express").testing().create( require("express")().use(function (_, r) { r.end("Hi!") }) ).listen()'
```
### More realistic
```javascript
'use strict';
// Note: using staging server url, remove .testing() for production
var LEX = require('letsencrypt-express').testing();
var express = require('express');
var app = express();
app.use('/', function (req, res) {
res.send({ success: true });
});
LEX.create({
configDir: './letsencrypt.config' // ~/letsencrypt, /etc/letsencrypt, whatever you want
, onRequest: app // your express app (or plain node http app)
, letsencrypt: null // you can provide you own instance of letsencrypt
// if you need to configure it (with an agreeToTerms
// callback, for example)
, approveRegistration: function (hostname, cb) { // PRODUCTION MODE needs this function, but only if you want
// automatic registration (usually not necessary)
// renewals for registered domains will still be automatic
cb(null, {
domains: [hostname]
, email: 'user@example.com'
, agreeTos: true // you
});
}
}).listen([80], [443, 5001], function () {
console.log("ENCRYPT __ALL__ THE DOMAINS!");
});
```
### More Options Exposed
```javascript
'use strict';
var lex = require('letsencrypt-express');
var express = require('express');
var app = express();
app.use('/', function (req, res) {
res.send({ success: true });
});
var results = lex.create({
configDir: '/etc/letsencrypt'
, onRequest: app
, server: require('letsencrypt').productionServerUrl
}).listen(
// you can give just the port, or expand out to the full options
[80, { port: 8080, address: 'localhost', onListening: function () { console.log('http://localhost'); } }]
// you can give just the port, or expand out to the full options
, [443, 5001, { port: 8443, address: 'localhost' }]
// this is pretty much the default onListening handler
, function onListening() {
var server = this;
var protocol = ('requestCert' in server) ? 'https': 'http';
console.log("Listening at " + protocol + '://localhost:' + this.address().port);
}
);
// In case you need access to the raw servers (i.e. using websockets)
console.log(results.plainServers);
console.log(results.tlsServers);
```
2016-02-16 01:20:32 +00:00
### All Options Exposed
2016-02-13 01:04:40 +00:00
Here's absolutely every option and function exposed
2016-02-16 01:20:32 +00:00
```javascript
2016-02-13 01:04:40 +00:00
var http = require('http');
2016-04-06 21:24:53 +00:00
var https = require('spdy');
2016-02-13 01:04:40 +00:00
var LEX = require('letsencrypt-express');
var LE = require('letsencrypt');
var lex;
lex = LEX.create({
webrootPath: '/tmp/.well-known/acme-challenge'
, lifetime: 90 * 24 * 60 * 60 * 1000 // expect certificates to last 90 days
, failedWait: 5 * 60 * 1000 // if registering fails wait 5 minutes before trying again
, renewWithin: 3 * 24 * 60 * 60 * 1000 // renew at least 3 days before expiration
, memorizeFor: 1 * 24 * 60 * 60 * 1000 // keep certificates in memory for 1 day
, approveRegistration: function (hostname, cb) {
cb(null, {
domains: [hostname]
, email: 'user@example.com'
, agreeTos: true
});
}
, handleRenewFailure: function (err, hostname, certInfo) {
console.error("ERROR: Failed to renew domain '", hostname, "':");
if (err) {
console.error(err.stack || err);
}
if (certInfo) {
console.error(certInfo);
}
}
, letsencrypt: LE.create(
// options
{ configDir: './letsencrypt.config'
, manual: true
2016-02-13 01:04:40 +00:00
, server: LE.productionServerUrl
, privkeyPath: LE.privkeyPath
, fullchainPath: LE.fullchainPath
, certPath: LE.certPath
, chainPath: LE.chainPath
, renewalPath: LE.renewalPath
, accountsDir: LE.accountsDir
, debug: false
}
// handlers
, { setChallenge: LEX.setChallenge
, removeChallenge: LEX.removeChallenge
}
)
, debug: false
});
http.createServer(LEX.createAcmeResponder(lex, function (req, res) {
res.setHeader('Location', 'https://' + req.headers.host + req.url);
res.end('<!-- Hello Mr Developer! Please use HTTPS instead -->');
}));
https.createServer(lex.httpsOptions, LEX.createAcmeResponder(lex, function (req, res) {
res.end('Hello!');
}));
```
2015-12-17 00:51:37 +00:00
## Heroku?
This doesn't work on heroku because heroku uses a proxy with built-in https
(which is a smart thing to do) and besides, they want you to pay big bucks
for https. (hopefully not for long?...)