mirror of
				https://github.com/therootcompany/greenlock-express.js.git
				synced 2024-11-16 17:28:59 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 13033d018e | 
							
								
								
									
										67
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								README.md
									
									
									
									
									
								
							@ -114,41 +114,37 @@ All you have to do is start the webserver and then visit it at its domain name.
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
require('greenlock-express').create({
 | 
			
		||||
  email: 'john.doe@example.com'     // The email address of the ACME user / hosting provider
 | 
			
		||||
, agreeTos: true                    // You must accept the ToS as the host which handles the certs
 | 
			
		||||
, configDir: '~/.config/acme/'      // Writable directory where certs will be saved
 | 
			
		||||
, communityMember: true             // Join the community to get notified of important updates
 | 
			
		||||
, telemetry: true                   // Contribute telemetry data to the project
 | 
			
		||||
 | 
			
		||||
  // Let's Encrypt v2 is ACME draft 11
 | 
			
		||||
  version: 'draft-11'
 | 
			
		||||
  // Using your express app:
 | 
			
		||||
  // simply export it as-is, then include it here
 | 
			
		||||
, app: require('./app.js')
 | 
			
		||||
 | 
			
		||||
  // Note: If at first you don't succeed, switch to staging to debug
 | 
			
		||||
  // https://acme-staging-v02.api.letsencrypt.org/directory
 | 
			
		||||
, server: 'https://acme-v02.api.letsencrypt.org/directory'
 | 
			
		||||
//, debug: true
 | 
			
		||||
}).listen(80, 443);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
  // Where the certs will be saved, MUST have write access
 | 
			
		||||
, configDir: '~/.config/acme/'
 | 
			
		||||
`app.js`:
 | 
			
		||||
```js
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
  // You MUST change this to a valid email address
 | 
			
		||||
, email: 'john.doe@example.com'
 | 
			
		||||
var express = require('express');
 | 
			
		||||
var app = express();
 | 
			
		||||
 | 
			
		||||
  // You MUST change these to valid domains
 | 
			
		||||
  // NOTE: all domains will validated and listed on the certificate
 | 
			
		||||
, approvedDomains: [ 'example.com', 'www.example.com' ]
 | 
			
		||||
 | 
			
		||||
  // You MUST NOT build clients that accept the ToS without asking the user
 | 
			
		||||
, agreeTos: true
 | 
			
		||||
 | 
			
		||||
, app: require('express')().use('/', function (req, res) {
 | 
			
		||||
app.use('/', function (req, res) {
 | 
			
		||||
  res.setHeader('Content-Type', 'text/html; charset=utf-8')
 | 
			
		||||
  res.end('Hello, World!\n\n💚 🔒.js');
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
  // Join the community to get notified of important updates
 | 
			
		||||
, communityMember: true
 | 
			
		||||
// Don't do this:
 | 
			
		||||
// app.listen(3000)
 | 
			
		||||
 | 
			
		||||
  // Contribute telemetry data to the project
 | 
			
		||||
, telemetry: true
 | 
			
		||||
 | 
			
		||||
//, debug: true
 | 
			
		||||
 | 
			
		||||
}).listen(80, 443);
 | 
			
		||||
// Do this instead:
 | 
			
		||||
module.exports = app;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### `communityMember`
 | 
			
		||||
@ -181,7 +177,6 @@ Double check the following:
 | 
			
		||||
  * You MUST set `email` to a **valid address**
 | 
			
		||||
  * MX records must validate (`dig MX example.com` for `'john@example.com'`)
 | 
			
		||||
* **valid DNS records**
 | 
			
		||||
  * You MUST set `approveDomains` to real domains
 | 
			
		||||
  * Must have public DNS records (test with `dig +trace A example.com; dig +trace www.example.com` for `[ 'example.com', 'www.example.com' ]`)
 | 
			
		||||
* **write access**
 | 
			
		||||
  * You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`)
 | 
			
		||||
@ -320,6 +315,10 @@ var glx = require('greenlock-express').create({
 | 
			
		||||
  // Contribute telemetry data to the project
 | 
			
		||||
, telemetry: true
 | 
			
		||||
 | 
			
		||||
  // the default servername to use when the client doesn't specify
 | 
			
		||||
  // (because some IoT devices don't support servername indication)
 | 
			
		||||
, servername: 'example.com'
 | 
			
		||||
 | 
			
		||||
, approveDomains: approveDomains
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -345,6 +344,10 @@ var http01 = require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challen
 | 
			
		||||
function approveDomains(opts, certs, cb) {
 | 
			
		||||
  // This is where you check your database and associated
 | 
			
		||||
  // email addresses with domains and agreements and such
 | 
			
		||||
  // if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); }
 | 
			
		||||
 | 
			
		||||
  // The domains being approved for the first time are listed in opts.domains
 | 
			
		||||
  // Certs being renewed are listed in certs.altnames (if that's useful)
 | 
			
		||||
 | 
			
		||||
  // Opt-in to submit stats and get important updates
 | 
			
		||||
  opts.communityMember = true;
 | 
			
		||||
@ -352,11 +355,6 @@ function approveDomains(opts, certs, cb) {
 | 
			
		||||
  // If you wish to replace the default challenge plugin, you may do so here
 | 
			
		||||
  opts.challenges = { 'http-01': http01 };
 | 
			
		||||
 | 
			
		||||
  // The domains being approved for the first time are listed in opts.domains
 | 
			
		||||
  // Certs being renewed are listed in certs.altnames
 | 
			
		||||
  if (certs) {
 | 
			
		||||
    opts.domains = [certs.subject].concat(certs.altnames);
 | 
			
		||||
  }
 | 
			
		||||
  opts.email = 'john.doe@example.com';
 | 
			
		||||
  opts.agreeTos = true;
 | 
			
		||||
 | 
			
		||||
@ -388,11 +386,10 @@ require('https').createServer(glx.httpsOptions, app).listen(443, function () {
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**Security Warning**:
 | 
			
		||||
**Security**:
 | 
			
		||||
 | 
			
		||||
If you don't do proper checks in `approveDomains(opts, certs, cb)`
 | 
			
		||||
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.
 | 
			
		||||
Greenlock will do a self-check on all domain registrations
 | 
			
		||||
to prevent you from hitting rate limits.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# API
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										130
									
								
								index.js
									
									
									
									
									
								
							
							
						
						
									
										130
									
								
								index.js
									
									
									
									
									
								
							@ -30,7 +30,7 @@ module.exports.create = function (opts) {
 | 
			
		||||
    console.error(e.code + ": '" + e.address + ":" + e.port + "'");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _listen(plainPort, plain) {
 | 
			
		||||
  function _createPlain(plainPort) {
 | 
			
		||||
    if (!plainPort) { plainPort = 80; }
 | 
			
		||||
 | 
			
		||||
    var parts = String(plainPort).split(':');
 | 
			
		||||
@ -41,14 +41,80 @@ module.exports.create = function (opts) {
 | 
			
		||||
    var server;
 | 
			
		||||
    var validHttpPort = (parseInt(p, 10) >= 0);
 | 
			
		||||
 | 
			
		||||
    function tryPlain() {
 | 
			
		||||
    if (addr) { args[1] = addr; }
 | 
			
		||||
    if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
 | 
			
		||||
      console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    server = require('http').createServer(
 | 
			
		||||
      greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')()))
 | 
			
		||||
    );
 | 
			
		||||
    httpType = 'http';
 | 
			
		||||
 | 
			
		||||
    return { server: server, listen: function () { return new Promise(function (resolve, reject) {
 | 
			
		||||
      args[0] = p;
 | 
			
		||||
      args.push(function () {
 | 
			
		||||
        if (!greenlock.servername) {
 | 
			
		||||
          if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) {
 | 
			
		||||
            greenlock.servername = greenlock.approvedDomains[0];
 | 
			
		||||
          }
 | 
			
		||||
          if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) {
 | 
			
		||||
            greenlock.servername = greenlock.approvedDomains[0];
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (!greenlock.servername) {
 | 
			
		||||
          resolve(null);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) {
 | 
			
		||||
          if (certs) {
 | 
			
		||||
            console.info("Using '%s' as default certificate", greenlock.servername);
 | 
			
		||||
            return {
 | 
			
		||||
              key: Buffer.from(certs.privkey, 'ascii')
 | 
			
		||||
            , cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii')
 | 
			
		||||
            };
 | 
			
		||||
          }
 | 
			
		||||
          console.info("Fetching certificate for '%s' to use as default for HTTPS server...", greenlock.servername);
 | 
			
		||||
          return new PromiseA(function (resolve, reject) {
 | 
			
		||||
            // using SNICallback because all options will be set
 | 
			
		||||
            greenlock.tlsOptions.SNICallback(greenlock.servername, function (err/*, secureContext*/) {
 | 
			
		||||
              if (err) { reject(err); return; }
 | 
			
		||||
              return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) {
 | 
			
		||||
                resolve({
 | 
			
		||||
                  key: Buffer.from(certs.privkey, 'ascii')
 | 
			
		||||
                , cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii')
 | 
			
		||||
                });
 | 
			
		||||
              }).catch(reject);
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
        }).then(resolve).catch(reject);
 | 
			
		||||
      });
 | 
			
		||||
      server.listen.apply(server, args).on('error', function (e) {
 | 
			
		||||
        if (server.listenerCount('error') < 2) {
 | 
			
		||||
          console.warn("Did not successfully create http server and bind to port '" + p + "':");
 | 
			
		||||
          explainError(e);
 | 
			
		||||
          process.exit(41);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }); } };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function _create(port) {
 | 
			
		||||
    if (!port) { port = 443; }
 | 
			
		||||
 | 
			
		||||
    var parts = String(port).split(':');
 | 
			
		||||
    var p = parts.pop();
 | 
			
		||||
    var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, '');
 | 
			
		||||
    var args = [];
 | 
			
		||||
    var httpType;
 | 
			
		||||
    var server;
 | 
			
		||||
    var validHttpPort = (parseInt(p, 10) >= 0);
 | 
			
		||||
 | 
			
		||||
    if (addr) { args[1] = addr; }
 | 
			
		||||
    if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
 | 
			
		||||
      console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function trySecure() {
 | 
			
		||||
    var https;
 | 
			
		||||
    try {
 | 
			
		||||
      https = require('spdy');
 | 
			
		||||
@ -58,6 +124,31 @@ module.exports.create = function (opts) {
 | 
			
		||||
      https = require('https');
 | 
			
		||||
      httpType = 'https';
 | 
			
		||||
    }
 | 
			
		||||
    var sniCallback = greenlock.tlsOptions.SNICallback;
 | 
			
		||||
    greenlock.tlsOptions.SNICallback = function (domain, cb) {
 | 
			
		||||
      sniCallback(domain, function (err, context) {
 | 
			
		||||
        cb(err, context);
 | 
			
		||||
        if (!context || server._hasDefaultSecureContext) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        return greenlock.check({ domains: [ domain ] }).then(function (certs) {
 | 
			
		||||
          // ignore the case that check doesn't have all the right args here
 | 
			
		||||
          // to get the same certs that it just got (eventually the right ones will come in)
 | 
			
		||||
          if (!certs) { return; }
 | 
			
		||||
          console.info("Using '%s' as default certificate", domain);
 | 
			
		||||
          server.setSecureContext({
 | 
			
		||||
            key: Buffer.from(certs.privkey, 'ascii')
 | 
			
		||||
          , cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii')
 | 
			
		||||
          });
 | 
			
		||||
          server._hasDefaultSecureContext = true;
 | 
			
		||||
        }).catch(function (/*e*/) {
 | 
			
		||||
          // this may be that the test.example.com was requested, but it's listed
 | 
			
		||||
          // on the cert for demo.example.com which is in its own directory, not the other
 | 
			
		||||
          //console.warn("Unusual error: couldn't get newly authorized certificate:");
 | 
			
		||||
          //console.warn(e.message);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
    server = https.createServer(
 | 
			
		||||
      greenlock.tlsOptions
 | 
			
		||||
    , greenlock.middleware.sanitizeHost(function (req, res) {
 | 
			
		||||
@ -77,17 +168,10 @@ module.exports.create = function (opts) {
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
    server.type = httpType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (addr) { args[1] = addr; }
 | 
			
		||||
    if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
 | 
			
		||||
      console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
 | 
			
		||||
    }
 | 
			
		||||
    if (plain) { tryPlain(); } else { trySecure(); }
 | 
			
		||||
 | 
			
		||||
    var promise = new PromiseA(function (resolve) {
 | 
			
		||||
    return { server: server, listen: function () { return new PromiseA(function (resolve) {
 | 
			
		||||
      args[0] = p;
 | 
			
		||||
      args.push(function () { resolve(server); });
 | 
			
		||||
      args.push(function () { resolve(/*server*/); });
 | 
			
		||||
      server.listen.apply(server, args).on('error', function (e) {
 | 
			
		||||
        if (server.listenerCount('error') < 2) {
 | 
			
		||||
          console.warn("Did not successfully create http server and bind to port '" + p + "':");
 | 
			
		||||
@ -95,10 +179,7 @@ module.exports.create = function (opts) {
 | 
			
		||||
          process.exit(41);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    promise.server = server;
 | 
			
		||||
    return promise;
 | 
			
		||||
    }); } };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // NOTE: 'greenlock' is just 'opts' renamed
 | 
			
		||||
@ -111,7 +192,6 @@ module.exports.create = function (opts) {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  opts.listen = function (plainPort, port, fnPlain, fn) {
 | 
			
		||||
    var promises = [];
 | 
			
		||||
    var server;
 | 
			
		||||
    var plainServer;
 | 
			
		||||
 | 
			
		||||
@ -122,13 +202,18 @@ module.exports.create = function (opts) {
 | 
			
		||||
      fnPlain = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    promises.push(_listen(plainPort, true));
 | 
			
		||||
    promises.push(_listen(port, false));
 | 
			
		||||
    var obj1 = _createPlain(plainPort, true);
 | 
			
		||||
    var obj2 = _create(port, false);
 | 
			
		||||
 | 
			
		||||
    server = promises[1].server;
 | 
			
		||||
    plainServer = promises[0].server;
 | 
			
		||||
    plainServer = obj1.server;
 | 
			
		||||
    server = obj2.server;
 | 
			
		||||
 | 
			
		||||
    PromiseA.all(promises).then(function () {
 | 
			
		||||
    server.then = obj1.listen().then(function (tlsOptions) {
 | 
			
		||||
      if (tlsOptions) {
 | 
			
		||||
        server.setSecureContext(tlsOptions);
 | 
			
		||||
        server._hasDefaultSecureContext = true;
 | 
			
		||||
      }
 | 
			
		||||
      return obj2.listen().then(function () {
 | 
			
		||||
        // Report plain http status
 | 
			
		||||
        if ('function' === typeof fnPlain) {
 | 
			
		||||
          fnPlain.apply(plainServer);
 | 
			
		||||
@ -144,6 +229,7 @@ module.exports.create = function (opts) {
 | 
			
		||||
          console.info('[:' + (server.address().port || server.address()) + "] Serving " + server.type);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }).then;
 | 
			
		||||
 | 
			
		||||
    server.unencrypted = plainServer;
 | 
			
		||||
    return server;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "greenlock-express",
 | 
			
		||||
  "version": "2.5.0",
 | 
			
		||||
  "version": "2.6.3",
 | 
			
		||||
  "description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "homepage": "https://git.coolaj86.com/coolaj86/greenlock-express.js",
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
    "example": "examples"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "greenlock": "^2.5.0",
 | 
			
		||||
    "greenlock": "^2.6.6",
 | 
			
		||||
    "le-challenge-fs": "^2.0.8",
 | 
			
		||||
    "le-sni-auto": "^2.1.4",
 | 
			
		||||
    "le-store-certbot": "^2.1.0",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user