diff --git a/examples/vhost.js b/examples/vhost.js index cccd272..977b763 100644 --- a/examples/vhost.js +++ b/examples/vhost.js @@ -1,86 +1,114 @@ #!/usr/bin/env node 'use strict'; -// This is an example for virtual hosting +/////////////////// +// vhost example // +/////////////////// + +// +// virtual hosting example +// -var email = 'john@example.com'; // The prefix where sites go by name. // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path var srv = '/srv/www/'; - +var path = require('path'); var fs = require('fs'); var finalhandler = require('finalhandler'); var serveStatic = require('serve-static'); -var path = require('path'); -// Allowed characters are a-z,0-9,.,-,_ with TLDs being alpha-only //var glx = require('greenlock-express') var glx = require('../').create({ - // Let's Encrypt v2 is ACME draft 11 - version: 'draft-11' + version: 'draft-11' // Let's Encrypt v2 is ACME draft 11 -, server: 'https://acme-v02.api.letsencrypt.org/directory' - // Note: If at first you don't succeed, stop and switch to staging - // https://acme-staging-v02.api.letsencrypt.org/directory +, server: 'https://acme-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging + // https://acme-staging-v02.api.letsencrypt.org/directory -, approveDomains: function (opts, certs, cb) { - // In this example the filesystem is our "database". - // We check in /srv/www for whateve.com and if it exists, it's allowed +, configDir: '~/.config/acme/' // You MUST have access to write to directory where certs + // are saved. ex: /home/foouser/.config/acme +, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the + // domain name here and reject invalid ones - // The domains being approved for the first time are listed in opts.domains +, app: myVhostApp // Any node-style http app (i.e. express, koa, hapi, rill) - // Certs being renewed are listed in certs.altnames - if (certs) { - opts.domains = certs.altnames; - cb(null, { options: opts, certs: certs }); - return; - } - - // SECURITY Greenlock validates opts.domains ahead-of-time - var hostdir = path.join(srv, opts.domains[0]); - // TODO could test for www/no-www both in directory and IP - fs.readdir(hostdir, function (err, nodes) { - var e; - if (err || !nodes) { - e = new Error("rejecting '" + opts.domains[0] + "' because '" + hostdir + "' could not be read"); - console.error(err); - console.error(e); - cb(e); - return; - } - - // TODO check for some sort of htaccess.json and use email in that - // NOTE: you can also change other options such as `challengeType` and `challenge` - // opts.challengeType = 'http-01'; - // opts.challenge = require('le-challenge-fs').create({}); - opts.email = email; - opts.agreeTos = true; - cb(null, { options: opts, certs: certs }); - }); - - } - - // You MUST have access to write to directory where certs are saved - // ex: /home/foouser/acme/etc -, configDir: '~/.config/acme/' - -, app: function (req, res) { - // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access - console.log(req.headers.host); - var hostname = req.headers.host; - - var serve = serveStatic(path.join(srv, hostname), { redirect: true }); - serve(req, res, finalhandler(req, res)); - } - - // Get notified of important updates and help me make greenlock better -, communityMember: true + /* CHANGE TO A VALID EMAIL */ +, email:'jon@example.com' // Email for Let's Encrypt account and Greenlock Security +, agreeTos: true // Accept Let's Encrypt ToS +, communityMember: true // Join Greenlock to get important updates, no spam //, debug: true }); var server = glx.listen(80, 443); +server.on('listening', function () { + console.info(server.type + " listening on", server.address()); +}); + +// [SECURITY] +// Since v2.4.0+ Greenlock proactively protects against +// SQL injection and timing attacks by rejecting invalid domain names, +// but it's up to you to make sure that you accept opts.domain BEFORE +// an attempt is made to issue a certificate for it. +function myApproveDomains(opts, certs, cb) { + + // In this example the filesystem is our "database". + // We check in /srv/www/ for opts.domain (i.e. "example.com") and only proceed if it exists. + console.log(opts.domain); + + // Check that the hosting domain exists on the file system. + var hostdir = path.join(srv, opts.domain); + fs.readdir(hostdir, function (err, nodes) { + var e; + if (err || !nodes) { + e = new Error("rejecting '" + opts.domains[0] + "' because '" + hostdir + "' could not be read"); + console.error(e); + console.error(err); + cb(e); + return; + } + + // You could put a variety of configuration details alongside the vhost folder + // For example, /srv/www/example.com.json could describe the following: + + // If you have multiple domains grouped together, you can list them on the same certificate + // opts.domains = [ 'example.com', 'www.example.com', 'api.example.com', 'sso.example.com' ] + + // You can also change other options on-the-fly + // (approveDomains is called after the in-memory certificates cache is checked, but before any ACME requests) + + // opts.email = "jon@example.com" + // opts.agreeTos = true; + // opts.challengeType = 'http-01'; + // opts.challenge = require('le-challenge-fs').create({}); + cb(null, { options: opts, certs: certs }); + }); + +} + +// [SECURITY] +// Since v2.4.0+ Greenlock Express will proactively protect against +// SQL injection and timing attacks by rejecting invalid domain names +// in Host headers. +// It will also make them lowercase and protect against "domain fronting". +// However, it's up to you to make sure you actually have a domain to serve :) +var servers = {}; +function myVhostApp(req, res) { + var hostname = req.headers.host; + var srvpath = path.join(srv, hostname); + console.log('vhost for', req.headers.host); + + if (!servers[hostname]) { + try { + fs.accessSync(srvpath); + servers[hostname] = serveStatic(srvpath, { redirect: true }); + } catch(e) { + finalhandler(req, res); + } + } + + servers[hostname](req, res, finalhandler(req, res)); +}