make Prettier

This commit is contained in:
AJ ONeal 2019-06-03 03:47:07 -06:00
parent a49ccb7398
commit c73ad565a3
21 changed files with 1905 additions and 1777 deletions

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"bracketSpacing": true,
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": true
}

205
README.md
View File

@ -78,18 +78,18 @@ Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu
<a href="https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk"><img src="https://i.imgur.com/Y8ix6Ts.png" title="QuickStart Video" alt="YouTube Video Preview" /></a> <a href="https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk"><img src="https://i.imgur.com/Y8ix6Ts.png" title="QuickStart Video" alt="YouTube Video Preview" /></a>
* [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro - [0:00](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=0) - Intro
* [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example - [2:22](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk#t=142) - Demonstrating QuickStart Example
* [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas - [6:37](https://www.youtube.com/watch?v=e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk?t=397) - Troubleshooting / Gotchas
#### Beyond the QuickStart (Part 2) #### Beyond the QuickStart (Part 2)
* [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project - [1:00](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=60) - Bringing Greenlock into an Existing Express Project
* [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback - [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback
#### Security Concerns (Part 3) #### Security Concerns (Part 3)
* [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation - [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation
### Working Example Code ### Working Example Code
@ -110,35 +110,39 @@ node greenlock-express.js/examples/simple.js
All you have to do is start the webserver and then visit it at its domain name. All you have to do is start the webserver and then visit it at its domain name.
`server.js`: `server.js`:
```javascript
'use strict';
require('greenlock-express').create({ ```javascript
email: 'john.doe@example.com' // The email address of the ACME user / hosting provider "use strict";
, agreeTos: true // You must accept the ToS as the host which handles the certs
, configDir: '~/.config/acme/' // Writable directory where certs will be saved require("greenlock-express")
, communityMember: true // Join the community to get notified of important updates .create({
, telemetry: true // Contribute telemetry data to the project 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
// Using your express app: // Using your express app:
// simply export it as-is, then include it here // simply export it as-is, then include it here
, app: require('./app.js') app: require("./app.js")
//, debug: true //, debug: true
}).listen(80, 443); })
.listen(80, 443);
``` ```
`app.js`: `app.js`:
```js
'use strict';
var express = require('express'); ```js
"use strict";
var express = require("express");
var app = express(); var app = express();
app.use('/', function (req, res) { app.use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8') res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}) });
// Don't do this: // Don't do this:
// app.listen(3000) // app.listen(3000)
@ -166,29 +170,29 @@ You can see our full privacy policy at <https://greenlock.domains/legal/#privacy
Double check the following: Double check the following:
* **Public Facing IP** for `http-01` challenges - **Public Facing IP** for `http-01` challenges
* Are you running this *as* a public-facing webserver (good)? or localhost (bad)? - Are you running this _as_ a public-facing webserver (good)? or localhost (bad)?
* Does `ifconfig` show a public address (good)? or a private one - 10.x, 192.168.x, etc (bad)? - Does `ifconfig` show a public address (good)? or a private one - 10.x, 192.168.x, etc (bad)?
* If you're on a non-public server, are you using the `dns-01` challenge? - If you're on a non-public server, are you using the `dns-01` challenge?
* **correct ACME version** - **correct ACME version**
* Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'` - Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'`
* Let's Encrypt v1 must use `version: 'v01'` - Let's Encrypt v1 must use `version: 'v01'`
* **valid email** - **valid email**
* You MUST set `email` to a **valid address** - You MUST set `email` to a **valid address**
* MX records must validate (`dig MX example.com` for `'john@example.com'`) - MX records must validate (`dig MX example.com` for `'john@example.com'`)
* **valid DNS records** - **valid DNS records**
* Must have public DNS records (test with `dig +trace A example.com; dig +trace www.example.com` for `[ 'example.com', 'www.example.com' ]`) - 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** - **write access**
* You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`) - You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`)
* **port binding privileges** - **port binding privileges**
* You MUST be able to bind to ports 80 and 443 - You MUST be able to bind to ports 80 and 443
* You can do this via `sudo` or [`setcap`](https://gist.github.com/firstdoit/6389682) - You can do this via `sudo` or [`setcap`](https://gist.github.com/firstdoit/6389682)
* **API limits** - **API limits**
* You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc - You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc
* **Red Lock, Untrusted** - **Red Lock, Untrusted**
* You MUST use the **production** server url, not staging - You MUST use the **production** server url, not staging
* The API URL should not have 'acme-staging-v02', but should have 'acme-v02' - The API URL should not have 'acme-staging-v02', but should have 'acme-v02'
* Delete the `configDir` used for getting certificates in staging - Delete the `configDir` used for getting certificates in staging
### Production vs Staging ### Production vs Staging
@ -211,7 +215,7 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## Working Examples ## Working Examples
| Example | Location + Description | | Example | Location + Description |
|:---------------:|:---------:| | :-------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| **QuickStart** | [examples/quickstart.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/quickstart.js) uses the fewest options and accepts all default settings. It's guaranteed to work for you. | | **QuickStart** | [examples/quickstart.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/quickstart.js) uses the fewest options and accepts all default settings. It's guaranteed to work for you. |
| Production | [examples/production.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/production.js) shows how to require an express app (or other middleware system), expand the `approveDomains` callback, provides an example database shim, and exposes the server instance. | | Production | [examples/production.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/production.js) shows how to require an express app (or other middleware system), expand the `approveDomains` callback, provides an example database shim, and exposes the server instance. |
| Virtual&nbsp;Hosting | [examples/vhost.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/vhost.js) shows how to dynamically secure and serve domains based on their existance on the file system. | | Virtual&nbsp;Hosting | [examples/vhost.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/vhost.js) shows how to dynamically secure and serve domains based on their existance on the file system. |
@ -231,7 +235,7 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## HTTP-01 Challenges ## HTTP-01 Challenges
| | Plugin | | | Plugin |
|:--------------:|:---------:| | :--------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) | | **Default (fs)** | [acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js) |
| **Manual (cli)** | [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js) | | **Manual (cli)** | [acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js) |
| AWS S3 | [acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js) | | AWS S3 | [acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js) |
@ -239,11 +243,10 @@ https://acme-staging-v02.api.letsencrypt.org/directory
| - | Build Your Own <br> [acme-http-01-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) | | - | Build Your Own <br> [acme-http-01-challenge-test](https://git.rootprojects.org/root/acme-challenge-test.js) |
| Full List | Search [acme-http-01-](https://www.npmjs.com/search?q=acme-http-01-) on npm (or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) for older versions) | | Full List | Search [acme-http-01-](https://www.npmjs.com/search?q=acme-http-01-) on npm (or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) for older versions) |
## DNS-01 Challenges ## DNS-01 Challenges
| | Plugin | | | Plugin |
|:--------------:|:---------:| | :--------------: | :----------------------------------------------------------------------------------------------------------------------------------------: |
| **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) | | **Manual (cli)** | [acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js) |
| AWS Route 53 | [thadeetrompetter/le-challenge-route53](https://github.com/thadeetrompetter/le-challenge-route53) | | AWS Route 53 | [thadeetrompetter/le-challenge-route53](https://github.com/thadeetrompetter/le-challenge-route53) |
| CloudFlare | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) | | CloudFlare | [buschtoens/le-challenge-cloudflare](https://github.com/buschtoens/le-challenge-cloudflare) |
@ -256,12 +259,12 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## Account & Certificate Storage ## Account & Certificate Storage
| | Plugin | | | Plugin |
|:--------------:|:---------:| | :------------------: | :---------------------------------------------------------------------------------------------------: |
| **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) | | **Simplest** | [greenlock-store-fs](https://git.rootprojects.org/root/greenlock-store-fs.js) |
| certbot (v2 default) | [le-store-certbot](https://git.coolaj86.com/coolaj86/le-store-certbot.js) | | certbot (v2 default) | [le-store-certbot](https://git.coolaj86.com/coolaj86/le-store-certbot.js) |
| AWS S3 | [gl-store-s3](https://git.rootprojects.org/root/gl-store-s3.js) | | AWS S3 | [gl-store-s3](https://git.rootprojects.org/root/gl-store-s3.js) |
| Consul | [sebastian-software/le-store-consul](https://github.com/sebastian-software/le-store-consul) | | Consul | [sebastian-software/le-store-consul](https://github.com/sebastian-software/le-store-consul) |
| json (fs) | [paulgrove/le-store-simple-fs](https://github.com/paulgrove/le-store-simple-fs) | json (fs) | [paulgrove/le-store-simple-fs](https://github.com/paulgrove/le-store-simple-fs) |
| Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) | | Redis | [digitalbazaar/le-store-redis](https://github.com/digitalbazaar/le-store-redis) |
| - | Build Your Own <br> [greenlock-store-test](https://git.rootprojects.org/root/greenlock-store-test.js) | | - | Build Your Own <br> [greenlock-store-test](https://git.rootprojects.org/root/greenlock-store-test.js) |
| Full List | Search [le-store-](https://www.npmjs.com/search?q=le-store-) on npm | | Full List | Search [le-store-](https://www.npmjs.com/search?q=le-store-) on npm |
@ -269,12 +272,11 @@ https://acme-staging-v02.api.letsencrypt.org/directory
## Auto-SNI ## Auto-SNI
| | Plugin | | | Plugin |
|:-----------:|:---------:| | :---------: | :-------------------------------------------------------------: |
| **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) | | **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) |
(you probably wouldn't need or want to replace this) (you probably wouldn't need or want to replace this)
**Bugs**: Please report bugs with the community plugins to the appropriate owner first, then here if you don't get a response. **Bugs**: Please report bugs with the community plugins to the appropriate owner first, then here if you don't get a response.
# Usage # Usage
@ -300,30 +302,31 @@ node greenlock-express.js/examples/normal.js
It looks a little more like this: It looks a little more like this:
`serve.js`: `serve.js`:
```javascript ```javascript
'use strict'; "use strict";
// returns an instance of greenlock.js with additional helper methods // returns an instance of greenlock.js with additional helper methods
var glx = require('greenlock-express').create({ var glx = require("greenlock-express").create({
server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging: // Note: If at first you don't succeed, stop and switch to staging:
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
, version: 'draft-11' // Let's Encrypt v2 (ACME v2) version: "draft-11", // Let's Encrypt v2 (ACME v2)
// If you wish to replace the default account and domain key storage plugin // If you wish to replace the default account and domain key storage plugin
, store: require('le-store-certbot').create({ store: require("le-store-certbot").create({
configDir: require('path').join(require('os').homedir(), 'acme', 'etc') configDir: require("path").join(require("os").homedir(), "acme", "etc"),
, webrootPath: '/tmp/acme-challenges' webrootPath: "/tmp/acme-challenges"
}) }),
// Contribute telemetry data to the project // Contribute telemetry data to the project
, telemetry: true telemetry: true,
// the default servername to use when the client doesn't specify // the default servername to use when the client doesn't specify
// (because some IoT devices don't support servername indication) // (because some IoT devices don't support servername indication)
, servername: 'example.com' servername: "example.com",
, approveDomains: approveDomains approveDomains: approveDomains
}); });
var server = glx.listen(80, 443, function() { var server = glx.listen(80, 443, function() {
@ -341,10 +344,10 @@ plainServer.on('error', function (err) { ... });
``` ```
The Automatic Certificate Issuance is initiated via SNI (`httpsOptions.SNICallback`). The Automatic Certificate Issuance is initiated via SNI (`httpsOptions.SNICallback`).
For security, domain validation MUST have an approval callback in *production*. For security, domain validation MUST have an approval callback in _production_.
```javascript ```javascript
var http01 = require('le-challenge-fs').create({ webrootPath: '/tmp/acme-challenges' }); var http01 = require("le-challenge-fs").create({ webrootPath: "/tmp/acme-challenges" });
function approveDomains(opts, certs, cb) { function approveDomains(opts, certs, cb) {
// This is where you check your database and associated // This is where you check your database and associated
// email addresses with domains and agreements and such // email addresses with domains and agreements and such
@ -357,9 +360,9 @@ function approveDomains(opts, certs, cb) {
opts.communityMember = true; opts.communityMember = true;
// If you wish to replace the default challenge plugin, you may do so here // If you wish to replace the default challenge plugin, you may do so here
opts.challenges = { 'http-01': http01 }; opts.challenges = { "http-01": http01 };
opts.email = 'john.doe@example.com'; opts.email = "john.doe@example.com";
opts.agreeTos = true; opts.agreeTos = true;
// NOTE: you can also change other options such as `challengeType` and `challenge` // NOTE: you can also change other options such as `challengeType` and `challenge`
@ -370,22 +373,23 @@ function approveDomains(opts, certs, cb) {
} }
``` ```
```javascript ```javascript
// handles acme-challenge and redirects to https // handles acme-challenge and redirects to https
require('http').createServer(glx.middleware(require('redirect-https')())).listen(80, function () { require("http")
.createServer(glx.middleware(require("redirect-https")()))
.listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address()); console.log("Listening for ACME http-01 challenges on", this.address());
}); });
var app = require("express")();
app.use("/", function(req, res) {
var app = require('express')(); res.end("Hello, World!");
app.use('/', function (req, res) {
res.end('Hello, World!');
}); });
// handles your app // handles your app
require('https').createServer(glx.httpsOptions, app).listen(443, function () { require("https")
.createServer(glx.httpsOptions, app)
.listen(443, function() {
console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address());
}); });
``` ```
@ -395,7 +399,6 @@ require('https').createServer(glx.httpsOptions, app).listen(443, function () {
Greenlock will do a self-check on all domain registrations Greenlock will do a self-check on all domain registrations
to prevent you from hitting rate limits. to prevent you from hitting rate limits.
# API # API
This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO).
@ -405,35 +408,35 @@ The API is actually located at [greenlock.js options](https://git.rootprojects.o
The only "API" consists of two options, the rest is just a wrapper around `greenlock.js` to take LOC from 15 to 5: The only "API" consists of two options, the rest is just a wrapper around `greenlock.js` to take LOC from 15 to 5:
* `opts.app` An express app in the format `function (req, res) { ... }` (no `next`). - `opts.app` An express app in the format `function (req, res) { ... }` (no `next`).
* `server = glx.listen(plainAddr, tlsAddr, onListen)` Accepts port numbers (or arrays of port numbers) to listen on, returns secure server. - `server = glx.listen(plainAddr, tlsAddr, onListen)` Accepts port numbers (or arrays of port numbers) to listen on, returns secure server.
* `listen(80, 443)` - `listen(80, 443)`
* `listen(80, 443, onListenSecure)` - `listen(80, 443, onListenSecure)`
* `listen(80, 443, onListenPlain, onListenSecure)` - `listen(80, 443, onListenPlain, onListenSecure)`
* `listen('localhost:80', '0.0.0.0:443')` - `listen('localhost:80', '0.0.0.0:443')`
* `listen('[::1]:80', '[::]:443')` - `listen('[::1]:80', '[::]:443')`
* `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')` - `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')`
Brief overview of some simple options for `greenlock.js`: Brief overview of some simple options for `greenlock.js`:
* `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production - `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production
* `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2) - `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2)
* `opts.email` The default email to use to accept agreements. - `opts.email` The default email to use to accept agreements.
* `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first. - `opts.agreeTos` When set to `true`, this always accepts the LetsEncrypt TOS. When a string it checks the agreement url first.
* `opts.communityMember` Join the community to get notified of important updates and help make greenlock better - `opts.communityMember` Join the community to get notified of important updates and help make greenlock better
* `opts.approveDomains` can be either of: - `opts.approveDomains` can be either of:
* An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]` - An explicit array of allowed domains such as `[ 'example.com', 'www.example.com' ]`
* A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above) - A callback `function (opts, certs, cb) { cb(null, { options: opts, certs: certs }); }` for setting `email`, `agreeTos`, `domains`, etc (as shown in usage example above)
* `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate. - `opts.renewWithin` is the **maximum** number of days (in ms) before expiration to renew a certificate.
* `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate. - `opts.renewBy` is the **minimum** number of days (in ms) before expiration to renew a certificate.
## Supported ACME versions ## Supported ACME versions
* Let's Encrypt v1 (aka v01) - Let's Encrypt v1 (aka v01)
* Let's Encrypt v2 (aka v02 or ACME draft 11) - Let's Encrypt v2 (aka v02 or ACME draft 11)
* ACME draft 11 (ACME v2 is a misnomer) - ACME draft 11 (ACME v2 is a misnomer)
* Wildcard domains (via dns-01 challenges) - Wildcard domains (via dns-01 challenges)
* `*.example.com` - `*.example.com`
<small>tags: letsencrypt acme free ssl automated https node express.js</small> <small>tags: letsencrypt acme free ssl automated https node express.js</small>

View File

@ -1,9 +1,9 @@
'use strict'; "use strict";
var path = require('path'); var path = require("path");
module.exports = { module.exports = {
email: 'jon.doe@example.com' email: "jon.doe@example.com",
, configDir: path.join(__dirname, 'acme') configDir: path.join(__dirname, "acme"),
, srv: '/srv/www/' srv: "/srv/www/",
, api: '/srv/api/' api: "/srv/api/"
}; };

View File

@ -1,75 +1,75 @@
'use strict'; "use strict";
// npm install spdy@3.x // npm install spdy@3.x
//var Greenlock = require('greenlock-express') //var Greenlock = require('greenlock-express')
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' version: "draft-11",
, server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST change this to a valid email address
, email: 'jon@example.com' email: "jon@example.com",
// You MUST NOT build clients that accept the ToS without asking the user // You MUST NOT build clients that accept the ToS without asking the user
, agreeTos: true agreeTos: true,
// You MUST change these to valid domains // You MUST change these to valid domains
// NOTE: all domains will validated and listed on the certificate // NOTE: all domains will validated and listed on the certificate
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
// You MUST have access to write to directory where certs are saved // You MUST have access to write to directory where certs are saved
// ex: /home/foouser/acme/etc // ex: /home/foouser/acme/etc
, configDir: '~/.config/acme/' configDir: "~/.config/acme/",
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true
//, debug: true //, debug: true
}); });
//////////////////////// ////////////////////////
// http-01 Challenges // // http-01 Challenges //
//////////////////////// ////////////////////////
// http-01 challenge happens over http/1.1, not http2 // http-01 challenge happens over http/1.1, not http2
var redirectHttps = require('redirect-https')(); var redirectHttps = require("redirect-https")();
var acmeChallengeHandler = greenlock.middleware(function(req, res) { var acmeChallengeHandler = greenlock.middleware(function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('<h1>Hello, ⚠️ Insecure World!</h1><a>Visit Secure Site</a>' res.end(
+ '<script>document.querySelector("a").href=window.location.href.replace(/^http/i, "https");</script>' "<h1>Hello, ⚠️ Insecure World!</h1><a>Visit Secure Site</a>" +
'<script>document.querySelector("a").href=window.location.href.replace(/^http/i, "https");</script>'
); );
}); });
require('http').createServer(acmeChallengeHandler).listen(80, function () { require("http")
.createServer(acmeChallengeHandler)
.listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address()); console.log("Listening for ACME http-01 challenges on", this.address());
}); });
//////////////////////// ////////////////////////
// http2 via SPDY h2 // // http2 via SPDY h2 //
//////////////////////// ////////////////////////
// spdy is a drop-in replacement for the https API // spdy is a drop-in replacement for the https API
var spdyOptions = Object.assign({}, greenlock.tlsOptions); var spdyOptions = Object.assign({}, greenlock.tlsOptions);
spdyOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
var server = require('spdy').createServer(spdyOptions, require('express')().use('/', function (req, res) { var server = require("spdy").createServer(
res.setHeader('Content-Type', 'text/html; charset=utf-8'); spdyOptions,
res.end('<h1>Hello, 🔐 Secure World!</h1>'); require("express")().use("/", function(req, res) {
})); res.setHeader("Content-Type", "text/html; charset=utf-8");
server.on('error', function (err) { res.end("<h1>Hello, 🔐 Secure World!</h1>");
})
);
server.on("error", function(err) {
console.error(err); console.error(err);
}); });
server.on('listening', function () { server.on("listening", function() {
console.log("Listening for SPDY/http2/https requests on", this.address()); console.log("Listening for SPDY/http2/https requests on", this.address());
}); });
server.listen(443); server.listen(443);

View File

@ -1,29 +1,30 @@
'use strict'; "use strict";
//require('greenlock-express') //require('greenlock-express')
require('../').create({ require("../")
.create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' version: "draft-11",
, server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
, email: 'john.doe@example.com' email: "john.doe@example.com",
, agreeTos: true agreeTos: true,
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
, app: require('express')().use('/', function (req, res) { app: require("express")().use("/", function(req, res) {
res.end('Hello, World!'); res.end("Hello, World!");
}) }),
, renewWithin: (91 * 24 * 60 * 60 * 1000) renewWithin: 91 * 24 * 60 * 60 * 1000,
, renewBy: (90 * 24 * 60 * 60 * 1000) renewBy: 90 * 24 * 60 * 60 * 1000,
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true,
, debug: true debug: true
}).listen(80, 443); })
.listen(80, 443);

View File

@ -1,74 +1,70 @@
'use strict'; "use strict";
//var Greenlock = require('greenlock-express') //var Greenlock = require('greenlock-express')
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' version: "draft-11",
, server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST change this to a valid email address
, email: 'jon@example.com' email: "jon@example.com",
// You MUST NOT build clients that accept the ToS without asking the user // You MUST NOT build clients that accept the ToS without asking the user
, agreeTos: true agreeTos: true,
// You MUST change these to valid domains // You MUST change these to valid domains
// NOTE: all domains will validated and listed on the certificate // NOTE: all domains will validated and listed on the certificate
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
// You MUST have access to write to directory where certs are saved // You MUST have access to write to directory where certs are saved
// ex: /home/foouser/acme/etc // ex: /home/foouser/acme/etc
, configDir: '~/.config/acme/' configDir: "~/.config/acme/",
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true
//, debug: true //, debug: true
}); });
//////////////////////// ////////////////////////
// http-01 Challenges // // http-01 Challenges //
//////////////////////// ////////////////////////
// http-01 challenge happens over http/1.1, not http2 // http-01 challenge happens over http/1.1, not http2
var redirectHttps = require('redirect-https')(); var redirectHttps = require("redirect-https")();
var acmeChallengeHandler = greenlock.middleware(redirectHttps); var acmeChallengeHandler = greenlock.middleware(redirectHttps);
require('http').createServer(acmeChallengeHandler).listen(80, function () { require("http")
.createServer(acmeChallengeHandler)
.listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address()); console.log("Listening for ACME http-01 challenges on", this.address());
}); });
//////////////////////// ////////////////////////
// node.js' http2 api // // node.js' http2 api //
//////////////////////// ////////////////////////
// http2 is a new API with which you would use hapi or koa, not express // http2 is a new API with which you would use hapi or koa, not express
var server = require('http2').createSecureServer(greenlock.tlsOptions); var server = require("http2").createSecureServer(greenlock.tlsOptions);
server.on('error', function (err) { server.on("error", function(err) {
console.error(err); console.error(err);
}); });
// WARNING: Because the middleware don't handle this API style, // WARNING: Because the middleware don't handle this API style,
// the Host headers are unmodified and potentially dangerous // the Host headers are unmodified and potentially dangerous
// (ex: Host: Robert'); DROP TABLE Students;) // (ex: Host: Robert'); DROP TABLE Students;)
server.on('stream', function (stream, headers) { server.on("stream", function(stream, headers) {
console.log(headers); console.log(headers);
stream.respond({ stream.respond({
'content-type': 'text/html' "content-type": "text/html",
, ':status': 200 ":status": 200
}); });
stream.end('Hello, HTTP2 World!'); stream.end("Hello, HTTP2 World!");
}); });
server.on('listening', function () { server.on("listening", function() {
console.log("Listening for http2 requests on", this.address()); console.log("Listening for http2 requests on", this.address());
}); });
server.listen(443); server.listen(443);

View File

@ -1,15 +1,17 @@
'use strict'; "use strict";
var express = require('express'); var express = require("express");
var app = express(); var app = express();
app.use('/', function (req, res) { app.use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}); });
// DO NOT DO app.listen() unless we're testing this directly // DO NOT DO app.listen() unless we're testing this directly
if (require.main === module) { app.listen(3000); } if (require.main === module) {
app.listen(3000);
}
// Instead do export the app: // Instead do export the app:
module.exports = app; module.exports = app;

View File

@ -1,18 +1,17 @@
'use strict'; "use strict";
// //
// My Secure Server // My Secure Server
// //
//var greenlock = require('greenlock-express') //var greenlock = require('greenlock-express')
var greenlock = require('../').create({ var greenlock = require("../").create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
, version: 'draft-11' version: "draft-11",
// You MUST have write access to save certs // You MUST have write access to save certs
, configDir: '~/.config/acme/' configDir: "~/.config/acme/",
// The previous 'simple' example set these values statically, // The previous 'simple' example set these values statically,
// but this example uses approveDomains() to set them dynamically // but this example uses approveDomains() to set them dynamically
@ -21,25 +20,22 @@ var greenlock = require('../').create({
// approveDomains is the right place to check a database for // approveDomains is the right place to check a database for
// email addresses with domains and agreements and such // email addresses with domains and agreements and such
, approveDomains: approveDomains approveDomains: approveDomains,
, app: require('./my-express-app.js') app: require("./my-express-app.js"),
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true
//, debug: true //, debug: true
}); });
var server = greenlock.listen(80, 443); var server = greenlock.listen(80, 443);
// //
// My Secure Database Check // My Secure Database Check
// //
function approveDomains(opts, certs, cb) { function approveDomains(opts, certs, cb) {
// Only one domain is listed with *automatic* registration via SNI // Only one domain is listed with *automatic* registration via SNI
// (it's an array because managed registration allows for multiple domains, // (it's an array because managed registration allows for multiple domains,
// which was the case in the simple example) // which was the case in the simple example)
@ -52,7 +48,10 @@ function approveDomains(opts, certs, cb) {
} }
fooCheckDb(opts.domains, function(err, agree, email) { fooCheckDb(opts.domains, function(err, agree, email) {
if (err) { cb(err); return; } if (err) {
cb(err);
return;
}
// Services SHOULD automatically accept the ToS and use YOUR email // Services SHOULD automatically accept the ToS and use YOUR email
// Clients MUST NOT accept the ToS without asking the user // Clients MUST NOT accept the ToS without asking the user
@ -68,22 +67,21 @@ function approveDomains(opts, certs, cb) {
}); });
} }
// //
// My User / Domain Database // My User / Domain Database
// //
function fooCheckDb(domains, cb) { function fooCheckDb(domains, cb) {
// This is an oversimplified example of how we might implement a check in // This is an oversimplified example of how we might implement a check in
// our database if we have different rules for different users and domains // our database if we have different rules for different users and domains
var domains = [ 'example.com', 'www.example.com' ]; var domains = ["example.com", "www.example.com"];
var userEmail = 'john.doe@example.com'; var userEmail = "john.doe@example.com";
var userAgrees = true; var userAgrees = true;
var passCheck = opts.domains.every(function(domain) { var passCheck = opts.domains.every(function(domain) {
return -1 !== domains.indexOf(domain); return -1 !== domains.indexOf(domain);
}); });
if (!passCheck) { if (!passCheck) {
cb(new Error('domain not allowed')); cb(new Error("domain not allowed"));
} else { } else {
cb(null, userAgrees, userEmail); cb(null, userAgrees, userEmail);
} }

View File

@ -1,37 +1,38 @@
'use strict'; "use strict";
//require('greenlock-express') //require('greenlock-express')
require('../').create({ require("../")
.create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' version: "draft-11",
, server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST change this to a valid email address
, email: 'john.doe@example.com' email: "john.doe@example.com",
// You MUST NOT build clients that accept the ToS without asking the user // You MUST NOT build clients that accept the ToS without asking the user
, agreeTos: true agreeTos: true,
// You MUST change these to valid domains // You MUST change these to valid domains
// NOTE: all domains will validated and listed on the certificate // NOTE: all domains will validated and listed on the certificate
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
// You MUST have access to write to directory where certs are saved // You MUST have access to write to directory where certs are saved
// ex: /home/foouser/acme/etc // ex: /home/foouser/acme/etc
, configDir: '~/.config/acme/' configDir: "~/.config/acme/",
store: require("greenlock-store-fs"),
, app: require('express')().use('/', function (req, res) { app: require("express")().use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}) }),
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true
//, debug: true //, debug: true
})
}).listen(80, 443); .listen(80, 443);

View File

@ -1,4 +1,4 @@
'use strict'; "use strict";
// //
// WARNING: Not for noobs // WARNING: Not for noobs
@ -9,87 +9,96 @@
// This demo is used with tunnel-server.js and tunnel-client.js // This demo is used with tunnel-server.js and tunnel-client.js
// //
var email = 'john.doe@gmail.com'; var email = "john.doe@gmail.com";
var domains = [ 'example.com' ]; var domains = ["example.com"];
var agreeLeTos = true; var agreeLeTos = true;
//var secret = "My Little Brony"; //var secret = "My Little Brony";
var secret = require('crypto').randomBytes(16).toString('hex'); var secret = require("crypto")
.randomBytes(16)
.toString("hex");
require('../').create({ require("../")
version: 'draft-11' .create({
version: "draft-11",
, server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
, email: email email: email,
, agreeTos: agreeLeTos agreeTos: agreeLeTos,
, approveDomains: domains approveDomains: domains,
, configDir: '~/.config/acme/' configDir: "~/.config/acme/",
, app: remoteAccess(secret) app: remoteAccess(secret),
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true
//, debug: true //, debug: true
}).listen(3000, 8443); })
.listen(3000, 8443);
function remoteAccess(secret) { function remoteAccess(secret) {
var express = require('express'); var express = require("express");
var basicAuth = require('express-basic-auth'); var basicAuth = require("express-basic-auth");
var serveIndex = require('serve-index'); var serveIndex = require("serve-index");
var rootIndex = serveIndex('/', { hidden: true, icons: true, view: 'details' }); var rootIndex = serveIndex("/", { hidden: true, icons: true, view: "details" });
var rootFs = express.static('/', { dotfiles: 'allow', redirect: true, index: false }); var rootFs = express.static("/", { dotfiles: "allow", redirect: true, index: false });
var userIndex = serveIndex(require('os').homedir(), { hidden: true, icons: true, view: 'details' }); var userIndex = serveIndex(require("os").homedir(), { hidden: true, icons: true, view: "details" });
var userFs = express.static(require('os').homedir(), { dotfiles: 'allow', redirect: true, index: false }); var userFs = express.static(require("os").homedir(), { dotfiles: "allow", redirect: true, index: false });
var app = express(); var app = express();
var realm = 'Login Required'; var realm = "Login Required";
var myAuth = basicAuth({ var myAuth = basicAuth({
users: { 'root': secret, 'user': secret } users: { root: secret, user: secret },
, challenge: true challenge: true,
, realm: realm realm: realm,
, unauthorizedResponse: function (/*req*/) { unauthorizedResponse: function(/*req*/) {
return 'Unauthorized <a href="/">Home</a>'; return 'Unauthorized <a href="/">Home</a>';
} }
}); });
app.get('/', function (req, res) { app.get("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end( res.end('<a href="/browse/">View Files</a>' + "&nbsp; | &nbsp;" + '<a href="/logout/">Logout</a>');
'<a href="/browse/">View Files</a>'
+ '&nbsp; | &nbsp;'
+ '<a href="/logout/">Logout</a>'
);
}); });
app.use('/logout', function (req, res) { app.use("/logout", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"'); res.setHeader("WWW-Authenticate", 'Basic realm="' + realm + '"');
res.statusCode = 401; res.statusCode = 401;
//res.setHeader('Location', '/'); //res.setHeader('Location', '/');
res.end('Logged out &nbsp; | &nbsp; <a href="/">Home</a>'); res.end('Logged out &nbsp; | &nbsp; <a href="/">Home</a>');
}); });
app.use('/browse', myAuth); app.use("/browse", myAuth);
app.use('/browse', function (req, res, next) { app.use("/browse", function(req, res, next) {
if ('root' === req.auth.user) { rootFs(req, res, function () { rootIndex(req, res, next); }); return; } if ("root" === req.auth.user) {
if ('user' === req.auth.user) { userFs(req, res, function () { userIndex(req, res, next); }); return; } rootFs(req, res, function() {
res.end('Sad Panda'); rootIndex(req, res, next);
});
return;
}
if ("user" === req.auth.user) {
userFs(req, res, function() {
userIndex(req, res, next);
});
return;
}
res.end("Sad Panda");
}); });
console.log(''); console.log("");
console.log(''); console.log("");
console.log('Usernames are\n'); console.log("Usernames are\n");
console.log('\troot'); console.log("\troot");
console.log('\tuser'); console.log("\tuser");
console.log(''); console.log("");
console.log('Password (for both) is\n'); console.log("Password (for both) is\n");
console.log('\t' + secret); console.log("\t" + secret);
console.log(''); console.log("");
console.log("Shhhh... It's a secret to everybody!"); console.log("Shhhh... It's a secret to everybody!");
console.log(''); console.log("");
console.log(''); console.log("");
return app; return app;
} }

View File

@ -2,19 +2,19 @@
// I'm not a fan of `socket.io` because it's huge and complex. // I'm not a fan of `socket.io` because it's huge and complex.
// I much prefer `ws` because it's very simple and easy. // I much prefer `ws` because it's very simple and easy.
// That said, it's popular....... // That said, it's popular.......
'use strict'; "use strict";
//var greenlock = require('greenlock-express'); //var greenlock = require('greenlock-express');
var greenlock = require('../'); var greenlock = require("../");
var options = require('./greenlock-options.js'); var options = require("./greenlock-options.js");
var socketio = require('socket.io'); var socketio = require("socket.io");
var server; var server;
var io; var io;
// Any node http app will do - whether express, raw http or whatever // Any node http app will do - whether express, raw http or whatever
options.app = require('express')().use('/', function (req, res) { options.app = require("express")().use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}); });
// The server that's handed back from `listen` is a raw https server // The server that's handed back from `listen` is a raw https server
@ -22,11 +22,11 @@ server = greenlock.create(options).listen(80, 443);
io = socketio(server); io = socketio(server);
// Then you do your socket.io stuff // Then you do your socket.io stuff
io.on('connection', function (socket) { io.on("connection", function(socket) {
console.log('a user connected'); console.log("a user connected");
socket.emit('Welcome'); socket.emit("Welcome");
socket.on('chat message', function (msg) { socket.on("chat message", function(msg) {
socket.broadcast.emit('chat message', msg); socket.broadcast.emit("chat message", msg);
}); });
}); });

View File

@ -1,68 +1,64 @@
'use strict'; "use strict";
// npm install spdy@3.x // npm install spdy@3.x
//var Greenlock = require('greenlock-express') //var Greenlock = require('greenlock-express')
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' version: "draft-11",
, server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
// You MUST change this to a valid email address // You MUST change this to a valid email address
, email: 'jon@example.com' email: "jon@example.com",
// You MUST NOT build clients that accept the ToS without asking the user // You MUST NOT build clients that accept the ToS without asking the user
, agreeTos: true agreeTos: true,
// You MUST change these to valid domains // You MUST change these to valid domains
// NOTE: all domains will validated and listed on the certificate // NOTE: all domains will validated and listed on the certificate
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
// You MUST have access to write to directory where certs are saved // You MUST have access to write to directory where certs are saved
// ex: /home/foouser/acme/etc // ex: /home/foouser/acme/etc
, configDir: '~/.config/acme/' // MUST have write access configDir: "~/.config/acme/", // MUST have write access
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true
//, debug: true //, debug: true
}); });
//////////////////////// ////////////////////////
// http-01 Challenges // // http-01 Challenges //
//////////////////////// ////////////////////////
// http-01 challenge happens over http/1.1, not http2 // http-01 challenge happens over http/1.1, not http2
var redirectHttps = require('redirect-https')(); var redirectHttps = require("redirect-https")();
var acmeChallengeHandler = greenlock.middleware(redirectHttps); var acmeChallengeHandler = greenlock.middleware(redirectHttps);
require('http').createServer(acmeChallengeHandler).listen(80, function () { require("http")
.createServer(acmeChallengeHandler)
.listen(80, function() {
console.log("Listening for ACME http-01 challenges on", this.address()); console.log("Listening for ACME http-01 challenges on", this.address());
}); });
//////////////////////// ////////////////////////
// http2 via SPDY h2 // // http2 via SPDY h2 //
//////////////////////// ////////////////////////
// spdy is a drop-in replacement for the https API // spdy is a drop-in replacement for the https API
var spdyOptions = Object.assign({}, greenlock.tlsOptions); var spdyOptions = Object.assign({}, greenlock.tlsOptions);
spdyOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
var myApp = require('./my-express-app.js'); var myApp = require("./my-express-app.js");
var server = require('spdy').createServer(spdyOptions, myApp); var server = require("spdy").createServer(spdyOptions, myApp);
server.on('error', function (err) { server.on("error", function(err) {
console.error(err); console.error(err);
}); });
server.on('listening', function () { server.on("listening", function() {
console.log("Listening for SPDY/http2/https requests on", this.address()); console.log("Listening for SPDY/http2/https requests on", this.address());
}); });
server.listen(443); server.listen(443);

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
/////////////////// ///////////////////
// vhost example // // vhost example //
@ -11,74 +11,77 @@
// The prefix where sites go by name. // The prefix where sites go by name.
// For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path
var srv = process.argv[3] || '/srv/www/'; var srv = process.argv[3] || "/srv/www/";
var path = require('path'); var path = require("path");
var fs = require('fs').promises; var fs = require("fs").promises;
var finalhandler = require('finalhandler'); var finalhandler = require("finalhandler");
var serveStatic = require('serve-static'); var serveStatic = require("serve-static");
//var glx = require('greenlock-express') //var glx = require('greenlock-express')
var glx = require('./').create({ var glx = require("./").create({
version: "draft-11", // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' // Let's Encrypt v2 is ACME draft 11 server: "https://acme-v02.api.letsencrypt.org/directory", // If at first you don't succeed, stop and switch to staging
, 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 // https://acme-staging-v02.api.letsencrypt.org/directory
, configDir: process.argv[4] || '~/.config/acme/' // You MUST have access to write to directory where certs configDir: process.argv[4] || "~/.config/acme/", // You MUST have access to write to directory where certs
// are saved. ex: /home/foouser/.config/acme // are saved. ex: /home/foouser/.config/acme
, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
// domain name here and reject invalid ones // domain name here and reject invalid ones
, app: myVhostApp // Any node-style http app (i.e. express, koa, hapi, rill) app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill)
/* CHANGE TO A VALID EMAIL */ /* CHANGE TO A VALID EMAIL */
, email: process.argv[2] || 'jon.doe@example.com' // Email for Let's Encrypt account and Greenlock Security email: process.argv[2] || "jon.doe@example.com", // Email for Let's Encrypt account and Greenlock Security
, agreeTos: true // Accept Let's Encrypt ToS agreeTos: true // Accept Let's Encrypt ToS
//, communityMember: true // Join Greenlock to get important updates, no spam //, communityMember: true // Join Greenlock to get important updates, no spam
//, debug: true //, debug: true
}); });
var server = glx.listen(80, 443); var server = glx.listen(80, 443);
server.on('listening', function () { server.on("listening", function() {
console.info(server.type + " listening on", server.address()); console.info(server.type + " listening on", server.address());
}); });
function myApproveDomains(opts, certs, cb) { function myApproveDomains(opts, certs, cb) {
console.log('sni:', opts.domain); console.log("sni:", opts.domain);
// In this example the filesystem is our "database". // In this example the filesystem is our "database".
// We check in /srv/www for whatever.com and if it exists, it's allowed // We check in /srv/www for whatever.com and if it exists, it's allowed
// SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to
return checkWwws(opts.domains[0]).then(function () { return checkWwws(opts.domains[0])
.then(function() {
//opts.email = email; //opts.email = email;
opts.agreeTos = true; opts.agreeTos = true;
cb(null, { options: opts, certs: certs }); cb(null, { options: opts, certs: certs });
}).catch(cb); })
.catch(cb);
} }
function checkWwws(_hostname) { function checkWwws(_hostname) {
if (!_hostname) { if (!_hostname) {
// SECURITY, don't allow access to the 'srv' root // SECURITY, don't allow access to the 'srv' root
// (greenlock-express uses middleware to check '..', etc) // (greenlock-express uses middleware to check '..', etc)
return ''; return "";
} }
var hostname = _hostname; var hostname = _hostname;
var _hostdir = path.join(srv, hostname); var _hostdir = path.join(srv, hostname);
var hostdir = _hostdir; var hostdir = _hostdir;
// TODO could test for www/no-www both in directory // TODO could test for www/no-www both in directory
return fs.readdir(hostdir).then(function () { return fs
.readdir(hostdir)
.then(function() {
// TODO check for some sort of htaccess.json and use email in that // 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` // NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01'; // opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({}); // opts.challenge = require('le-challenge-fs').create({});
return hostname; return hostname;
}).catch(function () { })
if ('www.' === hostname.slice(0, 4)) { .catch(function() {
if ("www." === hostname.slice(0, 4)) {
// Assume we'll redirect to non-www if it's available. // Assume we'll redirect to non-www if it's available.
hostname = hostname.slice(4); hostname = hostname.slice(4);
hostdir = path.join(srv, hostname); hostdir = path.join(srv, hostname);
@ -88,14 +91,15 @@ function checkWwws(_hostname) {
}); });
} else { } else {
// Or check and see if perhaps we should redirect non-www to www // Or check and see if perhaps we should redirect non-www to www
hostname = 'www.' + hostname; hostname = "www." + hostname;
hostdir = path.join(srv, hostname); hostdir = path.join(srv, hostname);
return fs.readdir(hostdir).then(function() { return fs.readdir(hostdir).then(function() {
// TODO list both domains? // TODO list both domains?
return hostname; return hostname;
}); });
} }
}).catch(function () { })
.catch(function() {
throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read"); throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read");
}); });
} }
@ -103,7 +107,7 @@ function checkWwws(_hostname) {
function myVhostApp(req, res) { function myVhostApp(req, res) {
// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
// (also: only domains approved above will get here) // (also: only domains approved above will get here)
console.log('vhost:', req.headers.host); console.log("vhost:", req.headers.host);
if (!req.headers.host) { if (!req.headers.host) {
// SECURITY, don't allow access to the 'srv' root // SECURITY, don't allow access to the 'srv' root
// (greenlock-express uses middleware to check '..', etc) // (greenlock-express uses middleware to check '..', etc)
@ -112,17 +116,19 @@ function myVhostApp(req, res) {
// We could cache wether or not a host exists for some amount of time // We could cache wether or not a host exists for some amount of time
var fin = finalhandler(req, res); var fin = finalhandler(req, res);
return checkWwws(req.headers.host).then(function (hostname) { return checkWwws(req.headers.host)
.then(function(hostname) {
if (hostname !== req.headers.host) { if (hostname !== req.headers.host) {
res.statusCode = 302; res.statusCode = 302;
res.setHeader('Location', 'https://' + hostname); res.setHeader("Location", "https://" + hostname);
// SECURITY this is safe only because greenlock disallows invalid hostnames // SECURITY this is safe only because greenlock disallows invalid hostnames
res.end("<!-- redirecting to https://" + hostname + "-->"); res.end("<!-- redirecting to https://" + hostname + "-->");
return; return;
} }
var serve = serveStatic(path.join(srv, hostname), { redirect: true }); var serve = serveStatic(path.join(srv, hostname), { redirect: true });
serve(req, res, fin); serve(req, res, fin);
}).catch(function () { })
.catch(function() {
fin(); fin();
}); });
} }

View File

@ -1,40 +1,46 @@
'use strict'; "use strict";
//////////////////////// ////////////////////////
// Greenlock Setup // // Greenlock Setup //
//////////////////////// ////////////////////////
//var Greenlock = require('greenlock-express'); //var Greenlock = require('greenlock-express');
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
// Let's Encrypt v2 is ACME draft 11 // Let's Encrypt v2 is ACME draft 11
// Note: If at first you don't succeed, stop and switch to staging // Note: If at first you don't succeed, stop and switch to staging
// https://acme-staging-v02.api.letsencrypt.org/directory // https://acme-staging-v02.api.letsencrypt.org/directory
server: 'https://acme-v02.api.letsencrypt.org/directory' server: "https://acme-v02.api.letsencrypt.org/directory",
, version: 'draft-11' version: "draft-11",
, configDir: '~/.config/acme/' configDir: "~/.config/acme/",
, app: require('./my-express-app.js') app: require("./my-express-app.js"),
// You MUST change these to a valid email and domains // You MUST change these to a valid email and domains
, email: 'john.doe@example.com' email: "john.doe@example.com",
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
, agreeTos: true agreeTos: true,
// Get notified of important updates and help me make greenlock better // Get notified of important updates and help me make greenlock better
, communityMember: true communityMember: true,
, telemetry: true telemetry: true
//, debug: true //, debug: true
}); });
var server = greenlock.listen(80, 443); var server = greenlock.listen(80, 443);
var WebSocket = require('ws'); var WebSocket = require("ws");
var ws = new WebSocket.Server({ server: server }); var ws = new WebSocket.Server({ server: server });
ws.on('connection', function (ws, req) { ws.on("connection", function(ws, req) {
// inspect req.headers.authorization (or cookies) for session info // inspect req.headers.authorization (or cookies) for session info
ws.send("[Secure Echo Server] Hello!\nAuth: '" + (req.headers.authorization || 'none') + "'\n" ws.send(
+ "Cookie: '" + (req.headers.cookie || 'none') + "'\n"); "[Secure Echo Server] Hello!\nAuth: '" +
ws.on('message', function (data) { ws.send(data); }); (req.headers.authorization || "none") +
"'\n" +
"Cookie: '" +
(req.headers.cookie || "none") +
"'\n"
);
ws.on("message", function(data) {
ws.send(data);
});
}); });

View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
/*global Promise*/ /*global Promise*/
/////////////////////// ///////////////////////
@ -11,56 +11,63 @@
// //
//var glx = require('greenlock-express') //var glx = require('greenlock-express')
var glx = require('../').create({ var glx = require("../").create({
version: "draft-11", // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' // Let's Encrypt v2 is ACME draft 11 server: "https://acme-staging-v02.api.letsencrypt.org/directory",
, server: '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 //, 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 // https://acme-staging-v02.api.letsencrypt.org/directory
, configDir: '~/acme/' // You MUST have access to write to directory where certs configDir: "~/acme/", // You MUST have access to write to directory where certs
// are saved. ex: /home/foouser/.config/acme // are saved. ex: /home/foouser/.config/acme
, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
// domain name here and reject invalid ones // domain name here and reject invalid ones
, app: require('./my-express-app.js') // Any node-style http app (i.e. express, koa, hapi, rill) app: require("./my-express-app.js"), // Any node-style http app (i.e. express, koa, hapi, rill)
/* CHANGE TO A VALID EMAIL */ /* CHANGE TO A VALID EMAIL */
, email: 'jon.doe@example.com' // Email for Let's Encrypt account and Greenlock Security email: "jon.doe@example.com", // Email for Let's Encrypt account and Greenlock Security
, agreeTos: true // Accept Let's Encrypt ToS agreeTos: true, // Accept Let's Encrypt ToS
, communityMember: true // Join Greenlock to (very rarely) get important updates communityMember: true, // Join Greenlock to (very rarely) get important updates
//, debug: true //, debug: true
, store: require('le-store-fs') store: require("le-store-fs")
}); });
var server = glx.listen(80, 443); var server = glx.listen(80, 443);
server.on('listening', function () { server.on("listening", function() {
console.info(server.type + " listening on", server.address()); console.info(server.type + " listening on", server.address());
}); });
function myApproveDomains(opts) { function myApproveDomains(opts) {
console.log('sni:', opts.domain); console.log("sni:", opts.domain);
// must be 'example.com' or start with 'example.com' // must be 'example.com' or start with 'example.com'
if ('example.com' !== opts.domain if (
&& 'example.com' !== opts.domain.split('.').slice(1).join('.')) { "example.com" !== opts.domain &&
"example.com" !==
opts.domain
.split(".")
.slice(1)
.join(".")
) {
return Promise.reject(new Error("we don't serve your kind here: " + opts.domain)); return Promise.reject(new Error("we don't serve your kind here: " + opts.domain));
} }
// the primary domain for the cert // the primary domain for the cert
opts.subject = 'example.com'; opts.subject = "example.com";
// the altnames (including the primary) // the altnames (including the primary)
opts.domains = [ opts.subject, '*.example.com' ]; opts.domains = [opts.subject, "*.example.com"];
if (!opts.challenges) { opts.challenges = {}; } if (!opts.challenges) {
opts.challenges['http-01'] = require('le-challenge-fs').create({}); opts.challenges = {};
}
opts.challenges["http-01"] = require("le-challenge-fs").create({});
// Note: When implementing a dns-01 plugin you should make it check in a loop // Note: When implementing a dns-01 plugin you should make it check in a loop
// until it can positively confirm that the DNS changes have propagated. // until it can positively confirm that the DNS changes have propagated.
// That could take several seconds to a few minutes. // That could take several seconds to a few minutes.
opts.challenges['dns-01'] = require('le-challenge-dns').create({}); opts.challenges["dns-01"] = require("le-challenge-dns").create({});
// explicitly set account id and certificate.id // explicitly set account id and certificate.id
opts.account = { id: opts.email }; opts.account = { id: opts.email };

188
index.js
View File

@ -1,8 +1,8 @@
'use strict'; "use strict";
var PromiseA; var PromiseA;
try { try {
PromiseA = require('bluebird'); PromiseA = require("bluebird");
} catch (e) { } catch (e) {
PromiseA = global.Promise; PromiseA = global.Promise;
} }
@ -11,18 +11,18 @@ try {
module.exports.create = function(opts) { module.exports.create = function(opts) {
// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware // accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
if (!opts._communityPackage) { if (!opts._communityPackage) {
opts._communityPackage = 'greenlock-express.js'; opts._communityPackage = "greenlock-express.js";
opts._communityPackageVersion = require('./package.json').version; opts._communityPackageVersion = require("./package.json").version;
} }
function explainError(e) { function explainError(e) {
console.error('Error:' + e.message); console.error("Error:" + e.message);
if ('EACCES' === e.errno) { if ("EACCES" === e.errno) {
console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'."); console.error("You don't have prmission to access '" + e.address + ":" + e.port + "'.");
console.error("You probably need to use \"sudo\" or \"sudo setcap 'cap_net_bind_service=+ep' $(which node)\""); console.error('You probably need to use "sudo" or "sudo setcap \'cap_net_bind_service=+ep\' $(which node)"');
return; return;
} }
if ('EADDRINUSE' === e.errno) { if ("EADDRINUSE" === e.errno) {
console.error("'" + e.address + ":" + e.port + "' is already being used by some other program."); console.error("'" + e.address + ":" + e.port + "' is already being used by some other program.");
console.error("You probably need to stop that program or restart your computer."); console.error("You probably need to stop that program or restart your computer.");
return; return;
@ -31,27 +31,37 @@ module.exports.create = function (opts) {
} }
function _createPlain(plainPort) { function _createPlain(plainPort) {
if (!plainPort) { plainPort = 80; } if (!plainPort) {
plainPort = 80;
}
var parts = String(plainPort).split(':'); var parts = String(plainPort).split(":");
var p = parts.pop(); var p = parts.pop();
var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, ''); var addr = parts
.join(":")
.replace(/^\[/, "")
.replace(/\]$/, "");
var args = []; var args = [];
var httpType; var httpType;
var server; var server;
var validHttpPort = (parseInt(p, 10) >= 0); var validHttpPort = parseInt(p, 10) >= 0;
if (addr) { args[1] = addr; } if (addr) {
args[1] = addr;
}
if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
} }
server = require('http').createServer( server = require("http").createServer(
greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')())) greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")()))
); );
httpType = 'http'; httpType = "http";
return { server: server, listen: function () { return new PromiseA(function (resolve, reject) { return {
server: server,
listen: function() {
return new PromiseA(function(resolve, reject) {
args[0] = p; args[0] = p;
args.push(function() { args.push(function() {
if (!greenlock.servername) { if (!greenlock.servername) {
@ -68,88 +78,121 @@ module.exports.create = function (opts) {
return; return;
} }
return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) { return greenlock
.check({ domains: [greenlock.servername] })
.then(function(certs) {
if (certs) { if (certs) {
return { return {
key: Buffer.from(certs.privkey, 'ascii') key: Buffer.from(certs.privkey, "ascii"),
, cert: Buffer.from(certs.cert + '\r\n' + certs.chain, '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); console.info(
"Fetching certificate for '%s' to use as default for HTTPS server...",
greenlock.servername
);
return new PromiseA(function(resolve, reject) { return new PromiseA(function(resolve, reject) {
// using SNICallback because all options will be set // using SNICallback because all options will be set
greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) { greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) {
if (err) { reject(err); return; } if (err) {
return greenlock.check({ domains: [ greenlock.servername ] }).then(function (certs) { reject(err);
return;
}
return greenlock
.check({ domains: [greenlock.servername] })
.then(function(certs) {
resolve({ resolve({
key: Buffer.from(certs.privkey, 'ascii') key: Buffer.from(certs.privkey, "ascii"),
, cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
}); });
}).catch(reject); })
.catch(reject);
}); });
}); });
}).then(resolve).catch(reject); })
.then(resolve)
.catch(reject);
}); });
server.listen.apply(server, args).on('error', function (e) { server.listen.apply(server, args).on("error", function(e) {
if (server.listenerCount('error') < 2) { if (server.listenerCount("error") < 2) {
console.warn("Did not successfully create http server and bind to port '" + p + "':"); console.warn("Did not successfully create http server and bind to port '" + p + "':");
explainError(e); explainError(e);
process.exit(41); process.exit(41);
} }
}); });
}); } }; });
}
};
} }
function _create(port) { function _create(port) {
if (!port) { port = 443; } if (!port) {
port = 443;
}
var parts = String(port).split(':'); var parts = String(port).split(":");
var p = parts.pop(); var p = parts.pop();
var addr = parts.join(':').replace(/^\[/, '').replace(/\]$/, ''); var addr = parts
.join(":")
.replace(/^\[/, "")
.replace(/\]$/, "");
var args = []; var args = [];
var httpType; var httpType;
var server; var server;
var validHttpPort = (parseInt(p, 10) >= 0); var validHttpPort = parseInt(p, 10) >= 0;
if (addr) { args[1] = addr; } if (addr) {
args[1] = addr;
}
if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) {
console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe");
} }
var https; var https;
try { try {
https = require('spdy'); https = require("spdy");
greenlock.tlsOptions.spdy = { protocols: [ 'h2', 'http/1.1' ], plain: false }; greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false };
httpType = 'http2 (spdy/h2)'; httpType = "http2 (spdy/h2)";
} catch (e) { } catch (e) {
https = require('https'); https = require("https");
httpType = 'https'; httpType = "https";
} }
var sniCallback = greenlock.tlsOptions.SNICallback; var sniCallback = greenlock.tlsOptions.SNICallback;
greenlock.tlsOptions.SNICallback = function(domain, cb) { greenlock.tlsOptions.SNICallback = function(domain, cb) {
sniCallback(domain, function(err, context) { sniCallback(domain, function(err, context) {
cb(err, context); cb(err, context);
if (!context || server._hasDefaultSecureContext) { return; } if (!context || server._hasDefaultSecureContext) {
if (!domain) { domain = greenlock.servername; } return;
if (!domain) { return; } }
if (!domain) {
domain = greenlock.servername;
}
if (!domain) {
return;
}
return greenlock.check({ domains: [ domain ] }).then(function (certs) { return greenlock
.check({ domains: [domain] })
.then(function(certs) {
// ignore the case that check doesn't have all the right args here // 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) // to get the same certs that it just got (eventually the right ones will come in)
if (!certs) { return; } if (!certs) {
return;
}
if (server.setSecureContext) { if (server.setSecureContext) {
// only available in node v11.0+ // only available in node v11.0+
server.setSecureContext({ server.setSecureContext({
key: Buffer.from(certs.privkey, 'ascii') key: Buffer.from(certs.privkey, "ascii"),
, cert: Buffer.from(certs.cert + '\r\n' + certs.chain, 'ascii') cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii")
}); });
console.info("Using '%s' as default certificate", domain); console.info("Using '%s' as default certificate", domain);
} else { } else {
console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); console.info("Setting default certificates dynamically requires node v11.0+. Skipping.");
} }
server._hasDefaultSecureContext = true; server._hasDefaultSecureContext = true;
}).catch(function (/*e*/) { })
.catch(function(/*e*/) {
// this may be that the test.example.com was requested, but it's listed // 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 // 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("Unusual error: couldn't get newly authorized certificate:");
@ -159,13 +202,15 @@ module.exports.create = function (opts) {
}; };
if (greenlock.tlsOptions.cert) { if (greenlock.tlsOptions.cert) {
server._hasDefaultSecureContext = true; server._hasDefaultSecureContext = true;
if (greenlock.tlsOptions.cert.toString('ascii').split("BEGIN").length < 3) { if (greenlock.tlsOptions.cert.toString("ascii").split("BEGIN").length < 3) {
console.warn("Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)"); console.warn(
"Invalid certificate file. 'tlsOptions.cert' should contain cert.pem (certificate file) *and* chain.pem (intermediate certificates) seperated by an extra newline (CRLF)"
);
} }
} }
server = https.createServer( server = https.createServer(
greenlock.tlsOptions greenlock.tlsOptions,
, greenlock.middleware.sanitizeHost(function (req, res) { greenlock.middleware.sanitizeHost(function(req, res) {
try { try {
greenlock.app(req, res); greenlock.app(req, res);
} catch (e) { } catch (e) {
@ -183,21 +228,28 @@ module.exports.create = function (opts) {
); );
server.type = httpType; server.type = httpType;
return { server: server, listen: function () { return new PromiseA(function (resolve) { return {
server: server,
listen: function() {
return new PromiseA(function(resolve) {
args[0] = p; args[0] = p;
args.push(function () { resolve(/*server*/); }); args.push(function() {
server.listen.apply(server, args).on('error', function (e) { resolve(/*server*/);
if (server.listenerCount('error') < 2) { });
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 + "':"); console.warn("Did not successfully create http server and bind to port '" + p + "':");
explainError(e); explainError(e);
process.exit(41); process.exit(41);
} }
}); });
}); } }; });
}
};
} }
// NOTE: 'greenlock' is just 'opts' renamed // NOTE: 'greenlock' is just 'opts' renamed
var greenlock = require('greenlock').create(opts); var greenlock = require("greenlock").create(opts);
if (!opts.app) { if (!opts.app) {
opts.app = function(req, res) { opts.app = function(req, res) {
@ -235,18 +287,22 @@ module.exports.create = function (opts) {
} }
return obj2.listen().then(function() { return obj2.listen().then(function() {
// Report plain http status // Report plain http status
if ('function' === typeof fnPlain) { if ("function" === typeof fnPlain) {
fnPlain.apply(plainServer); fnPlain.apply(plainServer);
} else if (!fn && !plainServer.listenerCount('listening') && !server.listenerCount('listening')) { } else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) {
console.info('[:' + (plainServer.address().port || plainServer.address()) console.info(
+ "] Handling ACME challenges and redirecting to " + server.type); "[:" +
(plainServer.address().port || plainServer.address()) +
"] Handling ACME challenges and redirecting to " +
server.type
);
} }
// Report h2/https status // Report h2/https status
if ('function' === typeof fn) { if ("function" === typeof fn) {
fn.apply(server); fn.apply(server);
} else if (!server.listenerCount('listening')) { } else if (!server.listenerCount("listening")) {
console.info('[:' + (server.address().port || server.address()) + "] Serving " + server.type); console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type);
} }
}); });
}).then; }).then;
@ -255,7 +311,7 @@ module.exports.create = function (opts) {
return server; return server;
}; };
opts.middleware.acme = function(opts) { opts.middleware.acme = function(opts) {
return greenlock.middleware.sanitizeHost(greenlock.middleware(require('redirect-https')(opts))); return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts)));
}; };
opts.middleware.secure = function(app) { opts.middleware.secure = function(app) {
return greenlock.middleware.sanitizeHost(app); return greenlock.middleware.sanitizeHost(app);

View File

@ -1,8 +1,8 @@
'use strict'; "use strict";
function requireBluebird() { function requireBluebird() {
try { try {
return require('bluebird'); return require("bluebird");
} catch (e) { } catch (e) {
console.error(""); console.error("");
console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support."); console.error("DON'T PANIC. You're running an old version of node with incomplete Promise support.");
@ -12,26 +12,26 @@ function requireBluebird() {
} }
} }
if ('undefined' === typeof Promise) { if ("undefined" === typeof Promise) {
global.Promise = requireBluebird(); global.Promise = requireBluebird();
} }
if ('function' !== typeof require('util').promisify) { if ("function" !== typeof require("util").promisify) {
require('util').promisify = requireBluebird().promisify; require("util").promisify = requireBluebird().promisify;
} }
if (!console.debug) { if (!console.debug) {
console.debug = console.log; console.debug = console.log;
} }
var fs = require('fs'); var fs = require("fs");
var fsAsync = {}; var fsAsync = {};
Object.keys(fs).forEach(function(key) { Object.keys(fs).forEach(function(key) {
var fn = fs[key]; var fn = fs[key];
if ('function' !== typeof fn || !/[a-z]/.test(key[0])) { if ("function" !== typeof fn || !/[a-z]/.test(key[0])) {
return; return;
} }
fsAsync[key] = require('util').promisify(fn); fsAsync[key] = require("util").promisify(fn);
}); });
exports.fsAsync = fsAsync; exports.fsAsync = fsAsync;

138
server.js
View File

@ -1,5 +1,5 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; "use strict";
/*global Promise*/ /*global Promise*/
///////////////////////////////// /////////////////////////////////
@ -15,45 +15,43 @@
// ex: /srv/api/api.example.com // ex: /srv/api/api.example.com
// //
var configpath = process.argv[2] || './config.js'; var configpath = process.argv[2] || "./config.js";
var config = require(configpath); var config = require(configpath);
// The prefix where sites go by name. // The prefix where sites go by name.
// For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path
var path = require('path'); var path = require("path");
var fs = require('./lib/compat.js').fsAsync; var fs = require("./lib/compat.js").fsAsync;
var finalhandler = require('finalhandler'); var finalhandler = require("finalhandler");
var serveStatic = require('serve-static'); var serveStatic = require("serve-static");
//var glx = require('greenlock-express') //var glx = require('greenlock-express')
var glx = require('./').create({ var glx = require("./").create({
version: "draft-11", // Let's Encrypt v2 is ACME draft 11
version: 'draft-11' // Let's Encrypt v2 is ACME draft 11
//, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' //, server: '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 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 // https://acme-staging-v02.api.letsencrypt.org/directory
, configDir: config.configDir // You MUST have access to write to directory where certs configDir: config.configDir, // You MUST have access to write to directory where certs
// are saved. ex: /home/foouser/.config/acme // are saved. ex: /home/foouser/.config/acme
, approveDomains: myApproveDomains // Greenlock's wraps around tls.SNICallback. Check the approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
// domain name here and reject invalid ones // domain name here and reject invalid ones
, app: myVhostApp // Any node-style http app (i.e. express, koa, hapi, rill) app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill)
/* CHANGE TO A VALID EMAIL */ /* CHANGE TO A VALID EMAIL */
, email: config.email // Email for Let's Encrypt account and Greenlock Security email: config.email, // Email for Let's Encrypt account and Greenlock Security
, agreeTos: true // Accept Let's Encrypt ToS agreeTos: true, // Accept Let's Encrypt ToS
//, communityMember: true // Join Greenlock to get important updates, no spam //, communityMember: true // Join Greenlock to get important updates, no spam
//, debug: true //, debug: true
, store: require('greenlock-store-fs') store: require("greenlock-store-fs")
}); });
var server = glx.listen(80, 443); var server = glx.listen(80, 443);
server.on('listening', function () { server.on("listening", function() {
console.info(server.type + " listening on", server.address()); console.info(server.type + " listening on", server.address());
}); });
@ -64,48 +62,64 @@ function myApproveDomains(opts) {
// SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to // SECURITY Greenlock validates opts.domains ahead-of-time so you don't have to
var domains = []; var domains = [];
var domain = opts.domain.replace(/^(www|api)\./, ''); var domain = opts.domain.replace(/^(www|api)\./, "");
return checkWwws(domain).then(function (hostname) { return checkWwws(domain)
.then(function(hostname) {
// this is either example.com or www.example.com // this is either example.com or www.example.com
domains.push(hostname); domains.push(hostname);
if ('api.' + domain !== opts.domain) { if ("api." + domain !== opts.domain) {
if (!domains.includes(opts.domain)) { if (!domains.includes(opts.domain)) {
domains.push(opts.domain) domains.push(opts.domain);
} }
} }
}).catch(function () { })
.catch(function() {
// ignore error // ignore error
return null; return null;
}).then(function () { })
.then(function() {
// check for api prefix // check for api prefix
var apiname = domain; var apiname = domain;
if (domains.length) { if (domains.length) {
apiname = 'api.' + domain; apiname = "api." + domain;
}
return checkApi(apiname)
.then(function(app) {
if (!app) {
return null;
} }
return checkApi(apiname).then(function (app) {
if (!app) { return null; }
domains.push(apiname); domains.push(apiname);
}).catch(function () { })
.catch(function() {
return null; return null;
}); });
}).then(function () { })
.then(function() {
if (0 === domains.length) { if (0 === domains.length) {
return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'")); return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'"));
} }
console.info('Approved domains:', domains); console.info("Approved domains:", domains);
opts.domains = domains; opts.domains = domains;
//opts.email = email; //opts.email = email;
opts.agreeTos = true; opts.agreeTos = true;
// pick the shortest (bare) or latest (www. instead of api.) to be the subject // pick the shortest (bare) or latest (www. instead of api.) to be the subject
opts.subject = opts.domains.sort(function(a, b) { opts.subject = opts.domains.sort(function(a, b) {
var len = a.length - b.length; var len = a.length - b.length;
if (0 !== len) { return len; } if (0 !== len) {
if (a < b) { return 1; } else { return -1; } return len;
}
if (a < b) {
return 1;
} else {
return -1;
}
})[0]; })[0];
if (!opts.challenges) { opts.challenges = {}; } if (!opts.challenges) {
opts.challenges['http-01'] = require('le-challenge-fs'); opts.challenges = {};
}
opts.challenges["http-01"] = require("le-challenge-fs");
//opts.challenges['dns-01'] = require('le-challenge-dns'); //opts.challenges['dns-01'] = require('le-challenge-dns');
// explicitly set account id and certificate.id // explicitly set account id and certificate.id
@ -118,18 +132,23 @@ function myApproveDomains(opts) {
function checkApi(hostname) { function checkApi(hostname) {
var apipath = path.join(config.api, hostname); var apipath = path.join(config.api, hostname);
var link = ''; var link = "";
return fs.stat(apipath).then(function (stats) { return fs
.stat(apipath)
.then(function(stats) {
if (stats.isDirectory()) { if (stats.isDirectory()) {
return require(apipath); return require(apipath);
} }
return fs.readFile(apipath, 'utf8').then(function (txt) { return fs.readFile(apipath, "utf8").then(function(txt) {
var linkpath = txt.split('\n')[0]; var linkpath = txt.split("\n")[0];
link = (' => ' + linkpath + ' '); link = " => " + linkpath + " ";
return require(linkpath); return require(linkpath);
}); });
}).catch(function (e) { })
if ('ENOENT' === e.code) { return null; } .catch(function(e) {
if ("ENOENT" === e.code) {
return null;
}
console.error(e); console.error(e);
throw new Error("rejecting '" + hostname + "' because '" + apipath + link + "' failed at require()"); throw new Error("rejecting '" + hostname + "' because '" + apipath + link + "' failed at require()");
}); });
@ -143,14 +162,17 @@ function checkWwws(_hostname) {
var hostname = _hostname; var hostname = _hostname;
var hostdir = path.join(config.srv, hostname); var hostdir = path.join(config.srv, hostname);
// TODO could test for www/no-www both in directory // TODO could test for www/no-www both in directory
return fs.readdir(hostdir).then(function () { return fs
.readdir(hostdir)
.then(function() {
// TODO check for some sort of htaccess.json and use email in that // 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` // NOTE: you can also change other options such as `challengeType` and `challenge`
// opts.challengeType = 'http-01'; // opts.challengeType = 'http-01';
// opts.challenge = require('le-challenge-fs').create({}); // opts.challenge = require('le-challenge-fs').create({});
return hostname; return hostname;
}).catch(function () { })
if ('www.' === hostname.slice(0, 4)) { .catch(function() {
if ("www." === hostname.slice(0, 4)) {
// Assume we'll redirect to non-www if it's available. // Assume we'll redirect to non-www if it's available.
hostname = hostname.slice(4); hostname = hostname.slice(4);
hostdir = path.join(config.srv, hostname); hostdir = path.join(config.srv, hostname);
@ -159,13 +181,14 @@ function checkWwws(_hostname) {
}); });
} else { } else {
// Or check and see if perhaps we should redirect non-www to www // Or check and see if perhaps we should redirect non-www to www
hostname = 'www.' + hostname; hostname = "www." + hostname;
hostdir = path.join(config.srv, hostname); hostdir = path.join(config.srv, hostname);
return fs.readdir(hostdir).then(function() { return fs.readdir(hostdir).then(function() {
return hostname; return hostname;
}); });
} }
}).catch(function () { })
.catch(function() {
throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read"); throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read");
}); });
} }
@ -173,29 +196,36 @@ function checkWwws(_hostname) {
function myVhostApp(req, res) { function myVhostApp(req, res) {
// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to // SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
// (also: only domains approved above will get here) // (also: only domains approved above will get here)
console.info(req.method, (req.headers.host||'') + req.url); console.info(req.method, (req.headers.host || "") + req.url);
Object.keys(req.headers).forEach(function(key) { Object.keys(req.headers).forEach(function(key) {
console.info(key, req.headers[key]) console.info(key, req.headers[key]);
}); });
// We could cache wether or not a host exists for some amount of time // We could cache wether or not a host exists for some amount of time
var fin = finalhandler(req, res); var fin = finalhandler(req, res);
return checkWwws(req.headers.host).then(function (hostname) { return checkWwws(req.headers.host)
.then(function(hostname) {
if (hostname !== req.headers.host) { if (hostname !== req.headers.host) {
res.statusCode = 302; res.statusCode = 302;
res.setHeader('Location', 'https://' + hostname); res.setHeader("Location", "https://" + hostname);
// SECURITY this is safe only because greenlock disallows invalid hostnames // SECURITY this is safe only because greenlock disallows invalid hostnames
res.end("<!-- redirecting to https://" + hostname + "-->"); res.end("<!-- redirecting to https://" + hostname + "-->");
return; return;
} }
var serve = serveStatic(path.join(config.srv, hostname), { redirect: true }); var serve = serveStatic(path.join(config.srv, hostname), { redirect: true });
serve(req, res, fin); serve(req, res, fin);
}).catch(function (err) { })
return checkApi(req.headers.host).then(function (app) { .catch(function(err) {
if (app) { app(req, res); return; } return checkApi(req.headers.host)
.then(function(app) {
if (app) {
app(req, res);
return;
}
console.error("none found", err); console.error("none found", err);
fin(); fin();
}).catch(function (err) { })
.catch(function(err) {
console.error("api crashed error", err); console.error("api crashed error", err);
fin(err); fin(err);
}); });

View File

@ -1,20 +1,20 @@
#!/usr/bin/env node #!/usr/bin/env node
var Greenlock = require('../'); var Greenlock = require("../");
var greenlock = Greenlock.create({ var greenlock = Greenlock.create({
version: 'draft-11' version: "draft-11",
, server: 'https://acme-staging-v02.api.letsencrypt.org/directory' server: "https://acme-staging-v02.api.letsencrypt.org/directory",
, agreeTos: true agreeTos: true,
, approvedDomains: [ 'example.com', 'www.example.com' ] approvedDomains: ["example.com", "www.example.com"],
, configDir: require('path').join(require('os').tmpdir(), 'acme') configDir: require("path").join(require("os").tmpdir(), "acme"),
, app: require('express')().use('/', function (req, res) { app: require("express")().use("/", function(req, res) {
res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end('Hello, World!\n\n💚 🔒.js'); res.end("Hello, World!\n\n💚 🔒.js");
}) })
}); });
var server1 = greenlock.listen(5080, 5443); var server1 = greenlock.listen(5080, 5443);
server1.on('listening', function () { server1.on("listening", function() {
console.log("### THREE 3333 - All is well server1", this.address()); console.log("### THREE 3333 - All is well server1", this.address());
setTimeout(function() { setTimeout(function() {
// so that the address() object doesn't disappear // so that the address() object doesn't disappear
@ -38,31 +38,41 @@ setTimeout(function () {
}, 1000); }, 1000);
}, 1000); }, 1000);
}); });
server2.on('listening', function () { server2.on("listening", function() {
console.log("### FOUR 44444 - All is well server2", server2.address()); console.log("### FOUR 44444 - All is well server2", server2.address());
}); });
}, 1000); }, 1000);
var server3 = greenlock.listen(22, 22, function () { var server3 = greenlock.listen(
22,
22,
function() {
console.error("Error: expected to get an error when launching plain server on port 22"); console.error("Error: expected to get an error when launching plain server on port 22");
}, function () { },
function() {
console.error("Error: expected to get an error when launching " + server3.type + " server on port 22"); console.error("Error: expected to get an error when launching " + server3.type + " server on port 22");
}); }
server3.unencrypted.on('error', function () { );
server3.unencrypted.on("error", function() {
console.log("Success: caught expected (plain) error"); console.log("Success: caught expected (plain) error");
}); });
server3.on('error', function () { server3.on("error", function() {
console.log("Success: caught expected " + server3.type + " error"); console.log("Success: caught expected " + server3.type + " error");
//server3.close(); //server3.close();
}); });
var server4 = greenlock.listen(7080, 7443, function () { var server4 = greenlock.listen(
console.log('Success: server4: plain'); 7080,
7443,
function() {
console.log("Success: server4: plain");
server4.unencrypted.close(); server4.unencrypted.close();
}, function () { },
console.log('Success: server4: ' + server4.type); function() {
console.log("Success: server4: " + server4.type);
server4.close(); server4.close();
}); }
);
var server5 = greenlock.listen(10080, 10443, function() { var server5 = greenlock.listen(10080, 10443, function() {
console.log("Server 5 with one fn", this.address()); console.log("Server 5 with one fn", this.address());
@ -70,6 +80,6 @@ var server5 = greenlock.listen(10080, 10443, function () {
server5.unencrypted.close(); server5.unencrypted.close();
}); });
var server6 = greenlock.listen('[::]:11080', '[::1]:11443'); var server6 = greenlock.listen("[::]:11080", "[::1]:11443");
var server7 = greenlock.listen('/tmp/gl.plain.sock', '/tmp/gl.sec.sock'); var server7 = greenlock.listen("/tmp/gl.plain.sock", "/tmp/gl.sec.sock");