make Prettier
This commit is contained in:
parent
a49ccb7398
commit
c73ad565a3
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": true
|
||||||
|
}
|
221
README.md
221
README.md
|
@ -37,23 +37,23 @@ and **node.js middleware systems**.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- [x] Automatic HTTPS
|
- [x] Automatic HTTPS
|
||||||
- [x] Free SSL
|
- [x] Free SSL
|
||||||
- [x] Free Wildcard SSL
|
- [x] Free Wildcard SSL
|
||||||
- [x] Multiple domain support (up to 100 altnames per SAN)
|
- [x] Multiple domain support (up to 100 altnames per SAN)
|
||||||
- [x] Dynamic Virtual Hosting (vhost)
|
- [x] Dynamic Virtual Hosting (vhost)
|
||||||
- [x] Automatical renewal (10 to 14 days before expiration)
|
- [x] Automatical renewal (10 to 14 days before expiration)
|
||||||
- [x] Great ACME support
|
- [x] Great ACME support
|
||||||
- [x] ACME draft 11
|
- [x] ACME draft 11
|
||||||
- [x] Let's Encrypt v2
|
- [x] Let's Encrypt v2
|
||||||
- [x] Let's Encrypt v1
|
- [x] Let's Encrypt v1
|
||||||
- [x] Full node.js support
|
- [x] Full node.js support
|
||||||
- [x] node v6+
|
- [x] node v6+
|
||||||
- [x] core https module
|
- [x] core https module
|
||||||
- [x] Express.js
|
- [x] Express.js
|
||||||
- [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js)
|
- [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js)
|
||||||
- [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js)
|
- [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js)
|
||||||
- [x] Extensible Plugin Support
|
- [x] Extensible Plugin Support
|
||||||
- [x] AWS (S3, Route53)
|
- [x] AWS (S3, Route53)
|
||||||
- [x] Azure
|
- [x] Azure
|
||||||
- [x] CloudFlare
|
- [x] CloudFlare
|
||||||
|
@ -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 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 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,33 +302,34 @@ 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() {
|
||||||
console.log("Listening on port 80 for ACME challenges and 443 for express app.");
|
console.log("Listening on port 80 for ACME challenges and 443 for express app.");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
@ -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,24 +373,25 @@ 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());
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**Security**:
|
**Security**:
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
|
12
config.js
12
config.js
|
@ -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/"
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,59 +1,56 @@
|
||||||
'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 //
|
||||||
|
@ -61,15 +58,18 @@ require('http').createServer(acmeChallengeHandler).listen(80, function () {
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,45 +1,41 @@
|
||||||
'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
|
||||||
//, email: 'none@see.note.above'
|
//, email: 'none@see.note.above'
|
||||||
//, agreeTos: false
|
//, agreeTos: false
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -51,8 +47,11 @@ function approveDomains(opts, certs, cb) {
|
||||||
opts.domains = [certs.subject].concat(certs.altnames);
|
opts.domains = [certs.subject].concat(certs.altnames);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>' + " | " + '<a href="/logout/">Logout</a>');
|
||||||
'<a href="/browse/">View Files</a>'
|
|
||||||
+ ' | '
|
|
||||||
+ '<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 | <a href="/">Home</a>');
|
res.end('Logged out | <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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,54 +1,50 @@
|
||||||
'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 //
|
||||||
|
@ -56,13 +52,13 @@ require('http').createServer(acmeChallengeHandler).listen(80, function () {
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// vhost example //
|
// vhost example //
|
||||||
|
@ -11,91 +11,95 @@
|
||||||
|
|
||||||
// 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);
|
||||||
return fs.readdir(hostdir).then(function () {
|
return fs.readdir(hostdir).then(function() {
|
||||||
// TODO list both domains?
|
// TODO list both domains?
|
||||||
return hostname;
|
return 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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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-v02.api.letsencrypt.org/directory' // If at first you don't succeed, stop and switch to staging
|
||||||
, 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
|
|
||||||
// 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 };
|
||||||
|
|
220
index.js
220
index.js
|
@ -1,28 +1,28 @@
|
||||||
'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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// opts.approveDomains(options, certs, cb)
|
// opts.approveDomains(options, certs, cb)
|
||||||
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,29 +31,39 @@ 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) {
|
||||||
if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) {
|
if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) {
|
||||||
greenlock.servername = greenlock.approvedDomains[0];
|
greenlock.servername = greenlock.approvedDomains[0];
|
||||||
|
@ -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(
|
||||||
return new PromiseA(function (resolve, reject) {
|
"Fetching certificate for '%s' to use as default for HTTPS server...",
|
||||||
|
greenlock.servername
|
||||||
|
);
|
||||||
|
return new PromiseA(function(resolve, reject) {
|
||||||
// using SNICallback because all options will be set
|
// 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,22 +202,24 @@ 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) {
|
||||||
console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:");
|
console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
try {
|
try {
|
||||||
res.statusCode = 500;
|
res.statusCode = 500;
|
||||||
res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler.");
|
res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler.");
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
// (headers may have already been sent, etc)
|
// (headers may have already been sent, etc)
|
||||||
}
|
}
|
||||||
|
@ -183,29 +228,36 @@ 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) {
|
||||||
res.end("Hello, World!\nWith Love,\nGreenlock for Express.js");
|
res.end("Hello, World!\nWith Love,\nGreenlock for Express.js");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.listen = function (plainPort, port, fnPlain, fn) {
|
opts.listen = function(plainPort, port, fnPlain, fn) {
|
||||||
var server;
|
var server;
|
||||||
var plainServer;
|
var plainServer;
|
||||||
|
|
||||||
|
@ -222,7 +274,7 @@ module.exports.create = function (opts) {
|
||||||
plainServer = obj1.server;
|
plainServer = obj1.server;
|
||||||
server = obj2.server;
|
server = obj2.server;
|
||||||
|
|
||||||
server.then = obj1.listen().then(function (tlsOptions) {
|
server.then = obj1.listen().then(function(tlsOptions) {
|
||||||
if (tlsOptions) {
|
if (tlsOptions) {
|
||||||
if (server.setSecureContext) {
|
if (server.setSecureContext) {
|
||||||
// only available in node v11.0+
|
// only available in node v11.0+
|
||||||
|
@ -233,20 +285,24 @@ module.exports.create = function (opts) {
|
||||||
}
|
}
|
||||||
server._hasDefaultSecureContext = true;
|
server._hasDefaultSecureContext = true;
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
@ -254,10 +310,10 @@ module.exports.create = function (opts) {
|
||||||
server.unencrypted = plainServer;
|
server.unencrypted = plainServer;
|
||||||
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
'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.");
|
||||||
console.error("EASY FIX: `npm install --save bluebird`");
|
console.error("EASY FIX: `npm install --save bluebird`");
|
||||||
|
@ -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;
|
||||||
|
|
152
server.js
152
server.js
|
@ -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-v02.api.letsencrypt.org/directory", // If at first you don't succeed, stop and switch to staging
|
||||||
//, 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
|
|
||||||
// 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
|
|
||||||
, store: require('greenlock-store-fs')
|
|
||||||
|
|
||||||
|
//, debug: true
|
||||||
|
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,29 +162,33 @@ 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);
|
||||||
return fs.readdir(hostdir).then(function () {
|
return fs.readdir(hostdir).then(function() {
|
||||||
return hostname;
|
return 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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,75 +1,85 @@
|
||||||
#!/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
|
||||||
server1.close();
|
server1.close();
|
||||||
server1.unencrypted.close();
|
server1.unencrypted.close();
|
||||||
}, 10);
|
}, 10);
|
||||||
});
|
});
|
||||||
setTimeout(function () {
|
setTimeout(function() {
|
||||||
var server2 = greenlock.listen(6080, 6443, function () {
|
var server2 = greenlock.listen(6080, 6443, function() {
|
||||||
console.log("### FIVE 55555 - Started server 2!");
|
console.log("### FIVE 55555 - Started server 2!");
|
||||||
setTimeout(function () {
|
setTimeout(function() {
|
||||||
server2.close();
|
server2.close();
|
||||||
server2.unencrypted.close();
|
server2.unencrypted.close();
|
||||||
server6.close();
|
server6.close();
|
||||||
server6.unencrypted.close();
|
server6.unencrypted.close();
|
||||||
server7.close();
|
server7.close();
|
||||||
server7.unencrypted.close();
|
server7.unencrypted.close();
|
||||||
setTimeout(function () {
|
setTimeout(function() {
|
||||||
// TODO greenlock needs a close event (and to listen to its server's close event)
|
// TODO greenlock needs a close event (and to listen to its server's close event)
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}, 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());
|
||||||
server5.close();
|
server5.close();
|
||||||
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");
|
||||||
|
|
Loading…
Reference in New Issue