Compare commits
	
		
			No commits in common. "master" and "v2" have entirely different histories.
		
	
	
		
	
		
							
								
								
									
										701
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										701
									
								
								README.md
									
									
									
									
									
								
							| @ -1,363 +1,466 @@ | |||||||
| # New Documentation & [v2/v3 Migration Guide](https://git.rootprojects.org/root/greenlock.js/src/branch/v3/MIGRATION_GUIDE_V2_V3.md) | # Greenlock v3 on its way (Nov 1st, 2019) | ||||||
| 
 | 
 | ||||||
| Greenlock v3 just came out of private beta **today** (Nov 1st, 2019). | Greenlock v3 is in private beta (for backers) and will be available publicly by Nov 1st. | ||||||
| 
 | 
 | ||||||
| The code is complete and we're working on great documentation. | You can keep an eye for updates on the [campaign page](https://indiegogo.com/at/greenlock) and, | ||||||
| 
 | if this has been a useful project that's saved you time, [please contribute](https://paypal.me/rootprojects/99). | ||||||
| Many **examples** and **full API** documentation are still coming. |  | ||||||
| 
 |  | ||||||
| # [Greenlock Express](https://git.rootprojects.org/root/greenlock-express.js) is Let's Encrypt for Node |  | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| | Built by [Root](https://therootcompany.com) for [Hub](https://rootprojects.org/hub/) |  | ||||||
| 
 | 
 | ||||||
| Free SSL, Automated HTTPS / HTTP2, served with Node via Express, Koa, hapi, etc. | <table> | ||||||
|  |   <tr> | ||||||
|  |     <td><a href="https://medium.com/@bohou/secure-your-nodejs-server-with-letsencrypt-for-free-f8925742faa9" target="_blank"><img src="https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/ibm-301x112.png"></a></td> | ||||||
|  |     <td><a href="https://github.com/mozilla-iot/le-store-certbot/issues/4" target="_blank"><img src="https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/mozilla-iot-301x112.png"></a></td> | ||||||
|  |     <td><a href="https://github.com/digitalbazaar/bedrock-letsencrypt" target="_blank"><img src="https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/digital-bazaar-301x112.png"></a></td> | ||||||
|  |   </tr> | ||||||
|  | </table> | ||||||
|  | <table> | ||||||
|  |   <tr> | ||||||
|  |     <td><a href="https://github.com/beakerbrowser/homebase" target="_blank"><img src="https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/beaker-browser-301x112.png"></a></td> | ||||||
|  |     <td><a href="https://telebit.cloud" target="_blank"><img src="https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/telebit-301x112.png"></a></td> | ||||||
|  |     <td><a href="https://rootprojects.org" target="_blank"><img src="https://git.rootprojects.org/root/greenlock.js/raw/branch/master/logo/ppl-301x112.png"></a></td> | ||||||
|  |   </tr> | ||||||
|  | </table> | ||||||
| 
 | 
 | ||||||
| ### Let's Encrypt for Node, Express, etc | # [Greenlock](https://git.rootprojects.org/root/greenlock-express.js)™ for Express.js | a [Root](https://rootprojects.org) project | ||||||
| 
 | 
 | ||||||
| Greenlock Express is a **Web Server** with **Fully Automated HTTPS** and renewals. | <small>formerly letsencrypt-express</small> | ||||||
| 
 | 
 | ||||||
| ```js | Free SSL, Free Wildcard SSL, and Fully Automated HTTPS made dead simple<br> | ||||||
| "use strict"; | <small>certificates issued by Let's Encrypt v2 via [ACME](https://git.rootprojects.org/root/acme-v2.js)</small> | ||||||
| 
 | 
 | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	// Serves on 80 and 443 |  | ||||||
| 	// Get's SSL certificates magically! |  | ||||||
|  |  | ||||||
|  | <a href="https://twitter.com/intent/follow?screen_name=GreenlockHTTPS"><img src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Follow%20@GreenlockHTTPS" title="Follow @GreenlockHTTPS on Twitter" alt="Twitter Badge"></a> | ||||||
| 
 | 
 | ||||||
| 	glx.serveApp(function(req, res) { | [Greenlock™](https://git.rootprojects.org/root/greenlock.js) is for | ||||||
| 		res.end("Hello, Encrypted World!"); | [Web Servers](https://git.rootprojects.org/root/greenlock-cli.js), | ||||||
| 	}); | [Web Browsers](https://greenlock.domains), | ||||||
| } | and **node.js middleware systems**. | ||||||
| 
 |  | ||||||
| var pkg = require("./package.json"); |  | ||||||
| require("greenlock-express") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: pkg.name, version: pkg.version }, |  | ||||||
| 			maintainerEmail: pkg.author, |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Manage via API or the config file: |  | ||||||
| 
 |  | ||||||
| `~/.config/greenlock/manage.json`: (default filesystem config) |  | ||||||
| 
 |  | ||||||
| ```json |  | ||||||
| { |  | ||||||
| 	"subscriberEmail": "letsencrypt-test@therootcompany.com", |  | ||||||
| 	"agreeToTerms": true, |  | ||||||
| 	"sites": { |  | ||||||
| 		"example.com": { |  | ||||||
| 			"subject": "example.com", |  | ||||||
| 			"altnames": ["example.com", "www.example.com"] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| # Let's Encrypt for... |  | ||||||
| 
 |  | ||||||
| - IoT |  | ||||||
| - Enterprise On-Prem |  | ||||||
| - Local Development |  | ||||||
| - Home Servers |  | ||||||
| - Quitting Heroku |  | ||||||
| 
 | 
 | ||||||
| # Features | # Features | ||||||
| 
 | 
 | ||||||
| - [x] Let's Encrypt v2 (November 2019) | - [x] Automatic HTTPS | ||||||
|   - [x] ACME Protocol (RFC 8555) |  | ||||||
|   - [x] HTTP Validation (HTTP-01) |  | ||||||
|   - [x] DNS Validation (DNS-01) |  | ||||||
|   - [ ] ALPN Validation (TLS-ALPN-01) |  | ||||||
|     - Need ALPN validation? [contact us](mailto:greenlock-support@therootcompany.com) |  | ||||||
| - [x] Automated HTTPS |  | ||||||
|   - [x] Fully Automatic Renewals every 45 days |  | ||||||
|   - [x] Free SSL |   - [x] Free SSL | ||||||
|   - [x] **Wildcard** SSL |   - [x] Free Wildcard SSL | ||||||
|   - [x] **Localhost** certificates |   - [x] Multiple domain support (up to 100 altnames per SAN) | ||||||
|   - [x] HTTPS-enabled Secure **WebSockets** (`wss://`) |   - [x] Dynamic Virtual Hosting (vhost) | ||||||
| - [x] Fully customizable |   - [x] Automatical renewal (10 to 14 days before expiration) | ||||||
|   - [x] **Reasonable defaults** | - [x] Great ACME support | ||||||
|   - [x] Domain Management |   - [x] ACME draft 11 | ||||||
|   - [x] Key and Certificate Management |   - [x] Let's Encrypt v2 | ||||||
|   - [x] ACME Challenge Plugins |   - [x] Let's Encrypt v1 | ||||||
|  | - [x] Full node.js support | ||||||
|  |   - [x] node v6+ | ||||||
|  |   - [x] core https module | ||||||
|  |   - [x] Express.js | ||||||
|  |   - [x] [Koa](https://git.rootprojects.org/root/greenlock-koa.js) | ||||||
|  |   - [x] [hapi](https://git.rootprojects.org/root/greenlock-hapi.js) | ||||||
|  | - [x] Extensible Plugin Support | ||||||
|  |   - [x] AWS (S3, Route53) | ||||||
|  |   - [x] Azure | ||||||
|  |   - [x] CloudFlare | ||||||
|  |   - [x] Consul | ||||||
|  |   - [x] Digital Ocean | ||||||
|  |   - [x] etcd | ||||||
|  |   - [x] Redis | ||||||
| 
 | 
 | ||||||
| # QuickStart Guide | # Install | ||||||
| 
 |  | ||||||
| Easy as 1, 2, 3... 4 |  | ||||||
| 
 |  | ||||||
| <details> |  | ||||||
| <summary>1. Create a node project</summary> |  | ||||||
| 
 |  | ||||||
| ## 1. Create a node project |  | ||||||
| 
 |  | ||||||
| Create an empty node project. |  | ||||||
| 
 |  | ||||||
| Be sure to fill out the package name, version, and an author email. |  | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| mkdir ~/my-project | npm install --save greenlock-express@2.x | ||||||
| pushd ~/my-project |  | ||||||
| npm init |  | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| </details> | # QuickStart | ||||||
| 
 | 
 | ||||||
| <details> | <!-- TODO better quickstart (fewer options) --> | ||||||
| <summary>2. Create an http app (i.e. express)</summary> |  | ||||||
| 
 | 
 | ||||||
| ## 2. Create an http app (i.e. express) | ### Screencast | ||||||
| 
 | 
 | ||||||
| This example is shown with Express, but any node app will do. Greenlock | Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu.be/e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) | ||||||
| works with everything. |  | ||||||
| (or any node-style http app) |  | ||||||
| 
 | 
 | ||||||
| `my-express-app.js`: | <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 | ||||||
|  | - [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 | ||||||
|  | 
 | ||||||
|  | #### 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 | ||||||
|  | - [2:26](https://www.youtube.com/watch?v=bTEn93gxY50&index=2&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk&t=146) - The `approveDomains` callback | ||||||
|  | 
 | ||||||
|  | #### Security Concerns (Part 3) | ||||||
|  | 
 | ||||||
|  | - [0:00](https://www.youtube.com/watch?v=aZgVqPzoZTY&index=3&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) - Potential Attacks, and Mitigation | ||||||
|  | 
 | ||||||
|  | ### Working Example Code | ||||||
|  | 
 | ||||||
|  | Here's a completely working example that will get you started. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | git clone https://git.rootprojects.org/root/greenlock-express.js.git | ||||||
|  | pushd greenlock-express.js | ||||||
|  |   npm install | ||||||
|  | popd | ||||||
|  | 
 | ||||||
|  | # edit 'email' and 'approveDomains' in | ||||||
|  | # greenlock-express.js/examples/simple.js | ||||||
|  | 
 | ||||||
|  | node greenlock-express.js/examples/simple.js | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | All you have to do is start the webserver and then visit it at its domain name. | ||||||
|  | 
 | ||||||
|  | `server.js`: | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | require("greenlock-express") | ||||||
|  | 	.create({ | ||||||
|  | 		email: "john.doe@example.com", // The email address of the ACME user / hosting provider | ||||||
|  | 		agreeTos: true, // You must accept the ToS as the host which handles the certs | ||||||
|  | 		configDir: "~/.config/acme/", // Writable directory where certs will be saved | ||||||
|  | 		communityMember: true, // Join the community to get notified of important updates | ||||||
|  | 		telemetry: true, // Contribute telemetry data to the project | ||||||
|  | 
 | ||||||
|  | 		// Using your express app: | ||||||
|  | 		// simply export it as-is, then include it here | ||||||
|  | 		app: require("./app.js") | ||||||
|  | 
 | ||||||
|  | 		//, debug: true | ||||||
|  | 	}) | ||||||
|  | 	.listen(80, 443); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | `app.js`: | ||||||
| 
 | 
 | ||||||
| ```js | ```js | ||||||
| "use strict"; | "use strict"; | ||||||
| 
 | 
 | ||||||
| // A plain, node-style app |  | ||||||
| 
 |  | ||||||
| function myPlainNodeHttpApp(req, res) { |  | ||||||
| 	res.end("Hello, Encrypted World!"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Wrap that plain app in express, |  | ||||||
| // because that's what you're used to |  | ||||||
| 
 |  | ||||||
| var express = require("express"); | var express = require("express"); | ||||||
| var app = express(); | var app = express(); | ||||||
| app.get("/", myPlainNodeHttpApp); |  | ||||||
| 
 | 
 | ||||||
| // export the app normally | app.use("/", function(req, res) { | ||||||
| // do not .listen() | 	res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 	res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
|  | // Don't do this: | ||||||
|  | // app.listen(3000) | ||||||
|  | 
 | ||||||
|  | // Do this instead: | ||||||
| module.exports = app; | module.exports = app; | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| </details> | ### `communityMember` | ||||||
| 
 | 
 | ||||||
| <details> | If you're the kind of person that likes the kinds of stuff that I do, | ||||||
| <summary>3. Serve with Greenlock Express</summary> | well, I want to do more of it and I'd like to get you involved. | ||||||
| 
 | 
 | ||||||
| ## 3. Serve with Greenlock Express | As expected, by default we keep your email private and only use it for | ||||||
|  | transactional messaging, urgent security or API updates | ||||||
|  | (such as the mandatory upgrade to Let's Encrypt v2), and ACME account registration. | ||||||
| 
 | 
 | ||||||
| Greenlock Express is designed with these goals in mind: | However, when you set the `communityMember` option to `true` we'll also | ||||||
|  | inform you when there are meaningful and relavant feature updates (no bugfix noise), | ||||||
|  | and give you early access to similar projects. | ||||||
| 
 | 
 | ||||||
| - Simplicity and ease-of-use | You can see our full privacy policy at <https://greenlock.domains/legal/#privacy>. | ||||||
| - Performance and scalability |  | ||||||
| - Configurability and control |  | ||||||
| 
 | 
 | ||||||
| You can start with **near-zero configuration** and | ### What if the example didn't work? | ||||||
| slowly add options for greater performance and customization |  | ||||||
| later, if you need them. |  | ||||||
| 
 | 
 | ||||||
| `server.js`: | Double check the following: | ||||||
| 
 | 
 | ||||||
| ```js | - **Public Facing IP** for `http-01` challenges | ||||||
| require("greenlock-express") |   - Are you running this _as_ a public-facing webserver (good)? or localhost (bad)? | ||||||
| 	.init(getConfig) |   - Does `ifconfig` show a public address (good)? or a private one - 10.x, 192.168.x, etc (bad)? | ||||||
| 	.serve(worker); |   - If you're on a non-public server, are you using the `dns-01` challenge? | ||||||
|  | - **correct ACME version** | ||||||
|  |   - Let's Encrypt **v2** (ACME v2) must use `version: 'draft-11'` | ||||||
|  |   - Let's Encrypt v1 must use `version: 'v01'` | ||||||
|  | - **valid email** | ||||||
|  |   - You MUST set `email` to a **valid address** | ||||||
|  |   - MX records must validate (`dig MX example.com` for `'john@example.com'`) | ||||||
|  | - **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' ]`) | ||||||
|  | - **write access** | ||||||
|  |   - You MUST set `configDir` to a writeable location (test with `touch ~/acme/etc/tmp.tmp`) | ||||||
|  | - **port binding privileges** | ||||||
|  |   - 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) | ||||||
|  | - **API limits** | ||||||
|  |   - You MUST NOT exceed the API [**usage limits**](https://letsencrypt.org/docs/staging-environment/) per domain, certificate, IP address, etc | ||||||
|  | - **Red Lock, Untrusted** | ||||||
|  |   - You MUST use the **production** server url, not staging | ||||||
|  |   - The API URL should not have 'acme-staging-v02', but should have 'acme-v02' | ||||||
|  |   - Delete the `configDir` used for getting certificates in staging | ||||||
| 
 | 
 | ||||||
| function getConfig() { | ### Production vs Staging | ||||||
| 	return { |  | ||||||
| 		// uses name and version as part of the ACME client user-agent |  | ||||||
| 		// uses author as the contact for support notices |  | ||||||
| 		package: require("./package.json") |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| function worker(server) { | If at first you don't succeed, stop and switch to staging. | ||||||
| 	// Works with any Node app (Express, etc) | 
 | ||||||
| 	var app = require("my-express-app.js"); | There are a number of common problems related to system configuration - | ||||||
| 	server.serveApp(app); | firewalls, ports, permissions, etc - that you are likely to run up against | ||||||
|  | when using greenlock for your first time. | ||||||
|  | 
 | ||||||
|  | I've put a "dry run" in place with built-in diagnostics, so hopefully | ||||||
|  | you get everything right on your first or second try. | ||||||
|  | 
 | ||||||
|  | However, in order to avoid being blocked by hitting the bad request rate limits | ||||||
|  | you should switch to using the `staging` server for any testing or debugging. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | https://acme-staging-v02.api.letsencrypt.org/directory | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Working Examples | ||||||
|  | 
 | ||||||
|  | |        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.                                          | | ||||||
|  | |      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.                                             | | ||||||
|  | | Wildcard Domains |                                                        [examples/wildcard.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcard.js) shows how to use the `acme-dns-01-cli` and wildcard cetificates.                                                         | | ||||||
|  | |   HTTPS (raw)    |                                     [examples/spdy.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/spdy.js) demonstrates how to manually configure a node web server using the node's built-in `http` and `https` modules.                                      | | ||||||
|  | |   HTTP2 (spdy)   |             Presently spdy is incompatible with node v11, but [examples/spdy.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/spdy.js) demonstrates how to manually configure a node web server with spdy-compatible versions of node and Greenlock.             | | ||||||
|  | |   HTTP2 (node)   |                             [examples/http2.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http2.js) uses node's new HTTP2 module, which is NOT compatible with the existing middleware systems (and is not "stable" as of v10.0).                             | | ||||||
|  | | WebSockets (ws)  |                                                     [examples/websockets.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/websockets.js) demonstrates how to use Greenlock express with a websocket server.                                                      | | ||||||
|  | |       socket.io       |                         [examples/socket.io.js](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/socket.io.js) demonstrates how to use Greenlock express with socket.io (even though `ws` is far simpler, faster, and better and every way).                         | | ||||||
|  | |           -           |                                                                    Build Your Own <br> Be sure to tell me ([@solderjs](https://twitter.com/@solderjs)) / us ([@GreenlockHTTPS](https://twitter.com/@GreenlockHTTPS)) about it. :)                                                                     | | ||||||
|  | |    Full List     |                                                                                        Check out the [examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) directory                                                                                         | | ||||||
|  | 
 | ||||||
|  | # Plugins | ||||||
|  | 
 | ||||||
|  | Plugins developed by `root` are officially maintained, written in Vanilla JS, and typically have 0 dependencies. | ||||||
|  | 
 | ||||||
|  | **IMPORTANT**: | ||||||
|  | Community plugins may or may not be maintained and working. | ||||||
|  | Please try with the defaults before switching to community plugins. | ||||||
|  | 
 | ||||||
|  | ## HTTP-01 Challenges | ||||||
|  | 
 | ||||||
|  | Plugins for ACME / Let's Encrypt HTTP-01 Challenges. | ||||||
|  | 
 | ||||||
|  | |                  |                                                                              Plugin                                                                               | | ||||||
|  | | :--------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------: | | ||||||
|  | | **Default (fs)** |                                         [root/acme-http-01-fs](https://git.rootprojects.org/root/acme-http-01-webroot.js)                                         | | ||||||
|  | | **Manual (cli)** |                                          [root/acme-http-01-cli](https://git.rootprojects.org/root/acme-http-01-cli.js)                                           | | ||||||
|  | |    standalone    |                                   [root/acme-http-01-standalone](https://git.rootprojects.org/root/acme-http-01-standalone.js)                                    | | ||||||
|  | |      AWS S3      |                                           [root/acme-http-01-s3](https://git.rootprojects.org/root/acme-http-01-s3.js)                                            | | ||||||
|  | |      Azure       |                               [kolarcz/node-le-challenge-azure-storage](https://github.com/kolarcz/node-le-challenge-azure-storage)                               | | ||||||
|  | |        -         |                                  Build Your Own <br> [acme-http-01-test](https://git.rootprojects.org/root/acme-http-01-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) | | ||||||
|  | 
 | ||||||
|  | ## DNS-01 Challenges | ||||||
|  | 
 | ||||||
|  | Plugins for ACME / Let's Encrypt DNS-01 Challenges. | ||||||
|  | 
 | ||||||
|  | |                  |                                                                   Plugin                                                                   | | ||||||
|  | | :--------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | | ||||||
|  | | **Manual (cli)** |                                [root/acme-dns-01-cli](https://git.rootprojects.org/root/acme-dns-01-cli.js)                                | | ||||||
|  | |    Cloudflare    |                          [nodecraft/acme-dns-01-cloudflare](https://github.com/nodecraft/acme-dns-01-cloudflare)                           | | ||||||
|  | |     DNSimple     |                           [root/acme-dns-01-dnsimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js)                           | | ||||||
|  | |  Digital Ocean   |                       [root/acme-dns-01-digitalocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js)                       | | ||||||
|  | |     Duck DNS     |                            [root/acme-dns-01-duckdns](https://git.rootprojects.org/root/acme-dns-01-duckdns.js)                            | | ||||||
|  | |      Gandi       |                              [root/acme-dns-01-gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js)                              | | ||||||
|  | |     GoDaddy      |                            [root/acme-dns-01-godaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js)                            | | ||||||
|  | |    NameCheap     |                          [root/acme-dns-01-namecheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js)                          | | ||||||
|  | |     Name.com     |                         [root/acme-dns-01-namedotcom](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js)                         | | ||||||
|  | |  Route 53 (AWS)  |                                [hgezim/acme-dsns-01-route53](https://github.com/hgezim/acme-dns-01-route53)                                | | ||||||
|  | |      Vultr       |                              [root/acme-dns-01-vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js)                              | | ||||||
|  | |       etcd       |                                  [ceecko/le-challenge-etcd](https://github.com/ceecko/le-challenge-etcd)                                   | | ||||||
|  | |        -         |                     **Build Your Own** <br> [acme-dns-01-test](https://git.rootprojects.org/root/acme-dns-01-test.js)                      | | ||||||
|  | |    Full List     | Search [acme-dns-01-](https://www.npmjs.com/search?q=acme-dns-01-) or [le-challenge-](https://www.npmjs.com/search?q=le-challenge-) on npm | | ||||||
|  | 
 | ||||||
|  | ## Account & Certificate Storage | ||||||
|  | 
 | ||||||
|  | |                      |                                                Plugin                                                 | | ||||||
|  | | :------------------: | :---------------------------------------------------------------------------------------------------: | | ||||||
|  | |     **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)               | | ||||||
|  | |        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)      | | ||||||
|  | |      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)            | | ||||||
|  | |          -           | 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                  | | ||||||
|  | 
 | ||||||
|  | ## Auto-SNI | ||||||
|  | 
 | ||||||
|  | |             |                             Plugin                              | | ||||||
|  | | :---------: | :-------------------------------------------------------------: | | ||||||
|  | | **Default** | [le-sni-auto](https://git.coolaj86.com/coolaj86/le-sni-auto.js) | | ||||||
|  | 
 | ||||||
|  | (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. | ||||||
|  | 
 | ||||||
|  | # Usage | ||||||
|  | 
 | ||||||
|  | The oversimplified example was the bait | ||||||
|  | (because everyone seems to want an example that fits in 3 lines, even if it's terribly bad practices), | ||||||
|  | now here's the switch. | ||||||
|  | 
 | ||||||
|  | We have another completely working example that will provides a little more to build off of. | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | git clone https://git.rootprojects.org/root/greenlock-express.js.git | ||||||
|  | pushd greenlock-express.js | ||||||
|  |   npm install | ||||||
|  | popd | ||||||
|  | 
 | ||||||
|  | # replace 'fooCheckDb' in | ||||||
|  | # greenlock-express.js/examples/normal.js | ||||||
|  | 
 | ||||||
|  | node greenlock-express.js/examples/normal.js | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | It looks a little more like this: | ||||||
|  | 
 | ||||||
|  | `serve.js`: | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | // returns an instance of greenlock.js with additional helper methods | ||||||
|  | var glx = require("greenlock-express").create({ | ||||||
|  | 	server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 	// Note: If at first you don't succeed, stop and switch to staging: | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory | ||||||
|  | 	version: "draft-11", // Let's Encrypt v2 (ACME v2) | ||||||
|  | 
 | ||||||
|  | 	// If you wish to replace the default account and domain key storage plugin | ||||||
|  | 	store: require("le-store-certbot").create({ | ||||||
|  | 		configDir: require("path").join(require("os").homedir(), "acme", "etc"), | ||||||
|  | 		webrootPath: "/tmp/acme-challenges" | ||||||
|  | 	}), | ||||||
|  | 
 | ||||||
|  | 	// Contribute telemetry data to the project | ||||||
|  | 	telemetry: true, | ||||||
|  | 
 | ||||||
|  | 	// the default servername to use when the client doesn't specify | ||||||
|  | 	// (because some IoT devices don't support servername indication) | ||||||
|  | 	servername: "example.com", | ||||||
|  | 
 | ||||||
|  | 	approveDomains: approveDomains | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server = glx.listen(80, 443, function() { | ||||||
|  | 	console.log("Listening on port 80 for ACME challenges and 443 for express app."); | ||||||
|  | }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Note: You shouldn't be using the plain HTTP server for anything except, potentially, for error handling | ||||||
|  | on the listen event (if the default print-and-quit behavior doesn't work for your use case). | ||||||
|  | If you need to do that, here's how: | ||||||
|  | 
 | ||||||
|  | ``` | ||||||
|  | var plainServer = server.unencrypted; | ||||||
|  | plainServer.on('error', function (err) { ... }); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The Automatic Certificate Issuance is initiated via SNI (`httpsOptions.SNICallback`). | ||||||
|  | For security, domain validation MUST have an approval callback in _production_. | ||||||
|  | 
 | ||||||
|  | ```javascript | ||||||
|  | var http01 = require("le-challenge-fs").create({ webrootPath: "/tmp/acme-challenges" }); | ||||||
|  | function approveDomains(opts, certs, cb) { | ||||||
|  | 	// This is where you check your database and associated | ||||||
|  | 	// email addresses with domains and agreements and such | ||||||
|  | 	// if (!isAllowed(opts.domains)) { return cb(new Error("not allowed")); } | ||||||
|  | 
 | ||||||
|  | 	// The domains being approved for the first time are listed in opts.domains | ||||||
|  | 	// Certs being renewed are listed in certs.altnames (if that's useful) | ||||||
|  | 
 | ||||||
|  | 	// Opt-in to submit stats and get important updates | ||||||
|  | 	opts.communityMember = true; | ||||||
|  | 
 | ||||||
|  | 	// If you wish to replace the default challenge plugin, you may do so here | ||||||
|  | 	opts.challenges = { "http-01": http01 }; | ||||||
|  | 
 | ||||||
|  | 	opts.email = "john.doe@example.com"; | ||||||
|  | 	opts.agreeTos = true; | ||||||
|  | 
 | ||||||
|  | 	// NOTE: you can also change other options such as `challengeType` and `challenge` | ||||||
|  | 	// opts.challengeType = 'http-01'; | ||||||
|  | 	// opts.challenge = require('le-challenge-fs').create({}); | ||||||
|  | 
 | ||||||
|  | 	cb(null, { options: opts, certs: certs }); | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| And start your server: | ```javascript | ||||||
|  | // handles acme-challenge and redirects to https | ||||||
|  | require("http") | ||||||
|  | 	.createServer(glx.middleware(require("redirect-https")())) | ||||||
|  | 	.listen(80, function() { | ||||||
|  | 		console.log("Listening for ACME http-01 challenges on", this.address()); | ||||||
|  | 	}); | ||||||
| 
 | 
 | ||||||
| ```bash | var app = require("express")(); | ||||||
| # Allow non-root node to use ports 80 (HTTP) and 443 (HTTPS) | app.use("/", function(req, res) { | ||||||
| sudo setcap 'cap_net_bind_service=+ep' $(which node) | 	res.end("Hello, World!"); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // handles your app | ||||||
|  | require("https") | ||||||
|  | 	.createServer(glx.httpsOptions, app) | ||||||
|  | 	.listen(443, function() { | ||||||
|  | 		console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); | ||||||
|  | 	}); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ```bash | **Security**: | ||||||
| # `npm start` will call `node ./server.js` by default |  | ||||||
| npm start |  | ||||||
| ``` |  | ||||||
| 
 | 
 | ||||||
| ```txt | Greenlock will do a self-check on all domain registrations | ||||||
| Greenlock v3.0.0 | to prevent you from hitting rate limits. | ||||||
| Greenlock Manager Config File: ~/.config/greenlock/manager.json |  | ||||||
| Greenlock Storage Directory: ~/.config/greenlock/ |  | ||||||
| 
 | 
 | ||||||
| Listening on 0.0.0.0:80 for ACME challenges and HTTPS redirects | # API | ||||||
| Listening on 0.0.0.0:443 for secure traffic |  | ||||||
| ``` |  | ||||||
| 
 | 
 | ||||||
| </details> | This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). | ||||||
| 
 | 
 | ||||||
| <details> | The API is actually located at [greenlock.js options](https://git.rootprojects.org/root/greenlock.js) | ||||||
| <summary>4. Manage SSL Certificates and Domains</summary> | (because all options are simply passed through to `greenlock.js` proper without modification). | ||||||
| 
 | 
 | ||||||
| ## 4. Manage domains | The only "API" consists of two options, the rest is just a wrapper around `greenlock.js` to take LOC from 15 to 5: | ||||||
| 
 | 
 | ||||||
| The management API is built to work with Databases, S3, etc. | - `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. | ||||||
|  |   - `listen(80, 443)` | ||||||
|  |   - `listen(80, 443, onListenSecure)` | ||||||
|  |   - `listen(80, 443, onListenPlain, onListenSecure)` | ||||||
|  |   - `listen('localhost:80', '0.0.0.0:443')` | ||||||
|  |   - `listen('[::1]:80', '[::]:443')` | ||||||
|  |   - `listen('/tmp/glx.plain.sock', '/tmp/glx.secure.sock')` | ||||||
| 
 | 
 | ||||||
| HOWEVER, by default it starts with a simple config file. | Brief overview of some simple options for `greenlock.js`: | ||||||
| 
 | 
 | ||||||
| <!-- | - `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production | ||||||
| This will update the config file (assuming the default fs-based management plugin): | - `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.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.approveDomains` can be either of: | ||||||
|  |   - 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) | ||||||
|  | - `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. | ||||||
| 
 | 
 | ||||||
| `~/.config/greenlock/manager.json`: | ## Supported ACME versions | ||||||
| 
 | 
 | ||||||
| ```json | - Let's Encrypt v1 (aka v01) | ||||||
| { | - Let's Encrypt v2 (aka v02 or ACME draft 11) | ||||||
| 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | - ACME draft 11 (ACME v2 is a misnomer) | ||||||
| 	"agreeToTerms": true, | - Wildcard domains (via dns-01 challenges) | ||||||
| 	"sites": { |   - `*.example.com` | ||||||
| 		"example.com": { |  | ||||||
| 			"subject": "example.com", |  | ||||||
| 			"altnames": ["example.com", "www.example.com"] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| 
 | 
 | ||||||
| COMING SOON | <small>tags: letsencrypt acme free ssl automated https node express.js</small> | ||||||
| 
 |  | ||||||
| Management can be done via the **CLI** or the JavaScript [**API**](https://git.rootprojects.org/root/greenlock.js/). |  | ||||||
| Since this is the QuickStart, we'll demo the **CLI**: |  | ||||||
| 
 |  | ||||||
| You need to create a Let's Encrypt _subscriber account_, which can be done globally, or per-site. |  | ||||||
| All individuals, and most businesses, should set this globally: |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| # COMING SOON |  | ||||||
| # (this command should be here by Nov 5th) |  | ||||||
| # (edit the config by hand for now) |  | ||||||
| # |  | ||||||
| # Set a global subscriber account |  | ||||||
| npx greenlock config --subscriber-email 'mycompany@example.com' --agree-to-terms true |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| <!-- todo print where the key was saved --> |  | ||||||
| 
 |  | ||||||
| A Let's Encrypt SSL certificate has a "Subject" (Primary Domain) and up to 100 "Alternative Names" |  | ||||||
| (of which the first _must_ be the subject). |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| # COMING SOON |  | ||||||
| # (this command should be here by Nov 5th) |  | ||||||
| # (edit the config by hand for now) |  | ||||||
| # |  | ||||||
| # Add a certificate with specific domains |  | ||||||
| npx greenlock add --subject example.com --altnames example.com,www.example.com |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| <!-- todo print where the cert was saved --> |  | ||||||
| 
 |  | ||||||
| Note: **Localhost**, **Wildcard**, and Certificates for Private Networks require |  | ||||||
| [**DNS validation**](https://git.rootprojects.org/root/greenlock-exp). |  | ||||||
| 
 |  | ||||||
| - DNS Validation |  | ||||||
|   - [**Wildcards**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcards/) (coming soon) |  | ||||||
|   - [**Localhost**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/localhost/) (coming soon) |  | ||||||
|   - [**CI/CD**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/ci-cd/) (coming soon) |  | ||||||
| 
 |  | ||||||
| </details> |  | ||||||
| 
 |  | ||||||
| # Plenty of Examples |  | ||||||
| 
 |  | ||||||
| **These are in-progress** Check back tomorrow (Nov 2nd, 2019). |  | ||||||
| 
 |  | ||||||
| - [greenlock-express.js/examples/](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples) |  | ||||||
|   - [Express](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/express/) |  | ||||||
|   - [Node's **http2**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http2/) |  | ||||||
|   - [Node's https](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/https/) |  | ||||||
|   - [**WebSockets**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/websockets/) |  | ||||||
|   - [Socket.IO](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/socket-io/) |  | ||||||
|   - [Cluster](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/cluster/) |  | ||||||
|   - [**Wildcards**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/wildcards/) (coming soon) |  | ||||||
|   - [**Localhost**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/localhost/) (coming soon) |  | ||||||
|   - [**CI/CD**](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/ci-cd/) (coming soon) |  | ||||||
|   - [HTTP Proxy](https://git.rootprojects.org/root/greenlock-express.js/src/branch/master/examples/http-proxy/) |  | ||||||
| 
 |  | ||||||
| # Easy to Customize |  | ||||||
| 
 |  | ||||||
| <!-- greenlock-manager-test => greenlock-manager-custom --> |  | ||||||
| 
 |  | ||||||
| <!-- |  | ||||||
| - [greenlock.js/examples/](https://git.rootprojects.org/root/greenlock.js/src/branch/master/examples) |  | ||||||
| --> |  | ||||||
| 
 |  | ||||||
| - [Custom Domain Management](https://git.rootprojects.org/root/greenlock-manager-test.js) |  | ||||||
| - [Custom Key & Cert Storage](https://git.rootprojects.org/root/greenlock-store-test.js) |  | ||||||
| - [Custom ACME HTTP-01 Challenges](https://git.rootprojects.org/root/acme-http-01-test.js) |  | ||||||
| - [Custom ACME DNS-01 Challenges](https://git.rootprojects.org/root/acme-dns-01-test.js) |  | ||||||
| 
 |  | ||||||
| # Ready-made Integrations |  | ||||||
| 
 |  | ||||||
| Greenlock Express integrates between Let's Encrypt's ACME Challenges and many popular services. |  | ||||||
| 
 |  | ||||||
| | Type        | Service                                                                             | Plugin                   | |  | ||||||
| | ----------- | ----------------------------------------------------------------------------------- | ------------------------ | |  | ||||||
| | dns-01      | CloudFlare                                                                          | acme-dns-01-cloudflare   | |  | ||||||
| | dns-01      | [Digital Ocean](https://git.rootprojects.org/root/acme-dns-01-digitalocean.js)      | acme-dns-01-digitalocean | |  | ||||||
| | dns-01      | [DNSimple](https://git.rootprojects.org/root/acme-dns-01-dnsimple.js)               | acme-dns-01-dnsimple     | |  | ||||||
| | dns-01      | [DuckDNS](https://git.rootprojects.org/root/acme-dns-01-duckdns.js)                 | acme-dns-01-duckdns      | |  | ||||||
| | http-01     | File System / [Web Root](https://git.rootprojects.org/root/acme-http-01-webroot.js) | acme-http-01-webroot     | |  | ||||||
| | dns-01      | [GoDaddy](https://git.rootprojects.org/root/acme-dns-01-godaddy.js)                 | acme-dns-01-godaddy      | |  | ||||||
| | dns-01      | [Gandi](https://git.rootprojects.org/root/acme-dns-01-gandi.js)                     | acme-dns-01-gandi        | |  | ||||||
| | dns-01      | [NameCheap](https://git.rootprojects.org/root/acme-dns-01-namecheap.js)             | acme-dns-01-namecheap    | |  | ||||||
| | dns-01      | [Name.com](https://git.rootprojects.org/root/acme-dns-01-namedotcom.js)         | acme-dns-01-namedotcom   | |  | ||||||
| | dns-01      | Route53 (AWS)                                                                       | acme-dns-01-route53      | |  | ||||||
| | http-01     | S3 (AWS, Digital Ocean, Scaleway)                                                   | acme-http-01-s3          | |  | ||||||
| | dns-01      | [Vultr](https://git.rootprojects.org/root/acme-dns-01-vultr.js)                     | acme-dns-01-vultr        | |  | ||||||
| | dns-01      | [Build your own](https://git.rootprojects.org/root/acme-dns-01-test.js)             | acme-dns-01-test         | |  | ||||||
| | http-01     | [Build your own](https://git.rootprojects.org/root/acme-http-01-test.js)            | acme-http-01-test        | |  | ||||||
| | tls-alpn-01 | [Contact us](mailto:support@therootcompany.com)                                     | -                        | |  | ||||||
| 
 |  | ||||||
| Search `acme-http-01-` or `acme-dns-01-` on npm to find more. |  | ||||||
| 
 |  | ||||||
| # Full Documentation |  | ||||||
| 
 |  | ||||||
| <!-- |  | ||||||
| - Greenlock CLI |  | ||||||
| - Greenlock JavaScript API |  | ||||||
| --> |  | ||||||
| 
 |  | ||||||
| Most of the documentation is done by use-case examples, as shown up at the top of the README. |  | ||||||
| 
 |  | ||||||
| We're working on more comprehensive documentation for this newly released version. |  | ||||||
| **Please open an issue** with questions in the meantime. |  | ||||||
| 
 |  | ||||||
| # Commercial Support |  | ||||||
| 
 |  | ||||||
| Do you need... |  | ||||||
| 
 |  | ||||||
| - training? |  | ||||||
| - specific features? |  | ||||||
| - different integrations? |  | ||||||
| - bugfixes, on _your_ timeline? |  | ||||||
| - custom code, built by experts? |  | ||||||
| - commercial support and licensing? |  | ||||||
| 
 |  | ||||||
| You're welcome to [contact us](mailto:aj@therootcompany.com) in regards to IoT, On-Prem, |  | ||||||
| Enterprise, and Internal installations, integrations, and deployments. |  | ||||||
| 
 |  | ||||||
| We have both commercial support and commercial licensing available. |  | ||||||
| 
 |  | ||||||
| We also offer consulting for all-things-ACME and Let's Encrypt. |  | ||||||
| 
 | 
 | ||||||
| # Legal & Rules of the Road | # Legal & Rules of the Road | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								demo.js
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								demo.js
									
									
									
									
									
								
							| @ -1,35 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| require("./") |  | ||||||
| 	.init(initialize) |  | ||||||
| 	.serve(worker) |  | ||||||
| 	.master(function() { |  | ||||||
| 		console.log("Hello from master"); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| function initialize() { |  | ||||||
| 	var pkg = require("./package.json"); |  | ||||||
| 	var config = { |  | ||||||
| 		package: { |  | ||||||
| 			name: "Greenlock_Express_Demo", |  | ||||||
| 			version: pkg.version, |  | ||||||
| 			author: pkg.author |  | ||||||
| 		}, |  | ||||||
| 		staging: true, |  | ||||||
| 		cluster: true, |  | ||||||
| 
 |  | ||||||
| 		notify: function(ev, params) { |  | ||||||
| 			console.info(ev, params); |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 	return config; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function worker(glx) { |  | ||||||
| 	console.info(); |  | ||||||
| 	console.info("Hello from worker #" + glx.id()); |  | ||||||
| 
 |  | ||||||
| 	glx.serveApp(function(req, res) { |  | ||||||
| 		res.end("Hello, Encrypted World!"); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| @ -1,39 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "websocket-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 
 |  | ||||||
| 			// When you're ready to go full cloud scale, you just change this to true:
 |  | ||||||
| 			// Note: in cluster you CANNOT use in-memory state (see below)
 |  | ||||||
| 			cluster: true, |  | ||||||
| 
 |  | ||||||
|       // This will default to the number of workers being equal to
 |  | ||||||
|       // n-1 cpus, with a minimum of 2
 |  | ||||||
|       workers: 4 |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	// WRONG
 |  | ||||||
| 	// This won't work like you
 |  | ||||||
| 	// think because EACH worker
 |  | ||||||
| 	// has ITS OWN `count`.
 |  | ||||||
| 	var count = 0; |  | ||||||
| 
 |  | ||||||
| 	var app = function(req, res) { |  | ||||||
| 		res.end("Hello... how many times now? Oh, " + count + " times"); |  | ||||||
| 		count += 1; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	// Serves on 80 and 443... for each worker
 |  | ||||||
| 	// Get's SSL certificates magically!
 |  | ||||||
| 	glx.serveApp(app); |  | ||||||
| } |  | ||||||
							
								
								
									
										75
									
								
								examples/demo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								examples/demo.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,75 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | // npm install spdy@3.x
 | ||||||
|  | 
 | ||||||
|  | //var Greenlock = require('greenlock-express')
 | ||||||
|  | var Greenlock = require("../"); | ||||||
|  | 
 | ||||||
|  | var greenlock = Greenlock.create({ | ||||||
|  | 	// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 	version: "draft-11", | ||||||
|  | 
 | ||||||
|  | 	server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 	// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 	// You MUST change this to a valid email address
 | ||||||
|  | 	email: "jon@example.com", | ||||||
|  | 
 | ||||||
|  | 	// You MUST NOT build clients that accept the ToS without asking the user
 | ||||||
|  | 	agreeTos: true, | ||||||
|  | 
 | ||||||
|  | 	// You MUST change these to valid domains
 | ||||||
|  | 	// NOTE: all domains will validated and listed on the certificate
 | ||||||
|  | 	approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 
 | ||||||
|  | 	// You MUST have access to write to directory where certs are saved
 | ||||||
|  | 	// ex: /home/foouser/acme/etc
 | ||||||
|  | 	configDir: "~/.config/acme/", | ||||||
|  | 
 | ||||||
|  | 	// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 	communityMember: true | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // http-01 Challenges //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | // http-01 challenge happens over http/1.1, not http2
 | ||||||
|  | var redirectHttps = require("redirect-https")(); | ||||||
|  | var acmeChallengeHandler = greenlock.middleware(function(req, res) { | ||||||
|  | 	res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 	res.end( | ||||||
|  | 		"<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() { | ||||||
|  | 		console.log("Listening for ACME http-01 challenges on", this.address()); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // http2 via SPDY h2  //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | // spdy is a drop-in replacement for the https API
 | ||||||
|  | var spdyOptions = Object.assign({}, greenlock.tlsOptions); | ||||||
|  | spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; | ||||||
|  | var server = require("spdy").createServer( | ||||||
|  | 	spdyOptions, | ||||||
|  | 	require("express")().use("/", function(req, res) { | ||||||
|  | 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 		res.end("<h1>Hello, 🔐 Secure World!</h1>"); | ||||||
|  | 	}) | ||||||
|  | ); | ||||||
|  | server.on("error", function(err) { | ||||||
|  | 	console.error(err); | ||||||
|  | }); | ||||||
|  | server.on("listening", function() { | ||||||
|  | 	console.log("Listening for SPDY/http2/https requests on", this.address()); | ||||||
|  | }); | ||||||
|  | server.listen(443); | ||||||
| @ -1,27 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	var app = require("./my-express-app.js"); |  | ||||||
| 
 |  | ||||||
| 	app.get("/hello", function(req, res) { |  | ||||||
| 		res.end("Hello, Encrypted World!"); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// Serves on 80 and 443
 |  | ||||||
| 	// Get's SSL certificates magically!
 |  | ||||||
| 	glx.serveApp(app); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "http2-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
							
								
								
									
										30
									
								
								examples/force-renew.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								examples/force-renew.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | //require('greenlock-express')
 | ||||||
|  | require("../") | ||||||
|  | 	.create({ | ||||||
|  | 		// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 		version: "draft-11", | ||||||
|  | 
 | ||||||
|  | 		server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 		// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 		// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 		email: "john.doe@example.com", | ||||||
|  | 
 | ||||||
|  | 		agreeTos: true, | ||||||
|  | 
 | ||||||
|  | 		approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 
 | ||||||
|  | 		app: require("express")().use("/", function(req, res) { | ||||||
|  | 			res.end("Hello, World!"); | ||||||
|  | 		}), | ||||||
|  | 
 | ||||||
|  | 		renewWithin: 91 * 24 * 60 * 60 * 1000, | ||||||
|  | 		renewBy: 90 * 24 * 60 * 60 * 1000, | ||||||
|  | 
 | ||||||
|  | 		// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 		communityMember: true, | ||||||
|  | 		debug: true | ||||||
|  | 	}) | ||||||
|  | 	.listen(80, 443); | ||||||
| @ -1,44 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	// we need the raw https server
 |  | ||||||
| 	var server = glx.httpsServer(); |  | ||||||
| 	var proxy = require("http-proxy").createProxyServer({ xfwd: true }); |  | ||||||
| 
 |  | ||||||
| 	// catches error events during proxying
 |  | ||||||
| 	proxy.on("error", function(err, req, res) { |  | ||||||
| 		console.error(err); |  | ||||||
| 		res.statusCode = 500; |  | ||||||
| 		res.end(); |  | ||||||
| 		return; |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// We'll proxy websockets too
 |  | ||||||
| 	server.on("upgrade", function(req, socket, head) { |  | ||||||
| 		proxy.ws(req, socket, head, { |  | ||||||
| 			ws: true, |  | ||||||
| 			target: "ws://localhost:3000" |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// servers a node app that proxies requests to a localhost
 |  | ||||||
| 	glx.serveApp(function(req, res) { |  | ||||||
| 		proxy.web(req, res, { |  | ||||||
| 			target: "http://localhost:3000" |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "http-proxy-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
| @ -1,42 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| 
 |  | ||||||
| // The WRONG way:
 |  | ||||||
| //var http = require('http');
 |  | ||||||
| //var httpServer = https.createSecureServer(redirectToHttps);
 |  | ||||||
| //
 |  | ||||||
| // Why is that wrong?
 |  | ||||||
| // Greenlock needs to change some low-level http and https options.
 |  | ||||||
| // Use glx.httpServer(redirectToHttps) instead.
 |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	//
 |  | ||||||
| 	// HTTP can only be used for ACME HTTP-01 Challenges
 |  | ||||||
| 	// (and it is not required for DNS-01 challenges)
 |  | ||||||
| 	//
 |  | ||||||
| 
 |  | ||||||
| 	// Get the raw http server:
 |  | ||||||
| 	var httpServer = glx.httpServer(function(req, res) { |  | ||||||
| 		res.statusCode = 301; |  | ||||||
| 		res.setHeader("Location", "https://" + req.headers.host + req.path); |  | ||||||
| 		res.end("Insecure connections are not allowed. Redirecting..."); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	httpServer.listen(80, "0.0.0.0", function() { |  | ||||||
| 		console.info("Listening on ", httpServer.address()); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "plain-http-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
							
								
								
									
										70
									
								
								examples/http2.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								examples/http2.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | //var Greenlock = require('greenlock-express')
 | ||||||
|  | var Greenlock = require("../"); | ||||||
|  | 
 | ||||||
|  | var greenlock = Greenlock.create({ | ||||||
|  | 	// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 	version: "draft-11", | ||||||
|  | 
 | ||||||
|  | 	server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 	// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 	// You MUST change this to a valid email address
 | ||||||
|  | 	email: "jon@example.com", | ||||||
|  | 
 | ||||||
|  | 	// You MUST NOT build clients that accept the ToS without asking the user
 | ||||||
|  | 	agreeTos: true, | ||||||
|  | 
 | ||||||
|  | 	// You MUST change these to valid domains
 | ||||||
|  | 	// NOTE: all domains will validated and listed on the certificate
 | ||||||
|  | 	approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 
 | ||||||
|  | 	// You MUST have access to write to directory where certs are saved
 | ||||||
|  | 	// ex: /home/foouser/acme/etc
 | ||||||
|  | 	configDir: "~/.config/acme/", | ||||||
|  | 
 | ||||||
|  | 	// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 	communityMember: true | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // http-01 Challenges //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | // http-01 challenge happens over http/1.1, not http2
 | ||||||
|  | var redirectHttps = require("redirect-https")(); | ||||||
|  | var acmeChallengeHandler = greenlock.middleware(redirectHttps); | ||||||
|  | require("http") | ||||||
|  | 	.createServer(acmeChallengeHandler) | ||||||
|  | 	.listen(80, function() { | ||||||
|  | 		console.log("Listening for ACME http-01 challenges on", this.address()); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // node.js' http2 api //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | // http2 is a new API with which you would use hapi or koa, not express
 | ||||||
|  | var server = require("http2").createSecureServer(greenlock.tlsOptions); | ||||||
|  | server.on("error", function(err) { | ||||||
|  | 	console.error(err); | ||||||
|  | }); | ||||||
|  | // WARNING: Because the middleware don't handle this API style,
 | ||||||
|  | // the Host headers are unmodified and potentially dangerous
 | ||||||
|  | // (ex: Host: Robert'); DROP TABLE Students;)
 | ||||||
|  | server.on("stream", function(stream, headers) { | ||||||
|  | 	console.log(headers); | ||||||
|  | 	stream.respond({ | ||||||
|  | 		"content-type": "text/html", | ||||||
|  | 		":status": 200 | ||||||
|  | 	}); | ||||||
|  | 	stream.end("Hello, HTTP2 World!"); | ||||||
|  | }); | ||||||
|  | server.on("listening", function() { | ||||||
|  | 	console.log("Listening for http2 requests on", this.address()); | ||||||
|  | }); | ||||||
|  | server.listen(443); | ||||||
| @ -1,48 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| 
 |  | ||||||
| // The WRONG way:
 |  | ||||||
| //var http2 = require('http2');
 |  | ||||||
| //var http2Server = https.createSecureServer(tlsOptions, app);
 |  | ||||||
| //
 |  | ||||||
| // Why is that wrong?
 |  | ||||||
| // Greenlock needs to change some low-level http and https options.
 |  | ||||||
| // Use glx.httpsServer(tlsOptions, app) instead.
 |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	//
 |  | ||||||
| 	// HTTP2 is the default httpsServer for node v12+
 |  | ||||||
| 	// (HTTPS/1.1 is used for node <= v11)
 |  | ||||||
| 	//
 |  | ||||||
| 
 |  | ||||||
| 	// Get the raw http2 server:
 |  | ||||||
| 	var http2Server = glx.httpsServer(function(req, res) { |  | ||||||
| 		res.end("Hello, Encrypted World!"); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	http2Server.listen(443, "0.0.0.0", function() { |  | ||||||
| 		console.info("Listening on ", http2Server.address()); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// Note:
 |  | ||||||
| 	// You must ALSO listen on port 80 for ACME HTTP-01 Challenges
 |  | ||||||
| 	// (the ACME and http->https middleware are loaded by glx.httpServer)
 |  | ||||||
| 	var httpServer = glx.httpServer(); |  | ||||||
| 	httpServer.listen(80, "0.0.0.0", function() { |  | ||||||
| 		console.info("Listening on ", httpServer.address()); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "http2-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
| @ -1,49 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| 
 |  | ||||||
| // The WRONG way:
 |  | ||||||
| //var https = require('https');
 |  | ||||||
| //var httpsServer = https.createServer(tlsOptions, app);
 |  | ||||||
| //
 |  | ||||||
| // Why is that wrong?
 |  | ||||||
| // Greenlock needs to change some low-level http and https options.
 |  | ||||||
| // Use glx.httpsServer(tlsOptions, app) instead.
 |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	//
 |  | ||||||
| 	// HTTPS/1.1 is only used for node v11 or lower
 |  | ||||||
| 	// (HTTP2 is used for node v12+)
 |  | ||||||
| 	//
 |  | ||||||
| 	// Why not just require('https')?
 |  | ||||||
| 
 |  | ||||||
| 	// Get the raw https server:
 |  | ||||||
| 	var httpsServer = glx.httpsServer(null, function(req, res) { |  | ||||||
| 		res.end("Hello, Encrypted World!"); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	httpsServer.listen(443, "0.0.0.0", function() { |  | ||||||
| 		console.info("Listening on ", httpsServer.address()); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// Note:
 |  | ||||||
| 	// You must ALSO listen on port 80 for ACME HTTP-01 Challenges
 |  | ||||||
| 	// (the ACME and http->https middleware are loaded by glx.httpServer)
 |  | ||||||
| 	var httpServer = glx.httpServer(); |  | ||||||
| 	httpServer.listen(80, "0.0.0.0", function() { |  | ||||||
| 		console.info("Listening on ", httpServer.address()); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "https1-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
							
								
								
									
										88
									
								
								examples/production.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								examples/production.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // My Secure Server
 | ||||||
|  | //
 | ||||||
|  | //var greenlock = require('greenlock-express')
 | ||||||
|  | var greenlock = require("../").create({ | ||||||
|  | 	// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 	// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 	server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 	version: "draft-11", | ||||||
|  | 	// You MUST have write access to save certs
 | ||||||
|  | 	configDir: "~/.config/acme/", | ||||||
|  | 
 | ||||||
|  | 	// The previous 'simple' example set these values statically,
 | ||||||
|  | 	// but this example uses approveDomains() to set them dynamically
 | ||||||
|  | 	//, email: 'none@see.note.above'
 | ||||||
|  | 	//, agreeTos: false
 | ||||||
|  | 
 | ||||||
|  | 	// approveDomains is the right place to check a database for
 | ||||||
|  | 	// email addresses with domains and agreements and such
 | ||||||
|  | 	approveDomains: approveDomains, | ||||||
|  | 
 | ||||||
|  | 	app: require("./my-express-app.js"), | ||||||
|  | 
 | ||||||
|  | 	// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 	communityMember: true | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server = greenlock.listen(80, 443); | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // My Secure Database Check
 | ||||||
|  | //
 | ||||||
|  | function approveDomains(opts, certs, cb) { | ||||||
|  | 	// Only one domain is listed with *automatic* registration via SNI
 | ||||||
|  | 	// (it's an array because managed registration allows for multiple domains,
 | ||||||
|  | 	//                                which was the case in the simple example)
 | ||||||
|  | 	console.log(opts.domains); | ||||||
|  | 
 | ||||||
|  | 	// The domains being approved for the first time are listed in opts.domains
 | ||||||
|  | 	// Certs being renewed are listed in certs.altnames
 | ||||||
|  | 	if (certs) { | ||||||
|  | 		opts.domains = [certs.subject].concat(certs.altnames); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fooCheckDb(opts.domains, function(err, agree, email) { | ||||||
|  | 		if (err) { | ||||||
|  | 			cb(err); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Services SHOULD automatically accept the ToS and use YOUR email
 | ||||||
|  | 		// Clients MUST NOT accept the ToS without asking the user
 | ||||||
|  | 		opts.agreeTos = agree; | ||||||
|  | 		opts.email = email; | ||||||
|  | 
 | ||||||
|  | 		// NOTE: you can also change other options such as `challengeType` and `challenge`
 | ||||||
|  | 		// (this would be helpful if you decided you wanted wildcard support as a domain altname)
 | ||||||
|  | 		// opts.challengeType = 'http-01';
 | ||||||
|  | 		// opts.challenge = require('le-challenge-fs').create({});
 | ||||||
|  | 
 | ||||||
|  | 		cb(null, { options: opts, certs: certs }); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // My User / Domain Database
 | ||||||
|  | //
 | ||||||
|  | function fooCheckDb(domains, cb) { | ||||||
|  | 	// 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
 | ||||||
|  | 	var domains = ["example.com", "www.example.com"]; | ||||||
|  | 	var userEmail = "john.doe@example.com"; | ||||||
|  | 	var userAgrees = true; | ||||||
|  | 	var passCheck = opts.domains.every(function(domain) { | ||||||
|  | 		return -1 !== domains.indexOf(domain); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	if (!passCheck) { | ||||||
|  | 		cb(new Error("domain not allowed")); | ||||||
|  | 	} else { | ||||||
|  | 		cb(null, userAgrees, userEmail); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										38
									
								
								examples/quickstart.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								examples/quickstart.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | //require('greenlock-express')
 | ||||||
|  | require("../") | ||||||
|  | 	.create({ | ||||||
|  | 		// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 		version: "draft-11", | ||||||
|  | 
 | ||||||
|  | 		server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 		// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 		// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 		// You MUST change this to a valid email address
 | ||||||
|  | 		email: "john.doe@example.com", | ||||||
|  | 
 | ||||||
|  | 		// You MUST NOT build clients that accept the ToS without asking the user
 | ||||||
|  | 		agreeTos: true, | ||||||
|  | 
 | ||||||
|  | 		// You MUST change these to valid domains
 | ||||||
|  | 		// NOTE: all domains will validated and listed on the certificate
 | ||||||
|  | 		approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 
 | ||||||
|  | 		// You MUST have access to write to directory where certs are saved
 | ||||||
|  | 		// ex: /home/foouser/acme/etc
 | ||||||
|  | 		configDir: "~/.config/acme/", | ||||||
|  | 		store: require("greenlock-store-fs"), | ||||||
|  | 
 | ||||||
|  | 		app: require("express")().use("/", function(req, res) { | ||||||
|  | 			res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 			res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | 		}), | ||||||
|  | 
 | ||||||
|  | 		// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 		communityMember: true | ||||||
|  | 
 | ||||||
|  | 		//, debug: true
 | ||||||
|  | 	}) | ||||||
|  | 	.listen(80, 443); | ||||||
| @ -1,22 +0,0 @@ | |||||||
| # Quick Start for Let's Encrypt with Node.js |  | ||||||
| 
 |  | ||||||
| ```js |  | ||||||
| npm install --save greenlock-express |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| Manage via API or the config file: |  | ||||||
| 
 |  | ||||||
| `~/.config/greenlock/manage.json`: (default filesystem config) |  | ||||||
| 
 |  | ||||||
| ```json |  | ||||||
| { |  | ||||||
| 	"subscriberEmail": "letsencrypt-test@therootcompany.com", |  | ||||||
| 	"agreeToTerms": true, |  | ||||||
| 	"sites": { |  | ||||||
| 		"example.com": { |  | ||||||
| 			"subject": "example.com", |  | ||||||
| 			"altnames": ["example.com", "www.example.com"] |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
| @ -1,32 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	// This can be a node http app (shown),
 |  | ||||||
| 	// an Express app, or Hapi, Koa, Rill, etc
 |  | ||||||
| 	var app = function(req, res) { |  | ||||||
| 		res.end("Hello, Encrypted World!"); |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	// Serves on 80 and 443
 |  | ||||||
| 	// Get's SSL certificates magically!
 |  | ||||||
| 	glx.serveApp(app); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			// Package name+version is used for ACME client user agent
 |  | ||||||
| 			package: { name: "websocket-example", version: pkg.version }, |  | ||||||
| 
 |  | ||||||
| 			// Maintainer email is the contact for critical bug and security notices
 |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 
 |  | ||||||
| 			// Change to true when you're ready to make your app cloud-scale
 |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
							
								
								
									
										104
									
								
								examples/remote-access.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								examples/remote-access.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // WARNING: Not for noobs
 | ||||||
|  | // Try the simple example first
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // This demo is used with tunnel-server.js and tunnel-client.js
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | var email = "john.doe@gmail.com"; | ||||||
|  | var domains = ["example.com"]; | ||||||
|  | var agreeLeTos = true; | ||||||
|  | //var secret = "My Little Brony";
 | ||||||
|  | var secret = require("crypto") | ||||||
|  | 	.randomBytes(16) | ||||||
|  | 	.toString("hex"); | ||||||
|  | 
 | ||||||
|  | require("../") | ||||||
|  | 	.create({ | ||||||
|  | 		version: "draft-11", | ||||||
|  | 
 | ||||||
|  | 		server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 		// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 		// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 		email: email, | ||||||
|  | 		agreeTos: agreeLeTos, | ||||||
|  | 		approveDomains: domains, | ||||||
|  | 		configDir: "~/.config/acme/", | ||||||
|  | 		app: remoteAccess(secret), | ||||||
|  | 		// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 		communityMember: true | ||||||
|  | 		//, debug: true
 | ||||||
|  | 	}) | ||||||
|  | 	.listen(3000, 8443); | ||||||
|  | 
 | ||||||
|  | function remoteAccess(secret) { | ||||||
|  | 	var express = require("express"); | ||||||
|  | 	var basicAuth = require("express-basic-auth"); | ||||||
|  | 	var serveIndex = require("serve-index"); | ||||||
|  | 
 | ||||||
|  | 	var rootIndex = serveIndex("/", { hidden: true, icons: true, view: "details" }); | ||||||
|  | 	var rootFs = express.static("/", { dotfiles: "allow", redirect: true, index: false }); | ||||||
|  | 
 | ||||||
|  | 	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 app = express(); | ||||||
|  | 	var realm = "Login Required"; | ||||||
|  | 
 | ||||||
|  | 	var myAuth = basicAuth({ | ||||||
|  | 		users: { root: secret, user: secret }, | ||||||
|  | 		challenge: true, | ||||||
|  | 		realm: realm, | ||||||
|  | 		unauthorizedResponse: function(/*req*/) { | ||||||
|  | 			return 'Unauthorized <a href="/">Home</a>'; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	app.get("/", function(req, res) { | ||||||
|  | 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 		res.end('<a href="/browse/">View Files</a>' + "  |  " + '<a href="/logout/">Logout</a>'); | ||||||
|  | 	}); | ||||||
|  | 	app.use("/logout", function(req, res) { | ||||||
|  | 		res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 		res.setHeader("WWW-Authenticate", 'Basic realm="' + realm + '"'); | ||||||
|  | 		res.statusCode = 401; | ||||||
|  | 		//res.setHeader('Location', '/');
 | ||||||
|  | 		res.end('Logged out   |   <a href="/">Home</a>'); | ||||||
|  | 	}); | ||||||
|  | 	app.use("/browse", myAuth); | ||||||
|  | 	app.use("/browse", function(req, res, next) { | ||||||
|  | 		if ("root" === req.auth.user) { | ||||||
|  | 			rootFs(req, res, function() { | ||||||
|  | 				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("Usernames are\n"); | ||||||
|  | 	console.log("\troot"); | ||||||
|  | 	console.log("\tuser"); | ||||||
|  | 	console.log(""); | ||||||
|  | 	console.log("Password (for both) is\n"); | ||||||
|  | 	console.log("\t" + secret); | ||||||
|  | 	console.log(""); | ||||||
|  | 	console.log("Shhhh... It's a secret to everybody!"); | ||||||
|  | 	console.log(""); | ||||||
|  | 	console.log(""); | ||||||
|  | 
 | ||||||
|  | 	return app; | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								examples/socket.io.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								examples/socket.io.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | // First and foremost:
 | ||||||
|  | // 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.
 | ||||||
|  | // That said, it's popular.......
 | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | //var greenlock = require('greenlock-express');
 | ||||||
|  | var greenlock = require("../"); | ||||||
|  | var options = require("./greenlock-options.js"); | ||||||
|  | var socketio = require("socket.io"); | ||||||
|  | var server; | ||||||
|  | var io; | ||||||
|  | 
 | ||||||
|  | // Any node http app will do - whether express, raw http or whatever
 | ||||||
|  | options.app = require("express")().use("/", function(req, res) { | ||||||
|  | 	res.setHeader("Content-Type", "text/html; charset=utf-8"); | ||||||
|  | 	res.end("Hello, World!\n\n💚 🔒.js"); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // The server that's handed back from `listen` is a raw https server
 | ||||||
|  | server = greenlock.create(options).listen(80, 443); | ||||||
|  | io = socketio(server); | ||||||
|  | 
 | ||||||
|  | // Then you do your socket.io stuff
 | ||||||
|  | io.on("connection", function(socket) { | ||||||
|  | 	console.log("a user connected"); | ||||||
|  | 	socket.emit("Welcome"); | ||||||
|  | 
 | ||||||
|  | 	socket.on("chat message", function(msg) { | ||||||
|  | 		socket.broadcast.emit("chat message", msg); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
| @ -1,49 +0,0 @@ | |||||||
| // First and foremost:
 |  | ||||||
| // 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.
 |  | ||||||
| // That said, it's popular.......
 |  | ||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| // Note: You DO NOT NEED socket.io
 |  | ||||||
| //       You can just use WebSockets
 |  | ||||||
| //       (see the websocket example)
 |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	var socketio = require("socket.io"); |  | ||||||
| 	var io; |  | ||||||
| 
 |  | ||||||
| 	// we need the raw https server
 |  | ||||||
| 	var server = glx.httpsServer(); |  | ||||||
| 
 |  | ||||||
| 	io = socketio(server); |  | ||||||
| 
 |  | ||||||
| 	// Then you do your socket.io stuff
 |  | ||||||
| 	io.on("connection", function(socket) { |  | ||||||
| 		console.log("a user connected"); |  | ||||||
| 		socket.emit("Welcome"); |  | ||||||
| 
 |  | ||||||
| 		socket.on("chat message", function(msg) { |  | ||||||
| 			socket.broadcast.emit("chat message", msg); |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// servers a node app that proxies requests to a localhost
 |  | ||||||
| 	glx.serveApp(function(req, res) { |  | ||||||
| 		res.setHeader("Content-Type", "text/html; charset=utf-8"); |  | ||||||
| 		res.end("Hello, World!\n\n💚 🔒.js"); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "socket-io-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
							
								
								
									
										64
									
								
								examples/spdy.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								examples/spdy.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | // npm install spdy@3.x
 | ||||||
|  | 
 | ||||||
|  | //var Greenlock = require('greenlock-express')
 | ||||||
|  | var Greenlock = require("../"); | ||||||
|  | 
 | ||||||
|  | var greenlock = Greenlock.create({ | ||||||
|  | 	// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 	version: "draft-11", | ||||||
|  | 
 | ||||||
|  | 	server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 	// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 	// You MUST change this to a valid email address
 | ||||||
|  | 	email: "jon@example.com", | ||||||
|  | 
 | ||||||
|  | 	// You MUST NOT build clients that accept the ToS without asking the user
 | ||||||
|  | 	agreeTos: true, | ||||||
|  | 
 | ||||||
|  | 	// You MUST change these to valid domains
 | ||||||
|  | 	// NOTE: all domains will validated and listed on the certificate
 | ||||||
|  | 	approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 
 | ||||||
|  | 	// You MUST have access to write to directory where certs are saved
 | ||||||
|  | 	// ex: /home/foouser/acme/etc
 | ||||||
|  | 	configDir: "~/.config/acme/", // MUST have write access
 | ||||||
|  | 
 | ||||||
|  | 	// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 	communityMember: true | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // http-01 Challenges //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | // http-01 challenge happens over http/1.1, not http2
 | ||||||
|  | var redirectHttps = require("redirect-https")(); | ||||||
|  | var acmeChallengeHandler = greenlock.middleware(redirectHttps); | ||||||
|  | require("http") | ||||||
|  | 	.createServer(acmeChallengeHandler) | ||||||
|  | 	.listen(80, function() { | ||||||
|  | 		console.log("Listening for ACME http-01 challenges on", this.address()); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // http2 via SPDY h2  //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | // spdy is a drop-in replacement for the https API
 | ||||||
|  | var spdyOptions = Object.assign({}, greenlock.tlsOptions); | ||||||
|  | spdyOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; | ||||||
|  | var myApp = require("./my-express-app.js"); | ||||||
|  | var server = require("spdy").createServer(spdyOptions, myApp); | ||||||
|  | server.on("error", function(err) { | ||||||
|  | 	console.error(err); | ||||||
|  | }); | ||||||
|  | server.on("listening", function() { | ||||||
|  | 	console.log("Listening for SPDY/http2/https requests on", this.address()); | ||||||
|  | }); | ||||||
|  | server.listen(443); | ||||||
| @ -1,3 +0,0 @@ | |||||||
| // SPDY is dead. It was replaced by HTTP2, which is a native node module
 |  | ||||||
| //
 |  | ||||||
| // Greenlock uses HTTP2 as the default https server in node v12+
 |  | ||||||
							
								
								
									
										134
									
								
								examples/vhost.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								examples/vhost.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,134 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | ///////////////////
 | ||||||
|  | // vhost example //
 | ||||||
|  | ///////////////////
 | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // virtual hosting example
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | // The prefix where sites go by name.
 | ||||||
|  | // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path
 | ||||||
|  | var srv = process.argv[3] || "/srv/www/"; | ||||||
|  | 
 | ||||||
|  | var path = require("path"); | ||||||
|  | var fs = require("fs").promises; | ||||||
|  | var finalhandler = require("finalhandler"); | ||||||
|  | var serveStatic = require("serve-static"); | ||||||
|  | 
 | ||||||
|  | //var glx = require('greenlock-express')
 | ||||||
|  | var glx = require("./").create({ | ||||||
|  | 	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
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 	configDir: process.argv[4] || "~/.config/acme/", // You MUST have access to write to directory where certs
 | ||||||
|  | 	// are saved. ex: /home/foouser/.config/acme
 | ||||||
|  | 
 | ||||||
|  | 	approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
 | ||||||
|  | 	// domain name here and reject invalid ones
 | ||||||
|  | 
 | ||||||
|  | 	app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill)
 | ||||||
|  | 
 | ||||||
|  | 	/* CHANGE TO A VALID EMAIL */ | ||||||
|  | 	email: process.argv[2] || "jon.doe@example.com", // Email for Let's Encrypt account and Greenlock Security
 | ||||||
|  | 	agreeTos: true // Accept Let's Encrypt ToS
 | ||||||
|  | 	//, communityMember: true                                   // Join Greenlock to get important updates, no spam
 | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server = glx.listen(80, 443); | ||||||
|  | server.on("listening", function() { | ||||||
|  | 	console.info(server.type + " listening on", server.address()); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function myApproveDomains(opts, certs, cb) { | ||||||
|  | 	console.log("sni:", opts.domain); | ||||||
|  | 	// In this example the filesystem is our "database".
 | ||||||
|  | 	// 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
 | ||||||
|  | 	return checkWwws(opts.domains[0]) | ||||||
|  | 		.then(function() { | ||||||
|  | 			//opts.email = email;
 | ||||||
|  | 			opts.agreeTos = true; | ||||||
|  | 			cb(null, { options: opts, certs: certs }); | ||||||
|  | 		}) | ||||||
|  | 		.catch(cb); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function checkWwws(_hostname) { | ||||||
|  | 	if (!_hostname) { | ||||||
|  | 		// SECURITY, don't allow access to the 'srv' root
 | ||||||
|  | 		// (greenlock-express uses middleware to check '..', etc)
 | ||||||
|  | 		return ""; | ||||||
|  | 	} | ||||||
|  | 	var hostname = _hostname; | ||||||
|  | 	var _hostdir = path.join(srv, hostname); | ||||||
|  | 	var hostdir = _hostdir; | ||||||
|  | 	// TODO could test for www/no-www both in directory
 | ||||||
|  | 	return fs | ||||||
|  | 		.readdir(hostdir) | ||||||
|  | 		.then(function() { | ||||||
|  | 			// TODO check for some sort of htaccess.json and use email in that
 | ||||||
|  | 			// NOTE: you can also change other options such as `challengeType` and `challenge`
 | ||||||
|  | 			// opts.challengeType = 'http-01';
 | ||||||
|  | 			// opts.challenge = require('le-challenge-fs').create({});
 | ||||||
|  | 			return hostname; | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			if ("www." === hostname.slice(0, 4)) { | ||||||
|  | 				// Assume we'll redirect to non-www if it's available.
 | ||||||
|  | 				hostname = hostname.slice(4); | ||||||
|  | 				hostdir = path.join(srv, hostname); | ||||||
|  | 				return fs.readdir(hostdir).then(function() { | ||||||
|  | 					// TODO list both domains?
 | ||||||
|  | 					return hostname; | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				// Or check and see if perhaps we should redirect non-www to www
 | ||||||
|  | 				hostname = "www." + hostname; | ||||||
|  | 				hostdir = path.join(srv, hostname); | ||||||
|  | 				return fs.readdir(hostdir).then(function() { | ||||||
|  | 					// TODO list both domains?
 | ||||||
|  | 					return hostname; | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			throw new Error("rejecting '" + _hostname + "' because '" + _hostdir + "' could not be read"); | ||||||
|  | 		}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function myVhostApp(req, res) { | ||||||
|  | 	// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
 | ||||||
|  | 	// (also: only domains approved above will get here)
 | ||||||
|  | 	console.log("vhost:", req.headers.host); | ||||||
|  | 	if (!req.headers.host) { | ||||||
|  | 		// SECURITY, don't allow access to the 'srv' root
 | ||||||
|  | 		// (greenlock-express uses middleware to check '..', etc)
 | ||||||
|  | 		return res.end(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// We could cache wether or not a host exists for some amount of time
 | ||||||
|  | 	var fin = finalhandler(req, res); | ||||||
|  | 	return checkWwws(req.headers.host) | ||||||
|  | 		.then(function(hostname) { | ||||||
|  | 			if (hostname !== req.headers.host) { | ||||||
|  | 				res.statusCode = 302; | ||||||
|  | 				res.setHeader("Location", "https://" + hostname); | ||||||
|  | 				// SECURITY this is safe only because greenlock disallows invalid hostnames
 | ||||||
|  | 				res.end("<!-- redirecting to https://" + hostname + "-->"); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			var serve = serveStatic(path.join(srv, hostname), { redirect: true }); | ||||||
|  | 			serve(req, res, fin); | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			fin(); | ||||||
|  | 		}); | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								examples/websockets.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								examples/websockets.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | ////////////////////////
 | ||||||
|  | // Greenlock Setup    //
 | ||||||
|  | ////////////////////////
 | ||||||
|  | 
 | ||||||
|  | //var Greenlock = require('greenlock-express');
 | ||||||
|  | var Greenlock = require("../"); | ||||||
|  | var greenlock = Greenlock.create({ | ||||||
|  | 	// Let's Encrypt v2 is ACME draft 11
 | ||||||
|  | 	// Note: If at first you don't succeed, stop and switch to staging
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 	server: "https://acme-v02.api.letsencrypt.org/directory", | ||||||
|  | 	version: "draft-11", | ||||||
|  | 	configDir: "~/.config/acme/", | ||||||
|  | 	app: require("./my-express-app.js"), | ||||||
|  | 
 | ||||||
|  | 	// You MUST change these to a valid email and domains
 | ||||||
|  | 	email: "john.doe@example.com", | ||||||
|  | 	approvedDomains: ["example.com", "www.example.com"], | ||||||
|  | 	agreeTos: true, | ||||||
|  | 
 | ||||||
|  | 	// Get notified of important updates and help me make greenlock better
 | ||||||
|  | 	communityMember: true, | ||||||
|  | 	telemetry: true | ||||||
|  | 	//, debug: true
 | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server = greenlock.listen(80, 443); | ||||||
|  | 
 | ||||||
|  | var WebSocket = require("ws"); | ||||||
|  | var ws = new WebSocket.Server({ server: server }); | ||||||
|  | ws.on("connection", function(ws, req) { | ||||||
|  | 	// inspect req.headers.authorization (or cookies) for session info
 | ||||||
|  | 	ws.send( | ||||||
|  | 		"[Secure Echo Server] Hello!\nAuth: '" + | ||||||
|  | 			(req.headers.authorization || "none") + | ||||||
|  | 			"'\n" + | ||||||
|  | 			"Cookie: '" + | ||||||
|  | 			(req.headers.cookie || "none") + | ||||||
|  | 			"'\n" | ||||||
|  | 	); | ||||||
|  | 	ws.on("message", function(data) { | ||||||
|  | 		ws.send(data); | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
| @ -1,42 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| function httpsWorker(glx) { |  | ||||||
| 	// we need the raw https server
 |  | ||||||
| 	var server = glx.httpsServer(); |  | ||||||
| 	var WebSocket = require("ws"); |  | ||||||
| 	var ws = new WebSocket.Server({ server: server }); |  | ||||||
| 	ws.on("connection", function(ws, req) { |  | ||||||
| 		// inspect req.headers.authorization (or cookies) for session info
 |  | ||||||
| 		ws.send( |  | ||||||
| 			"[Secure Echo Server] Hello!\nAuth: '" + |  | ||||||
| 				(req.headers.authorization || "none") + |  | ||||||
| 				"'\n" + |  | ||||||
| 				"Cookie: '" + |  | ||||||
| 				(req.headers.cookie || "none") + |  | ||||||
| 				"'\n" |  | ||||||
| 		); |  | ||||||
| 		ws.on("message", function(data) { |  | ||||||
| 			ws.send(data); |  | ||||||
| 		}); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	// servers a node app that proxies requests to a localhost
 |  | ||||||
| 	glx.serveApp(function(req, res) { |  | ||||||
| 		res.setHeader("Content-Type", "text/html; charset=utf-8"); |  | ||||||
| 		res.end("Hello, World!\n\n💚 🔒.js"); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| var pkg = require("../../package.json"); |  | ||||||
| //require("greenlock-express")
 |  | ||||||
| require("../../") |  | ||||||
| 	.init(function getConfig() { |  | ||||||
| 		// Greenlock Config
 |  | ||||||
| 
 |  | ||||||
| 		return { |  | ||||||
| 			package: { name: "websocket-example", version: pkg.version }, |  | ||||||
| 			maintainerEmail: "jon@example.com", |  | ||||||
| 			cluster: false |  | ||||||
| 		}; |  | ||||||
| 	}) |  | ||||||
| 	.serve(httpsWorker); |  | ||||||
							
								
								
									
										77
									
								
								examples/wildcard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								examples/wildcard.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | "use strict"; | ||||||
|  | /*global Promise*/ | ||||||
|  | 
 | ||||||
|  | ///////////////////////
 | ||||||
|  | // wildcard example //
 | ||||||
|  | //////////////////////
 | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // wildcard example
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | //var glx = require('greenlock-express')
 | ||||||
|  | var glx = require("../").create({ | ||||||
|  | 	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
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 	configDir: "~/acme/", // You MUST have access to write to directory where certs
 | ||||||
|  | 	// are saved. ex: /home/foouser/.config/acme
 | ||||||
|  | 
 | ||||||
|  | 	approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
 | ||||||
|  | 	// domain name here and reject invalid ones
 | ||||||
|  | 
 | ||||||
|  | 	app: require("./my-express-app.js"), // Any node-style http app (i.e. express, koa, hapi, rill)
 | ||||||
|  | 
 | ||||||
|  | 	/* CHANGE TO A VALID EMAIL */ | ||||||
|  | 	email: "jon.doe@example.com", // Email for Let's Encrypt account and Greenlock Security
 | ||||||
|  | 	agreeTos: true, // Accept Let's Encrypt ToS
 | ||||||
|  | 	communityMember: true, // Join Greenlock to (very rarely) get important updates
 | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | 	store: require("le-store-fs") | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | var server = glx.listen(80, 443); | ||||||
|  | server.on("listening", function() { | ||||||
|  | 	console.info(server.type + " listening on", server.address()); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function myApproveDomains(opts) { | ||||||
|  | 	console.log("sni:", opts.domain); | ||||||
|  | 
 | ||||||
|  | 	// must be 'example.com' or start with 'example.com'
 | ||||||
|  | 	if ( | ||||||
|  | 		"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)); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// the primary domain for the cert
 | ||||||
|  | 	opts.subject = "example.com"; | ||||||
|  | 	// the altnames (including the primary)
 | ||||||
|  | 	opts.domains = [opts.subject, "*.example.com"]; | ||||||
|  | 
 | ||||||
|  | 	if (!opts.challenges) { | ||||||
|  | 		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
 | ||||||
|  | 	// until it can positively confirm that the DNS changes have propagated.
 | ||||||
|  | 	// That could take several seconds to a few minutes.
 | ||||||
|  | 	opts.challenges["dns-01"] = require("le-challenge-dns").create({}); | ||||||
|  | 
 | ||||||
|  | 	// explicitly set account id and certificate.id
 | ||||||
|  | 	opts.account = { id: opts.email }; | ||||||
|  | 	opts.certificate = { id: opts.subject }; | ||||||
|  | 
 | ||||||
|  | 	return Promise.resolve(opts); | ||||||
|  | } | ||||||
| @ -1,44 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| require("./lib/compat"); |  | ||||||
| var cluster = require("cluster"); |  | ||||||
| 
 |  | ||||||
| // Greenlock Express
 |  | ||||||
| var GLE = module.exports; |  | ||||||
| 
 |  | ||||||
| // Node's cluster is awesome, because it encourages writing scalable services.
 |  | ||||||
| //
 |  | ||||||
| // The point of this provide an API that is consistent between single-process
 |  | ||||||
| // and multi-process services so that beginners can more easily take advantage
 |  | ||||||
| // of what cluster has to offer.
 |  | ||||||
| //
 |  | ||||||
| // This API provides just enough abstraction to make it easy, but leaves just
 |  | ||||||
| // enough hoopla so that there's not a large gap in understanding what happens
 |  | ||||||
| // under the hood. That's the hope, anyway.
 |  | ||||||
| 
 |  | ||||||
| GLE.init = function(fn) { |  | ||||||
| 	if (cluster.isWorker) { |  | ||||||
| 		// ignore the init function and launch the worker
 |  | ||||||
| 		return require("./worker.js").create(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var opts = fn(); |  | ||||||
| 	if (!opts || "object" !== typeof opts) { |  | ||||||
| 		throw new Error( |  | ||||||
| 			"the `Greenlock.init(fn)` function should return an object `{ maintainerEmail, packageAgent, notify }`" |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// just for ironic humor
 |  | ||||||
| 	["cloudnative", "cloudscale", "webscale", "distributed", "blockchain"].forEach(function(k) { |  | ||||||
| 		if (opts[k]) { |  | ||||||
| 			opts.cluster = true; |  | ||||||
| 		} |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	if (opts.cluster) { |  | ||||||
| 		return require("./master.js").create(opts); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return require("./single.js").create(opts); |  | ||||||
| }; |  | ||||||
							
								
								
									
										119
									
								
								greenlock.js
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								greenlock.js
									
									
									
									
									
								
							| @ -1,119 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| module.exports.create = function(opts) { |  | ||||||
| 	opts = parsePackage(opts); |  | ||||||
| 	opts.packageAgent = addGreenlockAgent(opts); |  | ||||||
| 
 |  | ||||||
| 	var Greenlock = require("@root/greenlock"); |  | ||||||
| 	var greenlock = Greenlock.create(opts); |  | ||||||
| 
 |  | ||||||
| 	// TODO move to greenlock proper
 |  | ||||||
| 	greenlock.getAcmeHttp01ChallengeResponse = function(opts) { |  | ||||||
| 		// TODO some sort of caching to prevent database hits?
 |  | ||||||
| 		return greenlock |  | ||||||
| 			._config({ servername: opts.servername }) |  | ||||||
| 			.then(function(site) { |  | ||||||
| 				if (!site) { |  | ||||||
| 					return null; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				// Hmm... this _should_ be impossible
 |  | ||||||
| 				if (!site.challenges || !site.challenges["http-01"]) { |  | ||||||
| 					return null; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				return Greenlock._loadChallenge(site.challenges, "http-01"); |  | ||||||
| 			}) |  | ||||||
| 			.then(function(plugin) { |  | ||||||
| 				return plugin |  | ||||||
| 					.get({ |  | ||||||
| 						challenge: { |  | ||||||
| 							type: opts.type, |  | ||||||
| 							//hostname: opts.servername,
 |  | ||||||
| 							altname: opts.servername, |  | ||||||
| 							identifier: { value: opts.servername }, |  | ||||||
| 							token: opts.token |  | ||||||
| 						} |  | ||||||
| 					}) |  | ||||||
| 					.then(function(result) { |  | ||||||
| 						var keyAuth; |  | ||||||
| 						if (result) { |  | ||||||
| 							// backwards compat that shouldn't be dropped
 |  | ||||||
| 							// because new v3 modules had to do this to be
 |  | ||||||
| 							// backwards compatible with Greenlock v2.7 at
 |  | ||||||
| 							// the time.
 |  | ||||||
| 							if (result.challenge) { |  | ||||||
| 								result = challenge; |  | ||||||
| 							} |  | ||||||
| 							keyAuth = result.keyAuthorization; |  | ||||||
| 						} |  | ||||||
| 						return { |  | ||||||
| 							keyAuthorization: keyAuth |  | ||||||
| 						}; |  | ||||||
| 					}); |  | ||||||
| 			}); |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	return greenlock; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function addGreenlockAgent(opts) { |  | ||||||
| 	// Add greenlock as part of Agent, unless this is greenlock
 |  | ||||||
| 	var packageAgent = opts.packageAgent || ""; |  | ||||||
| 	if (!/greenlock(-express|-pro)?/i.test(packageAgent)) { |  | ||||||
| 		var pkg = require("./package.json"); |  | ||||||
| 		packageAgent += " Greenlock_Express/" + pkg.version; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return packageAgent.trim(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // ex: "John Doe <john@example.com> (https://john.doe)"
 |  | ||||||
| // ex: "John Doe <john@example.com>"
 |  | ||||||
| // ex: "<john@example.com>"
 |  | ||||||
| // ex: "john@example.com"
 |  | ||||||
| var looseEmailRe = /(^|[\s<])([^'" <>:;`]+@[^'" <>:;`]+\.[^'" <>:;`]+)/; |  | ||||||
| function parsePackage(opts) { |  | ||||||
| 	// 'package' is sometimes a reserved word
 |  | ||||||
| 	var pkg = opts.package || opts.pkg; |  | ||||||
| 	if (!pkg) { |  | ||||||
| 		opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); |  | ||||||
| 		return opts; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (!opts.packageAgent) { |  | ||||||
| 		var err = "missing `package.THING`, which is used for the ACME client user agent string"; |  | ||||||
| 		if (!pkg.name) { |  | ||||||
| 			throw new Error(err.replace("THING", "name")); |  | ||||||
| 		} |  | ||||||
| 		if (!pkg.version) { |  | ||||||
| 			throw new Error(err.replace("THING", "version")); |  | ||||||
| 		} |  | ||||||
| 		opts.packageAgent = pkg.name + "/" + pkg.version; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if (!opts.maintainerEmail) { |  | ||||||
| 		try { |  | ||||||
| 			opts.maintainerEmail = pkg.author.email || pkg.author.match(looseEmailRe)[2]; |  | ||||||
| 		} catch (e) {} |  | ||||||
| 	} |  | ||||||
| 	if (!opts.maintainerEmail) { |  | ||||||
| 		throw new Error("missing or malformed `package.author`, which is used as the contact for support notices"); |  | ||||||
| 	} |  | ||||||
| 	opts.package = undefined; |  | ||||||
| 	opts.maintainerEmail = parseMaintainer(opts.maintainerEmail); |  | ||||||
| 
 |  | ||||||
| 	return opts; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function parseMaintainer(maintainerEmail) { |  | ||||||
| 	try { |  | ||||||
| 		maintainerEmail = maintainerEmail.match(looseEmailRe)[2]; |  | ||||||
| 	} catch (e) { |  | ||||||
| 		maintainerEmail = null; |  | ||||||
| 	} |  | ||||||
| 	if (!maintainerEmail) { |  | ||||||
| 		throw new Error("missing or malformed `maintainerEmail`, which is used as the contact for support notices"); |  | ||||||
| 	} |  | ||||||
| 	return maintainerEmail; |  | ||||||
| } |  | ||||||
| @ -1,106 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var HttpMiddleware = module.exports; |  | ||||||
| var servernameRe = /^[a-z0-9\.\-]+$/i; |  | ||||||
| var challengePrefix = "/.well-known/acme-challenge/"; |  | ||||||
| 
 |  | ||||||
| HttpMiddleware.create = function(gl, defaultApp) { |  | ||||||
| 	if (defaultApp && "function" !== typeof defaultApp) { |  | ||||||
| 		throw new Error("use greenlock.httpMiddleware() or greenlock.httpMiddleware(function (req, res) {})"); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return function(req, res, next) { |  | ||||||
| 		var hostname = HttpMiddleware.sanitizeHostname(req); |  | ||||||
| 
 |  | ||||||
| 		req.on("error", function(err) { |  | ||||||
| 			explainError(gl, err, "http_01_middleware_socket", hostname); |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		if (skipIfNeedBe(req, res, next, defaultApp, hostname)) { |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var token = req.url.slice(challengePrefix.length); |  | ||||||
| 
 |  | ||||||
| 		gl.getAcmeHttp01ChallengeResponse({ type: "http-01", servername: hostname, token: token }) |  | ||||||
| 			.catch(function(err) { |  | ||||||
| 				respondToError(gl, res, err, "http_01_middleware_challenge_response", hostname); |  | ||||||
| 				return { __done: true }; |  | ||||||
| 			}) |  | ||||||
| 			.then(function(result) { |  | ||||||
| 				if (result && result.__done) { |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 				return respondWithGrace(res, result, hostname, token); |  | ||||||
| 			}); |  | ||||||
| 	}; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function skipIfNeedBe(req, res, next, defaultApp, hostname) { |  | ||||||
| 	if (!hostname || 0 !== req.url.indexOf(challengePrefix)) { |  | ||||||
| 		if ("function" === typeof defaultApp) { |  | ||||||
| 			defaultApp(req, res, next); |  | ||||||
| 		} else if ("function" === typeof next) { |  | ||||||
| 			next(); |  | ||||||
| 		} else { |  | ||||||
| 			res.statusCode = 500; |  | ||||||
| 			res.end("[500] Developer Error: app.use('/', greenlock.httpMiddleware()) or greenlock.httpMiddleware(app)"); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function respondWithGrace(res, result, hostname, token) { |  | ||||||
| 	var keyAuth = result && result.keyAuthorization; |  | ||||||
| 	if (keyAuth && "string" === typeof keyAuth) { |  | ||||||
| 		res.setHeader("Content-Type", "text/plain; charset=utf-8"); |  | ||||||
| 		res.end(keyAuth); |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	res.statusCode = 404; |  | ||||||
| 	res.setHeader("Content-Type", "application/json; charset=utf-8"); |  | ||||||
| 	res.end(JSON.stringify({ error: { message: "domain '" + hostname + "' has no token '" + token + "'." } })); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function explainError(gl, err, ctx, hostname) { |  | ||||||
| 	if (!err.servername) { |  | ||||||
| 		err.servername = hostname; |  | ||||||
| 	} |  | ||||||
| 	if (!err.context) { |  | ||||||
| 		err.context = ctx; |  | ||||||
| 	} |  | ||||||
| 	(gl.notify || gl._notify)("error", err); |  | ||||||
| 	return err; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function respondToError(gl, res, err, ctx, hostname) { |  | ||||||
| 	err = explainError(gl, err, ctx, hostname); |  | ||||||
| 	res.statusCode = 500; |  | ||||||
| 	res.end("Internal Server Error: See logs for details."); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| HttpMiddleware.getHostname = function(req) { |  | ||||||
| 	return req.hostname || req.headers["x-forwarded-host"] || (req.headers.host || ""); |  | ||||||
| }; |  | ||||||
| HttpMiddleware.sanitizeHostname = function(req) { |  | ||||||
| 	// we can trust XFH because spoofing causes no ham in this limited use-case scenario
 |  | ||||||
| 	// (and only telebit would be legitimately setting XFH)
 |  | ||||||
| 	var servername = HttpMiddleware.getHostname(req) |  | ||||||
| 		.toLowerCase() |  | ||||||
| 		.replace(/:.*/, ""); |  | ||||||
| 	try { |  | ||||||
| 		req.hostname = servername; |  | ||||||
| 	} catch (e) { |  | ||||||
| 		// read-only express property
 |  | ||||||
| 	} |  | ||||||
| 	if (req.headers["x-forwarded-host"]) { |  | ||||||
| 		req.headers["x-forwarded-host"] = servername; |  | ||||||
| 	} |  | ||||||
| 	try { |  | ||||||
| 		req.headers.host = servername; |  | ||||||
| 	} catch (e) { |  | ||||||
| 		// TODO is this a possible error?
 |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return (servernameRe.test(servername) && -1 === servername.indexOf("..") && servername) || ""; |  | ||||||
| }; |  | ||||||
| @ -1,139 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var SanitizeHost = module.exports; |  | ||||||
| var HttpMiddleware = require("./http-middleware.js"); |  | ||||||
| 
 |  | ||||||
| SanitizeHost.create = function(gl, app) { |  | ||||||
| 	return function(req, res, next) { |  | ||||||
| 		function realNext() { |  | ||||||
| 			if ("function" === typeof app) { |  | ||||||
| 				app(req, res); |  | ||||||
| 			} else if ("function" === typeof next) { |  | ||||||
| 				next(); |  | ||||||
| 			} else { |  | ||||||
| 				res.statusCode = 500; |  | ||||||
| 				res.end("Error: no middleware assigned"); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var hostname = HttpMiddleware.getHostname(req); |  | ||||||
| 		// Replace the hostname, and get the safe version
 |  | ||||||
| 		var safehost = HttpMiddleware.sanitizeHostname(req); |  | ||||||
| 
 |  | ||||||
| 		// if no hostname, move along
 |  | ||||||
| 		if (!hostname) { |  | ||||||
| 			realNext(); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// if there were unallowed characters, complain
 |  | ||||||
| 		if (safehost.length !== hostname.length) { |  | ||||||
| 			res.statusCode = 400; |  | ||||||
| 			res.end("Malformed HTTP Header: 'Host: " + hostname + "'"); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Note: This sanitize function is also called on plain sockets, which don't need Domain Fronting checks
 |  | ||||||
| 		if (req.socket.encrypted) { |  | ||||||
| 			if (req.socket && "string" === typeof req.socket.servername) { |  | ||||||
| 				// Workaround for https://github.com/nodejs/node/issues/22389
 |  | ||||||
| 				if (!SanitizeHost._checkServername(safehost, req.socket)) { |  | ||||||
| 					res.statusCode = 400; |  | ||||||
| 					res.setHeader("Content-Type", "text/html; charset=utf-8"); |  | ||||||
| 					res.end( |  | ||||||
| 						"<h1>Domain Fronting Error</h1>" + |  | ||||||
| 							"<p>This connection was secured using TLS/SSL for '" + |  | ||||||
| 							(req.socket.servername || "").toLowerCase() + |  | ||||||
| 							"'</p>" + |  | ||||||
| 							"<p>The HTTP request specified 'Host: " + |  | ||||||
| 							safehost + |  | ||||||
| 							"', which is (obviously) different.</p>" + |  | ||||||
| 							"<p>Because this looks like a domain fronting attack, the connection has been terminated.</p>" |  | ||||||
| 					); |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			/* |  | ||||||
|       else if (safehost && !gl._skip_fronting_check) { |  | ||||||
| 
 |  | ||||||
| 				// We used to print a log message here, but it turns out that it's
 |  | ||||||
| 				// really common for IoT devices to not use SNI (as well as many bots
 |  | ||||||
| 				// and such).
 |  | ||||||
| 				// It was common for the log message to pop up as the first request
 |  | ||||||
| 				// to the server, and that was confusing. So instead now we do nothing.
 |  | ||||||
| 
 |  | ||||||
| 				//console.warn("no string for req.socket.servername," + " skipping fronting check for '" + safehost + "'");
 |  | ||||||
| 				//gl._skip_fronting_check = true;
 |  | ||||||
| 			} |  | ||||||
|       */ |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// carry on
 |  | ||||||
| 		realNext(); |  | ||||||
| 	}; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| var warnDomainFronting = true; |  | ||||||
| var warnUnexpectedError = true; |  | ||||||
| SanitizeHost._checkServername = function(safeHost, tlsSocket) { |  | ||||||
| 	var servername = (tlsSocket.servername || "").toLowerCase(); |  | ||||||
| 
 |  | ||||||
| 	// acceptable: older IoT devices may lack SNI support
 |  | ||||||
| 	if (!servername) { |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 	// acceptable: odd... but acceptable
 |  | ||||||
| 	if (!safeHost) { |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 	if (safeHost === servername) { |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if ("function" !== typeof tlsSocket.getCertificate) { |  | ||||||
| 		// domain fronting attacks allowed
 |  | ||||||
| 		if (warnDomainFronting) { |  | ||||||
| 			// https://github.com/nodejs/node/issues/24095
 |  | ||||||
| 			console.warn( |  | ||||||
| 				"Warning: node " + |  | ||||||
| 					process.version + |  | ||||||
| 					" is vulnerable to domain fronting attacks. Please use node v11.2.0 or greater." |  | ||||||
| 			); |  | ||||||
| 			warnDomainFronting = false; |  | ||||||
| 		} |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// connection established with servername and session is re-used for allowed name
 |  | ||||||
| 	// See https://github.com/nodejs/node/issues/24095
 |  | ||||||
| 	var cert = tlsSocket.getCertificate(); |  | ||||||
| 	try { |  | ||||||
| 		// TODO optimize / cache?
 |  | ||||||
| 		// *should* always have a string, right?
 |  | ||||||
| 		// *should* always be lowercase already, right?
 |  | ||||||
| 		//console.log(safeHost, cert.subject.CN, cert.subjectaltname);
 |  | ||||||
| 		var isSubject = (cert.subject.CN || "").toLowerCase() === safeHost; |  | ||||||
| 		if (isSubject) { |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var dnsnames = (cert.subjectaltname || "").split(/,\s+/); |  | ||||||
| 		var inSanList = dnsnames.some(function(name) { |  | ||||||
| 			// always prefixed with "DNS:"
 |  | ||||||
| 			return safeHost === name.slice(4).toLowerCase(); |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		if (inSanList) { |  | ||||||
| 			return true; |  | ||||||
| 		} |  | ||||||
| 	} catch (e) { |  | ||||||
| 		// not sure what else to do in this situation...
 |  | ||||||
| 		if (warnUnexpectedError) { |  | ||||||
| 			console.warn("Warning: encoutered error while performing domain fronting check: " + e.message); |  | ||||||
| 			warnUnexpectedError = false; |  | ||||||
| 		} |  | ||||||
| 		return true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return false; |  | ||||||
| }; |  | ||||||
							
								
								
									
										334
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										334
									
								
								index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,334 @@ | |||||||
|  | "use strict"; | ||||||
|  | 
 | ||||||
|  | var PromiseA; | ||||||
|  | try { | ||||||
|  | 	PromiseA = require("bluebird"); | ||||||
|  | } catch (e) { | ||||||
|  | 	PromiseA = global.Promise; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // opts.approveDomains(options, certs, cb)
 | ||||||
|  | module.exports.create = function(opts) { | ||||||
|  | 	// accept all defaults for greenlock.challenges, greenlock.store, greenlock.middleware
 | ||||||
|  | 	if (!opts._communityPackage) { | ||||||
|  | 		opts._communityPackage = "greenlock-express.js"; | ||||||
|  | 		opts._communityPackageVersion = require("./package.json").version; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function explainError(e) { | ||||||
|  | 		console.error("Error:" + e.message); | ||||||
|  | 		if ("EACCES" === e.errno) { | ||||||
|  | 			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)"'); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		if ("EADDRINUSE" === e.errno) { | ||||||
|  | 			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."); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		console.error(e.code + ": '" + e.address + ":" + e.port + "'"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function _createPlain(plainPort) { | ||||||
|  | 		if (!plainPort) { | ||||||
|  | 			plainPort = 80; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var parts = String(plainPort).split(":"); | ||||||
|  | 		var p = parts.pop(); | ||||||
|  | 		var addr = parts | ||||||
|  | 			.join(":") | ||||||
|  | 			.replace(/^\[/, "") | ||||||
|  | 			.replace(/\]$/, ""); | ||||||
|  | 		var args = []; | ||||||
|  | 		var httpType; | ||||||
|  | 		var server; | ||||||
|  | 		var validHttpPort = parseInt(p, 10) >= 0; | ||||||
|  | 
 | ||||||
|  | 		if (addr) { | ||||||
|  | 			args[1] = addr; | ||||||
|  | 		} | ||||||
|  | 		if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { | ||||||
|  | 			console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var mw = greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")())); | ||||||
|  | 		server = require("http").createServer(function(req, res) { | ||||||
|  | 			req.on("error", function(err) { | ||||||
|  | 				console.error("Insecure Request Network Connection Error:"); | ||||||
|  | 				console.error(err); | ||||||
|  | 			}); | ||||||
|  | 			mw(req, res); | ||||||
|  | 		}); | ||||||
|  | 		httpType = "http"; | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			server: server, | ||||||
|  | 			listen: function() { | ||||||
|  | 				return new PromiseA(function(resolve, reject) { | ||||||
|  | 					args[0] = p; | ||||||
|  | 					args.push(function() { | ||||||
|  | 						if (!greenlock.servername) { | ||||||
|  | 							if (Array.isArray(greenlock.approvedDomains) && greenlock.approvedDomains.length) { | ||||||
|  | 								greenlock.servername = greenlock.approvedDomains[0]; | ||||||
|  | 							} | ||||||
|  | 							if (Array.isArray(greenlock.approveDomains) && greenlock.approvedDomains.length) { | ||||||
|  | 								greenlock.servername = greenlock.approvedDomains[0]; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						if (!greenlock.servername) { | ||||||
|  | 							resolve(null); | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						return greenlock | ||||||
|  | 							.check({ domains: [greenlock.servername] }) | ||||||
|  | 							.then(function(certs) { | ||||||
|  | 								if (certs) { | ||||||
|  | 									return { | ||||||
|  | 										key: Buffer.from(certs.privkey, "ascii"), | ||||||
|  | 										cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") | ||||||
|  | 									}; | ||||||
|  | 								} | ||||||
|  | 								console.info( | ||||||
|  | 									"Fetching certificate for '%s' to use as default for HTTPS server...", | ||||||
|  | 									greenlock.servername | ||||||
|  | 								); | ||||||
|  | 								return new PromiseA(function(resolve, reject) { | ||||||
|  | 									// using SNICallback because all options will be set
 | ||||||
|  | 									greenlock.tlsOptions.SNICallback(greenlock.servername, function(err /*, secureContext*/) { | ||||||
|  | 										if (err) { | ||||||
|  | 											reject(err); | ||||||
|  | 											return; | ||||||
|  | 										} | ||||||
|  | 										return greenlock | ||||||
|  | 											.check({ domains: [greenlock.servername] }) | ||||||
|  | 											.then(function(certs) { | ||||||
|  | 												resolve({ | ||||||
|  | 													key: Buffer.from(certs.privkey, "ascii"), | ||||||
|  | 													cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") | ||||||
|  | 												}); | ||||||
|  | 											}) | ||||||
|  | 											.catch(reject); | ||||||
|  | 									}); | ||||||
|  | 								}); | ||||||
|  | 							}) | ||||||
|  | 							.then(resolve) | ||||||
|  | 							.catch(reject); | ||||||
|  | 					}); | ||||||
|  | 					server.listen.apply(server, args).on("error", function(e) { | ||||||
|  | 						if (server.listenerCount("error") < 2) { | ||||||
|  | 							console.warn("Did not successfully create http server and bind to port '" + p + "':"); | ||||||
|  | 							explainError(e); | ||||||
|  | 							process.exit(41); | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function _create(port) { | ||||||
|  | 		if (!port) { | ||||||
|  | 			port = 443; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var parts = String(port).split(":"); | ||||||
|  | 		var p = parts.pop(); | ||||||
|  | 		var addr = parts | ||||||
|  | 			.join(":") | ||||||
|  | 			.replace(/^\[/, "") | ||||||
|  | 			.replace(/\]$/, ""); | ||||||
|  | 		var args = []; | ||||||
|  | 		var httpType; | ||||||
|  | 		var server; | ||||||
|  | 		var validHttpPort = parseInt(p, 10) >= 0; | ||||||
|  | 
 | ||||||
|  | 		if (addr) { | ||||||
|  | 			args[1] = addr; | ||||||
|  | 		} | ||||||
|  | 		if (!validHttpPort && !/(\/)|(\\\\)/.test(p)) { | ||||||
|  | 			console.warn("'" + p + "' doesn't seem to be a valid port number, socket path, or pipe"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var https; | ||||||
|  | 		try { | ||||||
|  | 			https = require("spdy"); | ||||||
|  | 			greenlock.tlsOptions.spdy = { protocols: ["h2", "http/1.1"], plain: false }; | ||||||
|  | 			httpType = "http2 (spdy/h2)"; | ||||||
|  | 		} catch (e) { | ||||||
|  | 			https = require("https"); | ||||||
|  | 			httpType = "https"; | ||||||
|  | 		} | ||||||
|  | 		var sniCallback = greenlock.tlsOptions.SNICallback; | ||||||
|  | 		greenlock.tlsOptions.SNICallback = function(domain, cb) { | ||||||
|  | 			sniCallback(domain, function(err, context) { | ||||||
|  | 				cb(err, context); | ||||||
|  | 
 | ||||||
|  | 				if (!context || server._hasDefaultSecureContext) { | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 				if (!domain) { | ||||||
|  | 					domain = greenlock.servername; | ||||||
|  | 				} | ||||||
|  | 				if (!domain) { | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				return greenlock | ||||||
|  | 					.check({ domains: [domain] }) | ||||||
|  | 					.then(function(certs) { | ||||||
|  | 						// ignore the case that check doesn't have all the right args here
 | ||||||
|  | 						// to get the same certs that it just got (eventually the right ones will come in)
 | ||||||
|  | 						if (!certs) { | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
|  | 						if (server.setSecureContext) { | ||||||
|  | 							// only available in node v11.0+
 | ||||||
|  | 							server.setSecureContext({ | ||||||
|  | 								key: Buffer.from(certs.privkey, "ascii"), | ||||||
|  | 								cert: Buffer.from(certs.cert + "\r\n" + certs.chain, "ascii") | ||||||
|  | 							}); | ||||||
|  | 							console.info("Using '%s' as default certificate", domain); | ||||||
|  | 						} else { | ||||||
|  | 							console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); | ||||||
|  | 						} | ||||||
|  | 						server._hasDefaultSecureContext = true; | ||||||
|  | 					}) | ||||||
|  | 					.catch(function(/*e*/) { | ||||||
|  | 						// this may be that the test.example.com was requested, but it's listed
 | ||||||
|  | 						// on the cert for demo.example.com which is in its own directory, not the other
 | ||||||
|  | 						//console.warn("Unusual error: couldn't get newly authorized certificate:");
 | ||||||
|  | 						//console.warn(e.message);
 | ||||||
|  | 					}); | ||||||
|  | 			}); | ||||||
|  | 		}; | ||||||
|  | 		if (greenlock.tlsOptions.cert) { | ||||||
|  | 			server._hasDefaultSecureContext = true; | ||||||
|  | 			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)" | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var mw = greenlock.middleware.sanitizeHost(function(req, res) { | ||||||
|  | 			try { | ||||||
|  | 				greenlock.app(req, res); | ||||||
|  | 			} catch (e) { | ||||||
|  | 				console.error("[error] [greenlock.app] Your HTTP handler had an uncaught error:"); | ||||||
|  | 				console.error(e); | ||||||
|  | 				try { | ||||||
|  | 					res.statusCode = 500; | ||||||
|  | 					res.end("Internal Server Error: [Greenlock] HTTP exception logged for user-provided handler."); | ||||||
|  | 				} catch (e) { | ||||||
|  | 					// ignore
 | ||||||
|  | 					// (headers may have already been sent, etc)
 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 		server = https.createServer(greenlock.tlsOptions, function(req, res) { | ||||||
|  | 			/* | ||||||
|  | 			// Don't do this yet
 | ||||||
|  | 			req.on("error", function(err) { | ||||||
|  | 				console.error("HTTPS Request Network Connection Error:"); | ||||||
|  | 				console.error(err); | ||||||
|  | 			}); | ||||||
|  | 			*/ | ||||||
|  | 			mw(req, res); | ||||||
|  | 		}); | ||||||
|  | 		server.type = httpType; | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			server: server, | ||||||
|  | 			listen: function() { | ||||||
|  | 				return new PromiseA(function(resolve) { | ||||||
|  | 					args[0] = p; | ||||||
|  | 					args.push(function() { | ||||||
|  | 						resolve(/*server*/); | ||||||
|  | 					}); | ||||||
|  | 					server.listen.apply(server, args).on("error", function(e) { | ||||||
|  | 						if (server.listenerCount("error") < 2) { | ||||||
|  | 							console.warn("Did not successfully create http server and bind to port '" + p + "':"); | ||||||
|  | 							explainError(e); | ||||||
|  | 							process.exit(41); | ||||||
|  | 						} | ||||||
|  | 					}); | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// NOTE: 'greenlock' is just 'opts' renamed
 | ||||||
|  | 	var greenlock = require("greenlock").create(opts); | ||||||
|  | 
 | ||||||
|  | 	if (!opts.app) { | ||||||
|  | 		opts.app = function(req, res) { | ||||||
|  | 			res.end("Hello, World!\nWith Love,\nGreenlock for Express.js"); | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	opts.listen = function(plainPort, port, fnPlain, fn) { | ||||||
|  | 		var server; | ||||||
|  | 		var plainServer; | ||||||
|  | 
 | ||||||
|  | 		// If there is only one handler for the `listening` (i.e. TCP bound) event
 | ||||||
|  | 		// then we want to use it as HTTPS (backwards compat)
 | ||||||
|  | 		if (!fn) { | ||||||
|  | 			fn = fnPlain; | ||||||
|  | 			fnPlain = null; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var obj1 = _createPlain(plainPort, true); | ||||||
|  | 		var obj2 = _create(port, false); | ||||||
|  | 
 | ||||||
|  | 		plainServer = obj1.server; | ||||||
|  | 		server = obj2.server; | ||||||
|  | 
 | ||||||
|  | 		server.then = obj1.listen().then(function(tlsOptions) { | ||||||
|  | 			if (tlsOptions) { | ||||||
|  | 				if (server.setSecureContext) { | ||||||
|  | 					// only available in node v11.0+
 | ||||||
|  | 					server.setSecureContext(tlsOptions); | ||||||
|  | 					console.info("Using '%s' as default certificate", greenlock.servername); | ||||||
|  | 				} else { | ||||||
|  | 					console.info("Setting default certificates dynamically requires node v11.0+. Skipping."); | ||||||
|  | 				} | ||||||
|  | 				server._hasDefaultSecureContext = true; | ||||||
|  | 			} | ||||||
|  | 			return obj2.listen().then(function() { | ||||||
|  | 				// Report plain http status
 | ||||||
|  | 				if ("function" === typeof fnPlain) { | ||||||
|  | 					fnPlain.apply(plainServer); | ||||||
|  | 				} else if (!fn && !plainServer.listenerCount("listening") && !server.listenerCount("listening")) { | ||||||
|  | 					console.info( | ||||||
|  | 						"[:" + | ||||||
|  | 							(plainServer.address().port || plainServer.address()) + | ||||||
|  | 							"] Handling ACME challenges and redirecting to " + | ||||||
|  | 							server.type | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				// Report h2/https status
 | ||||||
|  | 				if ("function" === typeof fn) { | ||||||
|  | 					fn.apply(server); | ||||||
|  | 				} else if (!server.listenerCount("listening")) { | ||||||
|  | 					console.info("[:" + (server.address().port || server.address()) + "] Serving " + server.type); | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}).then; | ||||||
|  | 
 | ||||||
|  | 		server.unencrypted = plainServer; | ||||||
|  | 		return server; | ||||||
|  | 	}; | ||||||
|  | 	opts.middleware.acme = function(opts) { | ||||||
|  | 		return greenlock.middleware.sanitizeHost(greenlock.middleware(require("redirect-https")(opts))); | ||||||
|  | 	}; | ||||||
|  | 	opts.middleware.secure = function(app) { | ||||||
|  | 		return greenlock.middleware.sanitizeHost(app); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	return greenlock; | ||||||
|  | }; | ||||||
							
								
								
									
										36
									
								
								main.js
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								main.js
									
									
									
									
									
								
							| @ -1,36 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| // this is the stuff that should run in the main foreground process,
 |  | ||||||
| // whether it's single or master
 |  | ||||||
| 
 |  | ||||||
| var major = process.versions.node.split(".")[0]; |  | ||||||
| var minor = process.versions.node.split(".")[1]; |  | ||||||
| var _hasSetSecureContext = false; |  | ||||||
| var shouldUpgrade = false; |  | ||||||
| 
 |  | ||||||
| // TODO can we trust earlier versions as well?
 |  | ||||||
| if (major >= 12) { |  | ||||||
| 	_hasSetSecureContext = !!require("http2").createSecureServer({}, function() {}).setSecureContext; |  | ||||||
| } else { |  | ||||||
| 	_hasSetSecureContext = !!require("https").createServer({}, function() {}).setSecureContext; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TODO document in issues
 |  | ||||||
| if (!_hasSetSecureContext) { |  | ||||||
| 	// TODO this isn't necessary if greenlock options are set with options.cert
 |  | ||||||
| 	console.warn("Warning: node " + process.version + " is missing tlsSocket.setSecureContext()."); |  | ||||||
| 	console.warn("         The default certificate may not be set."); |  | ||||||
| 	shouldUpgrade = true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| if (major < 11 || (11 === major && minor < 2)) { |  | ||||||
| 	// https://github.com/nodejs/node/issues/24095
 |  | ||||||
| 	console.warn("Warning: node " + process.version + " is missing tlsSocket.getCertificate()."); |  | ||||||
| 	console.warn("         This is necessary to guard against domain fronting attacks."); |  | ||||||
| 	shouldUpgrade = true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| if (shouldUpgrade) { |  | ||||||
| 	console.warn("Warning: Please upgrade to node v11.2.0 or greater."); |  | ||||||
| 	console.warn(); |  | ||||||
| } |  | ||||||
							
								
								
									
										160
									
								
								master.js
									
									
									
									
									
								
							
							
						
						
									
										160
									
								
								master.js
									
									
									
									
									
								
							| @ -1,160 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| require("./main.js"); |  | ||||||
| 
 |  | ||||||
| var Master = module.exports; |  | ||||||
| 
 |  | ||||||
| var cluster = require("cluster"); |  | ||||||
| var os = require("os"); |  | ||||||
| var msgPrefix = "greenlock:"; |  | ||||||
| 
 |  | ||||||
| Master.create = function(opts) { |  | ||||||
| 	var resolveCb; |  | ||||||
| 	var _readyCb; |  | ||||||
| 	var _kicked = false; |  | ||||||
| 
 |  | ||||||
| 	var greenlock = require("./greenlock.js").create(opts); |  | ||||||
| 
 |  | ||||||
| 	var ready = new Promise(function(resolve) { |  | ||||||
| 		resolveCb = resolve; |  | ||||||
| 	}).then(function(fn) { |  | ||||||
| 		_readyCb = fn; |  | ||||||
| 		return fn; |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	function kickoff() { |  | ||||||
| 		if (_kicked) { |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 		_kicked = true; |  | ||||||
| 
 |  | ||||||
| 		Master._spawnWorkers(opts, greenlock); |  | ||||||
| 
 |  | ||||||
| 		ready.then(function(fn) { |  | ||||||
| 			// not sure what this API should be yet
 |  | ||||||
| 			fn(); |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	var master = { |  | ||||||
| 		serve: function() { |  | ||||||
| 			kickoff(); |  | ||||||
| 			return master; |  | ||||||
| 		}, |  | ||||||
| 		master: function(fn) { |  | ||||||
| 			if (_readyCb) { |  | ||||||
| 				throw new Error("can't call master twice"); |  | ||||||
| 			} |  | ||||||
| 			kickoff(); |  | ||||||
| 			resolveCb(fn); |  | ||||||
| 			return master; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 	return master; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function range(n) { |  | ||||||
| 	n = parseInt(n, 10); |  | ||||||
| 	if (!n) { |  | ||||||
| 		return []; |  | ||||||
| 	} |  | ||||||
| 	return new Array(n).join(",").split(","); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| Master._spawnWorkers = function(opts, greenlock) { |  | ||||||
| 	var numCpus = parseInt(process.env.NUMBER_OF_PROCESSORS, 10) || os.cpus().length; |  | ||||||
| 
 |  | ||||||
| 	// process rpc messages
 |  | ||||||
| 	// start when dead
 |  | ||||||
| 	var numWorkers = parseInt(opts.workers || opts.numWorkers, 10); |  | ||||||
| 	if (!numWorkers) { |  | ||||||
| 		if (numCpus <= 2) { |  | ||||||
| 			numWorkers = 2; |  | ||||||
| 		} else { |  | ||||||
| 			numWorkers = numCpus - 1; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	cluster.once("exit", function() { |  | ||||||
| 		setTimeout(function() { |  | ||||||
| 			process.exit(3); |  | ||||||
| 		}, 100); |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	var workers = range(numWorkers); |  | ||||||
| 	function next() { |  | ||||||
| 		if (!workers.length) { |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 		workers.pop(); |  | ||||||
| 
 |  | ||||||
| 		// for a nice aesthetic
 |  | ||||||
| 		setTimeout(function() { |  | ||||||
| 			Master._spawnWorker(opts, greenlock); |  | ||||||
| 			next(); |  | ||||||
| 		}, 250); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	next(); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| Master._spawnWorker = function(opts, greenlock) { |  | ||||||
| 	var w = cluster.fork(); |  | ||||||
| 	// automatically added to master's `cluster.workers`
 |  | ||||||
| 	w.once("exit", function(code, signal) { |  | ||||||
| 		// TODO handle failures
 |  | ||||||
| 		// Should test if the first starts successfully
 |  | ||||||
| 		// Should exit if failures happen too quickly
 |  | ||||||
| 
 |  | ||||||
| 		// For now just kill all when any die
 |  | ||||||
| 		if (signal) { |  | ||||||
| 			console.error("worker was killed by signal:", signal); |  | ||||||
| 		} else if (code !== 0) { |  | ||||||
| 			console.error("worker exited with error code:", code); |  | ||||||
| 		} else { |  | ||||||
| 			console.error("worker unexpectedly quit without exit code or signal"); |  | ||||||
| 		} |  | ||||||
| 		process.exit(2); |  | ||||||
| 
 |  | ||||||
| 		//addWorker();
 |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	function handleMessage(msg) { |  | ||||||
| 		if (0 !== (msg._id || "").indexOf(msgPrefix)) { |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 		if ("string" !== typeof msg._funcname) { |  | ||||||
| 			// TODO developer error
 |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		function rpc() { |  | ||||||
| 			return greenlock[msg._funcname](msg._input) |  | ||||||
| 				.then(function(result) { |  | ||||||
| 					w.send({ |  | ||||||
| 						_id: msg._id, |  | ||||||
| 						_result: result |  | ||||||
| 					}); |  | ||||||
| 				}) |  | ||||||
| 				.catch(function(e) { |  | ||||||
| 					var error = new Error(e.message); |  | ||||||
| 					Object.getOwnPropertyNames(e).forEach(function(k) { |  | ||||||
| 						error[k] = e[k]; |  | ||||||
| 					}); |  | ||||||
| 					w.send({ |  | ||||||
| 						_id: msg._id, |  | ||||||
| 						_error: error |  | ||||||
| 					}); |  | ||||||
| 				}); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		try { |  | ||||||
| 			rpc(); |  | ||||||
| 		} catch (e) { |  | ||||||
| 			console.error("Unexpected and uncaught greenlock." + msg._funcname + " error:"); |  | ||||||
| 			console.error(e); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	w.on("message", handleMessage); |  | ||||||
| }; |  | ||||||
							
								
								
									
										682
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										682
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,128 +1,507 @@ | |||||||
| { | { | ||||||
| 	"name": "@root/greenlock-express", | 	"name": "greenlock-express", | ||||||
| 	"version": "3.0.7", | 	"version": "2.7.18", | ||||||
| 	"lockfileVersion": 1, | 	"lockfileVersion": 1, | ||||||
| 	"requires": true, | 	"requires": true, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@root/acme": { |  | ||||||
| 			"version": "3.0.8", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/acme/-/acme-3.0.8.tgz", |  | ||||||
| 			"integrity": "sha512-VmBvLvWdCDkolkanI9Dzm1ouSWPaAa2eCCwcDZcVQbWoNiUIOqbbd57fcMA/gZxLyuJPStD2WXFuEuSMPDxcww==", |  | ||||||
| 			"requires": { |  | ||||||
| 				"@root/encoding": "^1.0.1", |  | ||||||
| 				"@root/keypairs": "^0.9.0", |  | ||||||
| 				"@root/pem": "^1.0.4", |  | ||||||
| 				"@root/request": "^1.3.11", |  | ||||||
| 				"@root/x509": "^0.7.2" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"@root/asn1": { |  | ||||||
| 			"version": "1.0.0", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/asn1/-/asn1-1.0.0.tgz", |  | ||||||
| 			"integrity": "sha512-0lfZNuOULKJDJmdIkP8V9RnbV3XaK6PAHD3swnFy4tZwtlMDzLKoM/dfNad7ut8Hu3r91wy9uK0WA/9zym5mig==", |  | ||||||
| 			"requires": { |  | ||||||
| 				"@root/encoding": "^1.0.1" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"@root/csr": { |  | ||||||
| 			"version": "0.8.1", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/csr/-/csr-0.8.1.tgz", |  | ||||||
| 			"integrity": "sha512-hKl0VuE549TK6SnS2Yn9nRvKbFZXn/oAg+dZJU/tlKl/f/0yRXeuUzf8akg3JjtJq+9E592zDqeXZ7yyrg8fSQ==", |  | ||||||
| 			"requires": { |  | ||||||
| 				"@root/asn1": "^1.0.0", |  | ||||||
| 				"@root/pem": "^1.0.4", |  | ||||||
| 				"@root/x509": "^0.7.2" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"@root/encoding": { |  | ||||||
| 			"version": "1.0.1", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/encoding/-/encoding-1.0.1.tgz", |  | ||||||
| 			"integrity": "sha512-OaEub02ufoU038gy6bsNHQOjIn8nUjGiLcaRmJ40IUykneJkIW5fxDqKxQx48cszuNflYldsJLPPXCrGfHs8yQ==" |  | ||||||
| 		}, |  | ||||||
| 		"@root/greenlock": { |  | ||||||
| 			"version": "3.0.17", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/greenlock/-/greenlock-3.0.17.tgz", |  | ||||||
| 			"integrity": "sha512-1XKhcLFEx1WFdn1Bc2rkAE/SL1ZUJYYMZdbnehTrfhCr5Y+9U1gdkNZnR/jInhoUvcicF/PXuZkGVucU50RNUg==", |  | ||||||
| 			"requires": { |  | ||||||
| 				"@root/acme": "^3.0.8", |  | ||||||
| 				"@root/csr": "^0.8.1", |  | ||||||
| 				"@root/keypairs": "^0.9.0", |  | ||||||
| 				"@root/mkdirp": "^1.0.0", |  | ||||||
| 				"@root/request": "^1.3.10", |  | ||||||
| 				"acme-http-01-standalone": "^3.0.5", |  | ||||||
| 				"cert-info": "^1.5.1", |  | ||||||
| 				"greenlock-manager-fs": "^3.0.1", |  | ||||||
| 				"greenlock-store-fs": "^3.2.0", |  | ||||||
| 				"safe-replace": "^1.1.0" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"@root/keypairs": { |  | ||||||
| 			"version": "0.9.0", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/keypairs/-/keypairs-0.9.0.tgz", |  | ||||||
| 			"integrity": "sha512-NXE2L9Gv7r3iC4kB/gTPZE1vO9Ox/p14zDzAJ5cGpTpytbWOlWF7QoHSJbtVX4H7mRG/Hp7HR3jWdWdb2xaaXg==", |  | ||||||
| 			"requires": { |  | ||||||
| 				"@root/encoding": "^1.0.1", |  | ||||||
| 				"@root/pem": "^1.0.4", |  | ||||||
| 				"@root/x509": "^0.7.2" |  | ||||||
| 			} |  | ||||||
| 		}, |  | ||||||
| 		"@root/mkdirp": { | 		"@root/mkdirp": { | ||||||
| 			"version": "1.0.0", | 			"version": "1.0.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | 			"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz", | ||||||
| 			"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | 			"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA==" | ||||||
| 		}, | 		}, | ||||||
| 		"@root/pem": { |  | ||||||
| 			"version": "1.0.4", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/pem/-/pem-1.0.4.tgz", |  | ||||||
| 			"integrity": "sha512-rEUDiUsHtild8GfIjFE9wXtcVxeS+ehCJQBwbQQ3IVfORKHK93CFnRtkr69R75lZFjcmKYVc+AXDB+AeRFOULA==" |  | ||||||
| 		}, |  | ||||||
| 		"@root/request": { | 		"@root/request": { | ||||||
| 			"version": "1.4.1", | 			"version": "1.3.11", | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz", | 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz", | ||||||
| 			"integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg==" | 			"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw==" | ||||||
| 		}, | 		}, | ||||||
| 		"@root/x509": { | 		"accepts": { | ||||||
| 			"version": "0.7.2", | 			"version": "1.3.5", | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz", | 			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", | ||||||
| 			"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==", | 			"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", | ||||||
|  | 			"dev": true, | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"@root/asn1": "^1.0.0", | 				"mime-types": "~2.1.18", | ||||||
| 				"@root/encoding": "^1.0.1" | 				"negotiator": "0.6.1" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"acme-http-01-standalone": { | 		"acme": { | ||||||
| 			"version": "3.0.5", | 			"version": "1.3.5", | ||||||
| 			"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz", | 			"resolved": "https://registry.npmjs.org/acme/-/acme-1.3.5.tgz", | ||||||
| 			"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg==" | 			"integrity": "sha512-KIFVyMho7y3RxRSTzkuX031TmfXwzl0ioy8+r2pnfLz6YWFQ5q7a/cYUDTgIbrFMPe/syY26Qv1DOdHQ5ARWcw==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"acme-v2": "^1.8.6" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"acme-dns-01-cli": { | ||||||
|  | 			"version": "3.0.7", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/acme-dns-01-cli/-/acme-dns-01-cli-3.0.7.tgz", | ||||||
|  | 			"integrity": "sha512-Aa4bUpq6ftX1VODiShOetOY5U0tsXY5EV7+fQwme3Q8Y9rjYBArBXHgFCAVKtK1AF+Ev8pIuF6Z42hzMFa73/w==" | ||||||
|  | 		}, | ||||||
|  | 		"acme-v2": { | ||||||
|  | 			"version": "1.8.6", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/acme-v2/-/acme-v2-1.8.6.tgz", | ||||||
|  | 			"integrity": "sha512-LWdicUYHTGDtYX7LlgsQurmM9txwfAFydg7mQLPKHrFMnNNtfJEtHC2fWfr+pFGNb3XKIbvyFUoyFB6cOmWRpA==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/request": "^1.3.11", | ||||||
|  | 				"rsa-compat": "^2.0.8" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"array-flatten": { | ||||||
|  | 			"version": "1.1.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", | ||||||
|  | 			"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"async-limiter": { | ||||||
|  | 			"version": "1.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", | ||||||
|  | 			"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"basic-auth": { | ||||||
|  | 			"version": "2.0.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", | ||||||
|  | 			"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"safe-buffer": "5.1.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"batch": { | ||||||
|  | 			"version": "0.6.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", | ||||||
|  | 			"integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"body-parser": { | ||||||
|  | 			"version": "1.18.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", | ||||||
|  | 			"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"bytes": "3.0.0", | ||||||
|  | 				"content-type": "~1.0.4", | ||||||
|  | 				"debug": "2.6.9", | ||||||
|  | 				"depd": "~1.1.2", | ||||||
|  | 				"http-errors": "~1.6.3", | ||||||
|  | 				"iconv-lite": "0.4.23", | ||||||
|  | 				"on-finished": "~2.3.0", | ||||||
|  | 				"qs": "6.5.2", | ||||||
|  | 				"raw-body": "2.3.3", | ||||||
|  | 				"type-is": "~1.6.16" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"bytes": { | ||||||
|  | 			"version": "3.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", | ||||||
|  | 			"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", | ||||||
|  | 			"dev": true | ||||||
| 		}, | 		}, | ||||||
| 		"cert-info": { | 		"cert-info": { | ||||||
| 			"version": "1.5.1", | 			"version": "1.5.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", | 			"resolved": "https://registry.npmjs.org/cert-info/-/cert-info-1.5.1.tgz", | ||||||
| 			"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" | 			"integrity": "sha512-eoQC/yAgW3gKTKxjzyClvi+UzuY97YCjcl+lSqbsGIy7HeGaWxCPOQFivhUYm27hgsBMhsJJFya3kGvK6PMIcQ==" | ||||||
| 		}, | 		}, | ||||||
|  | 		"content-disposition": { | ||||||
|  | 			"version": "0.5.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", | ||||||
|  | 			"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"content-type": { | ||||||
|  | 			"version": "1.0.4", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", | ||||||
|  | 			"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"cookie": { | ||||||
|  | 			"version": "0.3.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", | ||||||
|  | 			"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"cookie-signature": { | ||||||
|  | 			"version": "1.0.6", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", | ||||||
|  | 			"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"debug": { | ||||||
|  | 			"version": "2.6.9", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", | ||||||
|  | 			"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"ms": "2.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"depd": { | ||||||
|  | 			"version": "1.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", | ||||||
|  | 			"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"destroy": { | ||||||
|  | 			"version": "1.0.4", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", | ||||||
|  | 			"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"eckles": { | ||||||
|  | 			"version": "1.4.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/eckles/-/eckles-1.4.1.tgz", | ||||||
|  | 			"integrity": "sha512-auWyk/k8oSkVHaD4RxkPadKsLUcIwKgr/h8F7UZEueFDBO7BsE4y+H6IMUDbfqKIFPg/9MxV6KcBdJCmVVcxSA==" | ||||||
|  | 		}, | ||||||
|  | 		"ee-first": { | ||||||
|  | 			"version": "1.1.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", | ||||||
|  | 			"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"encodeurl": { | ||||||
|  | 			"version": "1.0.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", | ||||||
|  | 			"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
| 		"escape-html": { | 		"escape-html": { | ||||||
| 			"version": "1.0.3", | 			"version": "1.0.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", | 			"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", | ||||||
| 			"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" | 			"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" | ||||||
| 		}, | 		}, | ||||||
| 		"greenlock-manager-fs": { | 		"etag": { | ||||||
| 			"version": "3.0.1", | 			"version": "1.8.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.1.tgz", | 			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", | ||||||
| 			"integrity": "sha512-vZfGFq1TTKxaAqdGDUwNservrNzXx0xCwT/ovG/N378GrhS+U5S8B8LUlNtQU7Fdw6RToMiBcm22OOxSrvZ2zw==", | 			"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"eventemitter3": { | ||||||
|  | 			"version": "3.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", | ||||||
|  | 			"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"express": { | ||||||
|  | 			"version": "4.16.4", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", | ||||||
|  | 			"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"accepts": "~1.3.5", | ||||||
|  | 				"array-flatten": "1.1.1", | ||||||
|  | 				"body-parser": "1.18.3", | ||||||
|  | 				"content-disposition": "0.5.2", | ||||||
|  | 				"content-type": "~1.0.4", | ||||||
|  | 				"cookie": "0.3.1", | ||||||
|  | 				"cookie-signature": "1.0.6", | ||||||
|  | 				"debug": "2.6.9", | ||||||
|  | 				"depd": "~1.1.2", | ||||||
|  | 				"encodeurl": "~1.0.2", | ||||||
|  | 				"escape-html": "~1.0.3", | ||||||
|  | 				"etag": "~1.8.1", | ||||||
|  | 				"finalhandler": "1.1.1", | ||||||
|  | 				"fresh": "0.5.2", | ||||||
|  | 				"merge-descriptors": "1.0.1", | ||||||
|  | 				"methods": "~1.1.2", | ||||||
|  | 				"on-finished": "~2.3.0", | ||||||
|  | 				"parseurl": "~1.3.2", | ||||||
|  | 				"path-to-regexp": "0.1.7", | ||||||
|  | 				"proxy-addr": "~2.0.4", | ||||||
|  | 				"qs": "6.5.2", | ||||||
|  | 				"range-parser": "~1.2.0", | ||||||
|  | 				"safe-buffer": "5.1.2", | ||||||
|  | 				"send": "0.16.2", | ||||||
|  | 				"serve-static": "1.13.2", | ||||||
|  | 				"setprototypeof": "1.1.0", | ||||||
|  | 				"statuses": "~1.4.0", | ||||||
|  | 				"type-is": "~1.6.16", | ||||||
|  | 				"utils-merge": "1.0.1", | ||||||
|  | 				"vary": "~1.1.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"express-basic-auth": { | ||||||
|  | 			"version": "1.2.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.0.tgz", | ||||||
|  | 			"integrity": "sha512-iJ0h1Gk6fZRrFmO7tP9nIbxwNgCUJASfNj5fb0Hy15lGtbqqsxpt7609+wq+0XlByZjXmC/rslWQtnuSTVRIcg==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"basic-auth": "^2.0.1" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"finalhandler": { | ||||||
|  | 			"version": "1.1.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", | ||||||
|  | 			"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"debug": "2.6.9", | ||||||
|  | 				"encodeurl": "~1.0.2", | ||||||
|  | 				"escape-html": "~1.0.3", | ||||||
|  | 				"on-finished": "~2.3.0", | ||||||
|  | 				"parseurl": "~1.3.2", | ||||||
|  | 				"statuses": "~1.4.0", | ||||||
|  | 				"unpipe": "~1.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"follow-redirects": { | ||||||
|  | 			"version": "1.7.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", | ||||||
|  | 			"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"debug": "^3.2.6" | ||||||
|  | 			}, | ||||||
|  | 			"dependencies": { | ||||||
|  | 				"debug": { | ||||||
|  | 					"version": "3.2.6", | ||||||
|  | 					"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", | ||||||
|  | 					"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", | ||||||
|  | 					"dev": true, | ||||||
|  | 					"requires": { | ||||||
|  | 						"ms": "^2.1.1" | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 				"ms": { | ||||||
|  | 					"version": "2.1.2", | ||||||
|  | 					"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||||||
|  | 					"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", | ||||||
|  | 					"dev": true | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"forwarded": { | ||||||
|  | 			"version": "0.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", | ||||||
|  | 			"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"fresh": { | ||||||
|  | 			"version": "0.5.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", | ||||||
|  | 			"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"greenlock": { | ||||||
|  | 			"version": "2.8.8", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/greenlock/-/greenlock-2.8.8.tgz", | ||||||
|  | 			"integrity": "sha512-U2pqxXXf0naeZc2363Xe174C6/T9lXGZYQjXBqa/PMb1CYRQuHwXlAqFEUu75JkxyHAzFGj/uliqSyQwIc91Yg==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"acme": "^1.3.5", | ||||||
|  | 				"acme-dns-01-cli": "^3.0.0", | ||||||
|  | 				"acme-v2": "^1.8.6", | ||||||
|  | 				"cert-info": "^1.5.1", | ||||||
|  | 				"greenlock-store-fs": "^3.0.2", | ||||||
|  | 				"keypairs": "^1.2.14", | ||||||
|  | 				"le-challenge-fs": "^2.0.2", | ||||||
|  | 				"le-sni-auto": "^2.1.9", | ||||||
|  | 				"le-store-certbot": "^2.2.3", | ||||||
|  | 				"rsa-compat": "^2.0.8" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"greenlock-store-fs": { | ||||||
|  | 			"version": "3.0.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.0.2.tgz", | ||||||
|  | 			"integrity": "sha512-t4So75yKs1+7TqmxD5UKdf+zOQU0/4o0lb2auf5zUcAo7fwwNLOAXyWnnZRL3WuFBUiBGh1qXWleuMua0d3LPg==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"@root/mkdirp": "^1.0.0", | 				"@root/mkdirp": "^1.0.0", | ||||||
| 				"safe-replace": "^1.1.0" | 				"safe-replace": "^1.1.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"greenlock-store-fs": { | 		"http-errors": { | ||||||
| 			"version": "3.2.0", | 			"version": "1.6.3", | ||||||
| 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.0.tgz", | 			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", | ||||||
| 			"integrity": "sha512-zqcPnF+173oYq5qU7FoGtuqeG8dmmvAiSnz98kEHAHyvgRF9pE1T0MM0AuqDdj45I3kXlCj2gZBwutnRi37J3g==", | 			"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"depd": "~1.1.2", | ||||||
|  | 				"inherits": "2.0.3", | ||||||
|  | 				"setprototypeof": "1.1.0", | ||||||
|  | 				"statuses": ">= 1.4.0 < 2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"http-proxy": { | ||||||
|  | 			"version": "1.17.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", | ||||||
|  | 			"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"eventemitter3": "^3.0.0", | ||||||
|  | 				"follow-redirects": "^1.0.0", | ||||||
|  | 				"requires-port": "^1.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"iconv-lite": { | ||||||
|  | 			"version": "0.4.23", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", | ||||||
|  | 			"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"safer-buffer": ">= 2.1.2 < 3" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"inherits": { | ||||||
|  | 			"version": "2.0.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", | ||||||
|  | 			"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"ipaddr.js": { | ||||||
|  | 			"version": "1.8.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", | ||||||
|  | 			"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"keypairs": { | ||||||
|  | 			"version": "1.2.14", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/keypairs/-/keypairs-1.2.14.tgz", | ||||||
|  | 			"integrity": "sha512-ZoZfZMygyB0QcjSlz7Rh6wT2CJasYEHBPETtmHZEfxuJd7bnsOG5AdtPZqHZBT+hoHvuWCp/4y8VmvTvH0Y9uA==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"eckles": "^1.4.1", | ||||||
|  | 				"rasha": "^1.2.4" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"le-challenge-fs": { | ||||||
|  | 			"version": "2.0.9", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/le-challenge-fs/-/le-challenge-fs-2.0.9.tgz", | ||||||
|  | 			"integrity": "sha512-stzI6rxd+aXGxBl87QJKKY/i/wl3uz6EoWzX2xSazJvCPSYBQys1RVNgOcf0SfUQPh6TBCFJFSJkiR4mznb4sg==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"@root/mkdirp": "^1.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"le-sni-auto": { | ||||||
|  | 			"version": "2.1.9", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/le-sni-auto/-/le-sni-auto-2.1.9.tgz", | ||||||
|  | 			"integrity": "sha512-QmQHNwQDi/56GY8+qczFZ06FZbxaeJQjbjEhwwQHhkJ9IHhIQFkPfCT/OyDfLj4gqLIrg5ZX8CemxxVZnLEYfg==" | ||||||
|  | 		}, | ||||||
|  | 		"le-store-certbot": { | ||||||
|  | 			"version": "2.2.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/le-store-certbot/-/le-store-certbot-2.2.3.tgz", | ||||||
|  | 			"integrity": "sha512-c4ACR+v+JKMiAOOshLh6gdCKA7wIWR16+mROMLpQjq3rXJ3Vm8FaBHe2H+crT+flP+g7FmciAwUlfOJEJpIuCQ==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"@root/mkdirp": "^1.0.0", | 				"@root/mkdirp": "^1.0.0", | ||||||
|  | 				"pyconf": "^1.1.7", | ||||||
| 				"safe-replace": "^1.1.0" | 				"safe-replace": "^1.1.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"media-typer": { | ||||||
|  | 			"version": "0.3.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", | ||||||
|  | 			"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"merge-descriptors": { | ||||||
|  | 			"version": "1.0.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", | ||||||
|  | 			"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"methods": { | ||||||
|  | 			"version": "1.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", | ||||||
|  | 			"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"mime": { | ||||||
|  | 			"version": "1.4.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", | ||||||
|  | 			"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"mime-db": { | ||||||
|  | 			"version": "1.38.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", | ||||||
|  | 			"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"mime-types": { | ||||||
|  | 			"version": "2.1.22", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", | ||||||
|  | 			"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"mime-db": "~1.38.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"ms": { | ||||||
|  | 			"version": "2.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", | ||||||
|  | 			"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"negotiator": { | ||||||
|  | 			"version": "0.6.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", | ||||||
|  | 			"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"on-finished": { | ||||||
|  | 			"version": "2.3.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", | ||||||
|  | 			"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"ee-first": "1.1.1" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"parseurl": { | ||||||
|  | 			"version": "1.3.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", | ||||||
|  | 			"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"path-to-regexp": { | ||||||
|  | 			"version": "0.1.7", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", | ||||||
|  | 			"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"proxy-addr": { | ||||||
|  | 			"version": "2.0.4", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", | ||||||
|  | 			"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"forwarded": "~0.1.2", | ||||||
|  | 				"ipaddr.js": "1.8.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"pyconf": { | ||||||
|  | 			"version": "1.1.7", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/pyconf/-/pyconf-1.1.7.tgz", | ||||||
|  | 			"integrity": "sha512-v4clh33m68sjtMsh8XMpjhGWb/MQODAYZ1y7ORG5Qv58UK25OddoB+oXyexgDkK8ttFui/lZm2sQDgA2Ftjfkw==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"safe-replace": "^1.0.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"qs": { | ||||||
|  | 			"version": "6.5.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", | ||||||
|  | 			"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"range-parser": { | ||||||
|  | 			"version": "1.2.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", | ||||||
|  | 			"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"rasha": { | ||||||
|  | 			"version": "1.2.5", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/rasha/-/rasha-1.2.5.tgz", | ||||||
|  | 			"integrity": "sha512-KxtX+/fBk+wM7O3CNgwjSh5elwFilLvqWajhr6wFr2Hd63JnKTTi43Tw+Jb1hxJQWOwoya+NZWR2xztn3hCrTw==" | ||||||
|  | 		}, | ||||||
|  | 		"raw-body": { | ||||||
|  | 			"version": "2.3.3", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", | ||||||
|  | 			"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"bytes": "3.0.0", | ||||||
|  | 				"http-errors": "1.6.3", | ||||||
|  | 				"iconv-lite": "0.4.23", | ||||||
|  | 				"unpipe": "1.0.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
| 		"redirect-https": { | 		"redirect-https": { | ||||||
| 			"version": "1.3.0", | 			"version": "1.3.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz", | 			"resolved": "https://registry.npmjs.org/redirect-https/-/redirect-https-1.3.0.tgz", | ||||||
| @ -131,10 +510,133 @@ | |||||||
| 				"escape-html": "^1.0.3" | 				"escape-html": "^1.0.3" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
|  | 		"requires-port": { | ||||||
|  | 			"version": "1.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", | ||||||
|  | 			"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"rsa-compat": { | ||||||
|  | 			"version": "2.0.8", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/rsa-compat/-/rsa-compat-2.0.8.tgz", | ||||||
|  | 			"integrity": "sha512-BFiiSEbuxzsVdaxpejbxfX07qs+rtous49Y6mL/zw6YHh9cranDvm2BvBmqT3rso84IsxNlP5BXnuNvm1Wn3Tw==", | ||||||
|  | 			"requires": { | ||||||
|  | 				"keypairs": "^1.2.14" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"safe-buffer": { | ||||||
|  | 			"version": "5.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||||||
|  | 			"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
| 		"safe-replace": { | 		"safe-replace": { | ||||||
| 			"version": "1.1.0", | 			"version": "1.1.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | 			"resolved": "https://registry.npmjs.org/safe-replace/-/safe-replace-1.1.0.tgz", | ||||||
| 			"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | 			"integrity": "sha512-9/V2E0CDsKs9DWOOwJH7jYpSl9S3N05uyevNjvsnDauBqRowBPOyot1fIvV5N2IuZAbYyvrTXrYFVG0RZInfFw==" | ||||||
|  | 		}, | ||||||
|  | 		"safer-buffer": { | ||||||
|  | 			"version": "2.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", | ||||||
|  | 			"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"send": { | ||||||
|  | 			"version": "0.16.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", | ||||||
|  | 			"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"debug": "2.6.9", | ||||||
|  | 				"depd": "~1.1.2", | ||||||
|  | 				"destroy": "~1.0.4", | ||||||
|  | 				"encodeurl": "~1.0.2", | ||||||
|  | 				"escape-html": "~1.0.3", | ||||||
|  | 				"etag": "~1.8.1", | ||||||
|  | 				"fresh": "0.5.2", | ||||||
|  | 				"http-errors": "~1.6.2", | ||||||
|  | 				"mime": "1.4.1", | ||||||
|  | 				"ms": "2.0.0", | ||||||
|  | 				"on-finished": "~2.3.0", | ||||||
|  | 				"range-parser": "~1.2.0", | ||||||
|  | 				"statuses": "~1.4.0" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"serve-index": { | ||||||
|  | 			"version": "1.9.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", | ||||||
|  | 			"integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"accepts": "~1.3.4", | ||||||
|  | 				"batch": "0.6.1", | ||||||
|  | 				"debug": "2.6.9", | ||||||
|  | 				"escape-html": "~1.0.3", | ||||||
|  | 				"http-errors": "~1.6.2", | ||||||
|  | 				"mime-types": "~2.1.17", | ||||||
|  | 				"parseurl": "~1.3.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"serve-static": { | ||||||
|  | 			"version": "1.13.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", | ||||||
|  | 			"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"encodeurl": "~1.0.2", | ||||||
|  | 				"escape-html": "~1.0.3", | ||||||
|  | 				"parseurl": "~1.3.2", | ||||||
|  | 				"send": "0.16.2" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"setprototypeof": { | ||||||
|  | 			"version": "1.1.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", | ||||||
|  | 			"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"statuses": { | ||||||
|  | 			"version": "1.4.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", | ||||||
|  | 			"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"type-is": { | ||||||
|  | 			"version": "1.6.16", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", | ||||||
|  | 			"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"media-typer": "0.3.0", | ||||||
|  | 				"mime-types": "~2.1.18" | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 		"unpipe": { | ||||||
|  | 			"version": "1.0.0", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", | ||||||
|  | 			"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"utils-merge": { | ||||||
|  | 			"version": "1.0.1", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", | ||||||
|  | 			"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"vary": { | ||||||
|  | 			"version": "1.1.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", | ||||||
|  | 			"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", | ||||||
|  | 			"dev": true | ||||||
|  | 		}, | ||||||
|  | 		"ws": { | ||||||
|  | 			"version": "5.2.2", | ||||||
|  | 			"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", | ||||||
|  | 			"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", | ||||||
|  | 			"dev": true, | ||||||
|  | 			"requires": { | ||||||
|  | 				"async-limiter": "~1.0.0" | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								package.json
									
									
									
									
									
								
							| @ -1,26 +1,24 @@ | |||||||
| { | { | ||||||
| 	"name": "@root/greenlock-express", | 	"name": "greenlock-express", | ||||||
| 	"version": "3.0.10", | 	"version": "2.7.18", | ||||||
| 	"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", | 	"description": "Free SSL and managed or automatic HTTPS for node.js with Express, Koa, Connect, Hapi, and all other middleware systems.", | ||||||
| 	"main": "greenlock-express.js", | 	"main": "index.js", | ||||||
| 	"homepage": "https://greenlock.domains", | 	"homepage": "https://greenlock.domains", | ||||||
| 	"files": [ |  | ||||||
| 		"*.js", |  | ||||||
| 		"lib", |  | ||||||
| 		"scripts" |  | ||||||
| 	], |  | ||||||
| 	"scripts": { |  | ||||||
| 		"start": "node_todo server.js ./config.js", |  | ||||||
| 		"test": "node_todo test/greenlock.js" |  | ||||||
| 	}, |  | ||||||
| 	"directories": { | 	"directories": { | ||||||
| 		"example": "examples" | 		"example": "examples" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"@root/greenlock": "^3.0.17", | 		"greenlock": "^2.8.8", | ||||||
| 		"redirect-https": "^1.1.5" | 		"redirect-https": "^1.1.5" | ||||||
| 	}, | 	}, | ||||||
|  | 	"files": [ | ||||||
|  | 		"lib", | ||||||
|  | 		"scripts" | ||||||
|  | 	], | ||||||
| 	"trulyOptionalDependencies": { | 	"trulyOptionalDependencies": { | ||||||
|  | 		"spdy": "^3.4.7" | ||||||
|  | 	}, | ||||||
|  | 	"devDependencies": { | ||||||
| 		"http-proxy": "^1.17.0", | 		"http-proxy": "^1.17.0", | ||||||
| 		"express": "^4.16.3", | 		"express": "^4.16.3", | ||||||
| 		"express-basic-auth": "^1.2.0", | 		"express-basic-auth": "^1.2.0", | ||||||
| @ -29,7 +27,10 @@ | |||||||
| 		"serve-static": "^1.13.2", | 		"serve-static": "^1.13.2", | ||||||
| 		"ws": "^5.2.1" | 		"ws": "^5.2.1" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": {}, | 	"scripts": { | ||||||
|  | 		"start": "node server.js ./config.js", | ||||||
|  | 		"test": "node test/greenlock.js" | ||||||
|  | 	}, | ||||||
| 	"repository": { | 	"repository": { | ||||||
| 		"type": "git", | 		"type": "git", | ||||||
| 		"url": "https://git.rootprojects.org/root/greenlock-express.js.git" | 		"url": "https://git.rootprojects.org/root/greenlock-express.js.git" | ||||||
|  | |||||||
							
								
								
									
										325
									
								
								server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,325 @@ | |||||||
|  | #!/usr/bin/env node
 | ||||||
|  | "use strict"; | ||||||
|  | /*global Promise*/ | ||||||
|  | 
 | ||||||
|  | /////////////////////////////////
 | ||||||
|  | // an okay vhost + api example //
 | ||||||
|  | /////////////////////////////////
 | ||||||
|  | 
 | ||||||
|  | //
 | ||||||
|  | // I run this on a few servers. It demonstrates dynamic virtual hosting + apis
 | ||||||
|  | // /srv/www -> static sites in plain folders
 | ||||||
|  | // ex: /srv/www/example.com
 | ||||||
|  | //
 | ||||||
|  | // /srv/api -> express apps
 | ||||||
|  | // ex: /srv/api/api.example.com
 | ||||||
|  | //
 | ||||||
|  | 
 | ||||||
|  | var configpath = process.argv[2] || "./config.js"; | ||||||
|  | var config = require(configpath); | ||||||
|  | // The prefix where sites go by name.
 | ||||||
|  | // For example: whatever.com may live in /srv/www/whatever.com, thus /srv/www is our path
 | ||||||
|  | 
 | ||||||
|  | var path = require("path"); | ||||||
|  | var fs = require("./lib/compat.js").fsAsync; | ||||||
|  | var finalhandler = require("finalhandler"); | ||||||
|  | var serveStatic = require("serve-static"); | ||||||
|  | 
 | ||||||
|  | //var glx = require('greenlock-express')
 | ||||||
|  | var glx = require("./").create({ | ||||||
|  | 	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
 | ||||||
|  | 	// https://acme-staging-v02.api.letsencrypt.org/directory
 | ||||||
|  | 
 | ||||||
|  | 	configDir: config.configDir, // You MUST have access to write to directory where certs
 | ||||||
|  | 	// are saved. ex: /home/foouser/.config/acme
 | ||||||
|  | 
 | ||||||
|  | 	approveDomains: myApproveDomains, // Greenlock's wraps around tls.SNICallback. Check the
 | ||||||
|  | 	// domain name here and reject invalid ones
 | ||||||
|  | 
 | ||||||
|  | 	servername: config.servername, | ||||||
|  | 	app: myVhostApp, // Any node-style http app (i.e. express, koa, hapi, rill)
 | ||||||
|  | 
 | ||||||
|  | 	/* CHANGE TO A VALID EMAIL */ | ||||||
|  | 	email: config.email, // Email for Let's Encrypt account and Greenlock Security
 | ||||||
|  | 	agreeTos: true, // Accept Let's Encrypt ToS
 | ||||||
|  | 	//, communityMember: true                                   // Join Greenlock to get important updates, no spam
 | ||||||
|  | 
 | ||||||
|  | 	//, debug: true
 | ||||||
|  | 	store: require("greenlock-store-fs") | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | if (require.main === module) { | ||||||
|  | 	var server = glx.listen(80, 443); | ||||||
|  | 	server.on("listening", function() { | ||||||
|  | 		console.info(server.type + " listening on", server.address()); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function matchConfig(thing, domain) { | ||||||
|  | 	if (!thing) { | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	if (thing[domain]) { | ||||||
|  | 		return domain; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var keys = Object.keys(thing); | ||||||
|  | 	var result = null; | ||||||
|  | 	keys.some(function(k) { | ||||||
|  | 		if ("*" !== k[0]) { | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// "foo.whatever.com".endsWith("*.whatever.com".slice(1))
 | ||||||
|  | 		if (domain.endsWith(k.slice(1).toLowerCase())) { | ||||||
|  | 			result = k; | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function myApproveDomains(opts) { | ||||||
|  | 	console.info("SNI:", opts.domain); | ||||||
|  | 	// In this example the filesystem is our "database".
 | ||||||
|  | 	// 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
 | ||||||
|  | 
 | ||||||
|  | 	var domains = []; | ||||||
|  | 	var original = opts.domain; | ||||||
|  | 	var bare = original.replace(/^(www|api)\./, ""); | ||||||
|  | 	var challenger = matchConfig(config.challenges, original); | ||||||
|  | 	if (challenger) { | ||||||
|  | 		opts.challenges = { | ||||||
|  | 			"dns-01": config.challenges[challenger] | ||||||
|  | 		}; | ||||||
|  | 		domains.push(challenger); | ||||||
|  | 		return approveThem(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (matchConfig(config.proxy, original)) { | ||||||
|  | 		console.log("debug: found proxy for", original); | ||||||
|  | 		domains.push(original); | ||||||
|  | 		return approveThem(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	function approveThem() { | ||||||
|  | 		console.info("Approved domains:", domains); | ||||||
|  | 		opts.domains = domains; | ||||||
|  | 		//opts.email = email;
 | ||||||
|  | 		opts.agreeTos = true; | ||||||
|  | 		// pick the shortest (bare) or latest (www. instead of api.) to be the subject
 | ||||||
|  | 		opts.subject = opts.domains.sort(function(a, b) { | ||||||
|  | 			var len = a.length - b.length; | ||||||
|  | 			if (0 !== len) { | ||||||
|  | 				return len; | ||||||
|  | 			} | ||||||
|  | 			if (a < b) { | ||||||
|  | 				return 1; | ||||||
|  | 			} else { | ||||||
|  | 				return -1; | ||||||
|  | 			} | ||||||
|  | 		})[0]; | ||||||
|  | 
 | ||||||
|  | 		if (!opts.challenges) { | ||||||
|  | 			opts.challenges = {}; | ||||||
|  | 		} | ||||||
|  | 		opts.challenges["http-01"] = require("le-challenge-fs"); | ||||||
|  | 		//opts.challenges['dns-01'] = require('le-challenge-dns');
 | ||||||
|  | 
 | ||||||
|  | 		// explicitly set account id and certificate.id
 | ||||||
|  | 		opts.account = { id: opts.email }; | ||||||
|  | 		opts.certificate = { id: opts.subject }; | ||||||
|  | 
 | ||||||
|  | 		return Promise.resolve(opts); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// The goal here is to support both bare and www domains
 | ||||||
|  | 	//
 | ||||||
|  | 	// dns:example.com + fs:www.example.com => both
 | ||||||
|  | 	// dns:www.example.com + fs:example.com => both
 | ||||||
|  | 	//
 | ||||||
|  | 	// dns:api.example.com + fs:www.example.com => www.example.com
 | ||||||
|  | 	// dns:api.example.com + fs:example.com => example.com
 | ||||||
|  | 	//
 | ||||||
|  | 	// dns:example.com + fs:example.com => example.com
 | ||||||
|  | 	// dns:www.example.com + fs:www.example.com => www.example.com
 | ||||||
|  | 	return checkWwws(bare) | ||||||
|  | 		.then(function(hostname) { | ||||||
|  | 			// hostname is either example.com or www.example.com
 | ||||||
|  | 			domains.push(hostname); | ||||||
|  | 			if ("api." + bare !== original) { | ||||||
|  | 				if (!domains.includes(original)) { | ||||||
|  | 					domains.push(original); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			// ignore error
 | ||||||
|  | 			return null; | ||||||
|  | 		}) | ||||||
|  | 		.then(function() { | ||||||
|  | 			// check for api prefix
 | ||||||
|  | 			var apiname = bare; | ||||||
|  | 			if (domains.length) { | ||||||
|  | 				apiname = "api." + bare; | ||||||
|  | 			} | ||||||
|  | 			return checkApi(apiname) | ||||||
|  | 				.then(function(app) { | ||||||
|  | 					if (!app) { | ||||||
|  | 						return null; | ||||||
|  | 					} | ||||||
|  | 					domains.push(apiname); | ||||||
|  | 				}) | ||||||
|  | 				.catch(function() { | ||||||
|  | 					return null; | ||||||
|  | 				}); | ||||||
|  | 		}) | ||||||
|  | 		.then(function() { | ||||||
|  | 			// It's possible that example.com could have been requested,
 | ||||||
|  | 			// and not found, but api.example.com was found
 | ||||||
|  | 			if (!domains.includes(original)) { | ||||||
|  | 				return Promise.reject(new Error("no bare, www., or api. domain matching '" + opts.domain + "'")); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			return approveThem(); | ||||||
|  | 		}); | ||||||
|  | } | ||||||
|  | exports.myApproveDomains = myApproveDomains; | ||||||
|  | 
 | ||||||
|  | function checkApi(hostname) { | ||||||
|  | 	var apipath = path.join(config.api, hostname); | ||||||
|  | 	var link = ""; | ||||||
|  | 	return fs | ||||||
|  | 		.stat(apipath) | ||||||
|  | 		.then(function(stats) { | ||||||
|  | 			if (stats.isDirectory()) { | ||||||
|  | 				return require(apipath); | ||||||
|  | 			} | ||||||
|  | 			return fs.readFile(apipath, "utf8").then(function(txt) { | ||||||
|  | 				var linkpath = txt.split("\n")[0]; | ||||||
|  | 				link = " => " + linkpath + " "; | ||||||
|  | 				return require(linkpath); | ||||||
|  | 			}); | ||||||
|  | 		}) | ||||||
|  | 		.catch(function(e) { | ||||||
|  | 			if ("ENOENT" === e.code) { | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
|  | 			console.error(e); | ||||||
|  | 			throw new Error("rejecting '" + hostname + "' because '" + apipath + link + "' failed at require()"); | ||||||
|  | 		}); | ||||||
|  | } | ||||||
|  | exports.checkApi = checkApi; | ||||||
|  | 
 | ||||||
|  | function checkWwws(_hostname) { | ||||||
|  | 	if (!_hostname) { | ||||||
|  | 		// SECURITY don't serve the whole config.srv
 | ||||||
|  | 		return Promise.reject(new Error("missing hostname")); | ||||||
|  | 	} | ||||||
|  | 	var hostname = _hostname; | ||||||
|  | 	var hostdir = path.join(config.srv, hostname); | ||||||
|  | 	// TODO could test for www/no-www both in directory
 | ||||||
|  | 	return fs | ||||||
|  | 		.readdir(hostdir) | ||||||
|  | 		.then(function() { | ||||||
|  | 			// TODO check for some sort of htaccess.json and use email in that
 | ||||||
|  | 			// NOTE: you can also change other options such as `challengeType` and `challenge`
 | ||||||
|  | 			// opts.challengeType = 'http-01';
 | ||||||
|  | 			// opts.challenge = require('le-challenge-fs').create({});
 | ||||||
|  | 			return hostname; | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			if ("www." === hostname.slice(0, 4)) { | ||||||
|  | 				// Assume we'll redirect to non-www if it's available.
 | ||||||
|  | 				hostname = hostname.slice(4); | ||||||
|  | 				hostdir = path.join(config.srv, hostname); | ||||||
|  | 				return fs.readdir(hostdir).then(function() { | ||||||
|  | 					return hostname; | ||||||
|  | 				}); | ||||||
|  | 			} else { | ||||||
|  | 				// Or check and see if perhaps we should redirect non-www to www
 | ||||||
|  | 				hostname = "www." + hostname; | ||||||
|  | 				hostdir = path.join(config.srv, hostname); | ||||||
|  | 				return fs.readdir(hostdir).then(function() { | ||||||
|  | 					return hostname; | ||||||
|  | 				}); | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 		.catch(function() { | ||||||
|  | 			throw new Error("rejecting '" + _hostname + "' because '" + hostdir + "' could not be read"); | ||||||
|  | 		}); | ||||||
|  | } | ||||||
|  | exports.checkWwws = checkWwws; | ||||||
|  | 
 | ||||||
|  | var httpProxy = require("http-proxy"); | ||||||
|  | 
 | ||||||
|  | var proxy = httpProxy.createProxyServer({ | ||||||
|  | 	xfwd: true | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | proxy.on("error", function(req, res) { | ||||||
|  | 	res.statusCode = 500; | ||||||
|  | 	res.end("500: Server Error"); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | function myVhostApp(req, res) { | ||||||
|  | 	req.on("error", function(err) { | ||||||
|  | 		console.error("HTTPS Request Network Connection Error:"); | ||||||
|  | 		console.error(err); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// this is protected by greenlock-express from domain fronting attacks
 | ||||||
|  | 	var host = req.headers.host; | ||||||
|  | 	// ex: example.com
 | ||||||
|  | 	// ex: example.com:4080
 | ||||||
|  | 	console.log("debug: host is", host); | ||||||
|  | 	var domain = matchConfig(config.proxy, host); | ||||||
|  | 	if (domain) { | ||||||
|  | 		console.log("debug: forwarding to", config.proxy[domain]); | ||||||
|  | 		proxy.web(req, res, { target: config.proxy[domain] }); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// SECURITY greenlock pre-sanitizes hostnames to prevent unauthorized fs access so you don't have to
 | ||||||
|  | 	// (also: only domains approved above will get here)
 | ||||||
|  | 	console.info(""); | ||||||
|  | 	console.info(req.method, (req.headers.host || "") + req.url); | ||||||
|  | 	Object.keys(req.headers).forEach(function(key) { | ||||||
|  | 		console.info(key, req.headers[key]); | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	// We could cache wether or not a host exists for some amount of time
 | ||||||
|  | 	var fin = finalhandler(req, res); | ||||||
|  | 	return checkWwws(req.headers.host) | ||||||
|  | 		.then(function(hostname) { | ||||||
|  | 			if (hostname !== req.headers.host) { | ||||||
|  | 				res.statusCode = 302; | ||||||
|  | 				res.setHeader("Location", "https://" + hostname); | ||||||
|  | 				// SECURITY this is safe only because greenlock disallows invalid hostnames
 | ||||||
|  | 				res.end("<!-- redirecting to https://" + hostname + "-->"); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			var serve = serveStatic(path.join(config.srv, hostname), { redirect: true }); | ||||||
|  | 			serve(req, res, fin); | ||||||
|  | 		}) | ||||||
|  | 		.catch(function(err) { | ||||||
|  | 			return checkApi(req.headers.host) | ||||||
|  | 				.then(function(app) { | ||||||
|  | 					if (app) { | ||||||
|  | 						app(req, res); | ||||||
|  | 						return; | ||||||
|  | 					} | ||||||
|  | 					console.error("none found", err); | ||||||
|  | 					fin(); | ||||||
|  | 				}) | ||||||
|  | 				.catch(function(err) { | ||||||
|  | 					console.error("api crashed error", err); | ||||||
|  | 					fin(err); | ||||||
|  | 				}); | ||||||
|  | 		}); | ||||||
|  | } | ||||||
							
								
								
									
										157
									
								
								servers.js
									
									
									
									
									
								
							
							
						
						
									
										157
									
								
								servers.js
									
									
									
									
									
								
							| @ -1,157 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var Servers = module.exports; |  | ||||||
| 
 |  | ||||||
| var http = require("http"); |  | ||||||
| var HttpMiddleware = require("./http-middleware.js"); |  | ||||||
| var HttpsMiddleware = require("./https-middleware.js"); |  | ||||||
| var sni = require("./sni.js"); |  | ||||||
| var cluster = require("cluster"); |  | ||||||
| 
 |  | ||||||
| Servers.create = function(greenlock) { |  | ||||||
| 	var servers = {}; |  | ||||||
| 	var _httpServer; |  | ||||||
| 	var _httpsServer; |  | ||||||
| 
 |  | ||||||
| 	function startError(e) { |  | ||||||
| 		explainError(e); |  | ||||||
| 		process.exit(1); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	servers.httpServer = function(defaultApp) { |  | ||||||
| 		if (_httpServer) { |  | ||||||
| 			return _httpServer; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		_httpServer = http.createServer(HttpMiddleware.create(greenlock, defaultApp)); |  | ||||||
| 		_httpServer.once("error", startError); |  | ||||||
| 
 |  | ||||||
| 		return _httpServer; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	var _middlewareApp; |  | ||||||
| 
 |  | ||||||
| 	servers.httpsServer = function(secureOpts, defaultApp) { |  | ||||||
| 		if (defaultApp) { |  | ||||||
| 			// TODO guard against being set twice?
 |  | ||||||
| 			_middlewareApp = defaultApp; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (_httpsServer) { |  | ||||||
| 			if (secureOpts && Object.keys(secureOpts).length) { |  | ||||||
| 				throw new Error("Call glx.httpsServer(tlsOptions) before calling glx.serveApp(app)"); |  | ||||||
| 			} |  | ||||||
| 			return _httpsServer; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (!secureOpts) { |  | ||||||
| 			secureOpts = {}; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		_httpsServer = createSecureServer( |  | ||||||
| 			wrapDefaultSniCallback(greenlock, secureOpts), |  | ||||||
| 			HttpsMiddleware.create(greenlock, function(req, res) { |  | ||||||
| 				if (!_middlewareApp) { |  | ||||||
| 					throw new Error("Set app with `glx.serveApp(app)` or `glx.httpsServer(tlsOptions, app)`"); |  | ||||||
| 				} |  | ||||||
| 				_middlewareApp(req, res); |  | ||||||
| 			}) |  | ||||||
| 		); |  | ||||||
| 		_httpsServer.once("error", startError); |  | ||||||
| 
 |  | ||||||
| 		return _httpsServer; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	servers.id = function() { |  | ||||||
| 		return (cluster.isWorker && cluster.worker.id) || "0"; |  | ||||||
| 	}; |  | ||||||
| 	servers.serveApp = function(app) { |  | ||||||
| 		return new Promise(function(resolve, reject) { |  | ||||||
| 			if ("function" !== typeof app) { |  | ||||||
| 				reject(new Error("glx.serveApp(app) expects a node/express app in the format `function (req, res) { ... }`")); |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			var id = cluster.isWorker && cluster.worker.id; |  | ||||||
| 			var idstr = (id && "#" + id + " ") || ""; |  | ||||||
| 			var plainServer = servers.httpServer(require("redirect-https")()); |  | ||||||
| 			var plainAddr = "0.0.0.0"; |  | ||||||
| 			var plainPort = 80; |  | ||||||
| 			plainServer.listen(plainPort, plainAddr, function() { |  | ||||||
| 				console.info( |  | ||||||
| 					idstr + "Listening on", |  | ||||||
| 					plainAddr + ":" + plainPort, |  | ||||||
| 					"for ACME challenges, and redirecting to HTTPS" |  | ||||||
| 				); |  | ||||||
| 
 |  | ||||||
| 				// TODO fetch greenlock.servername
 |  | ||||||
| 				_middlewareApp = app || _middlewareApp; |  | ||||||
| 				var secureServer = servers.httpsServer(null, app); |  | ||||||
| 				var secureAddr = "0.0.0.0"; |  | ||||||
| 				var securePort = 443; |  | ||||||
| 				secureServer.listen(securePort, secureAddr, function() { |  | ||||||
| 					console.info(idstr + "Listening on", secureAddr + ":" + securePort, "for secure traffic"); |  | ||||||
| 
 |  | ||||||
| 					plainServer.removeListener("error", startError); |  | ||||||
| 					secureServer.removeListener("error", startError); |  | ||||||
| 					resolve(); |  | ||||||
| 				}); |  | ||||||
| 			}); |  | ||||||
| 		}); |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	return servers; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function explainError(e) { |  | ||||||
| 	console.error(); |  | ||||||
| 	console.error("Error: " + e.message); |  | ||||||
| 	if ("EACCES" === e.errno) { |  | ||||||
| 		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)"'); |  | ||||||
| 	} else if ("EADDRINUSE" === e.errno) { |  | ||||||
| 		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."); |  | ||||||
| 	} else { |  | ||||||
| 		console.error(e.code + ": '" + e.address + ":" + e.port + "'"); |  | ||||||
| 	} |  | ||||||
| 	console.error(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function wrapDefaultSniCallback(greenlock, secureOpts) { |  | ||||||
| 	// I'm not sure yet if the original SNICallback
 |  | ||||||
| 	// should be called before or after, so I'm just
 |  | ||||||
| 	// going to delay making that choice until I have the use case
 |  | ||||||
| 	/* |  | ||||||
| 		if (!secureOpts.SNICallback) { |  | ||||||
| 			secureOpts.SNICallback = function(servername, cb) { |  | ||||||
| 				cb(null, null); |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
|   */ |  | ||||||
| 	if (secureOpts.SNICallback) { |  | ||||||
| 		console.warn(); |  | ||||||
| 		console.warn("[warning] Ignoring the given tlsOptions.SNICallback function."); |  | ||||||
| 		console.warn(); |  | ||||||
| 		console.warn("          We're very open to implementing support for this,"); |  | ||||||
| 		console.warn("          we just don't understand the use case yet."); |  | ||||||
| 		console.warn("          Please open an issue to discuss. We'd love to help."); |  | ||||||
| 		console.warn(); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// TODO greenlock.servername for workers
 |  | ||||||
| 	secureOpts.SNICallback = sni.create(greenlock, secureOpts); |  | ||||||
| 	return secureOpts; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function createSecureServer(secureOpts, fn) { |  | ||||||
| 	var major = process.versions.node.split(".")[0]; |  | ||||||
| 
 |  | ||||||
| 	// TODO can we trust earlier versions as well?
 |  | ||||||
| 	if (major >= 12) { |  | ||||||
| 		secureOpts.allowHTTP1 = true; |  | ||||||
| 		return require("http2").createSecureServer(secureOpts, fn); |  | ||||||
| 	} else { |  | ||||||
| 		return require("https").createServer(secureOpts, fn); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
							
								
								
									
										25
									
								
								single.js
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								single.js
									
									
									
									
									
								
							| @ -1,25 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| require("./main.js"); |  | ||||||
| 
 |  | ||||||
| var Single = module.exports; |  | ||||||
| var Servers = require("./servers.js"); |  | ||||||
| 
 |  | ||||||
| Single.create = function(opts) { |  | ||||||
| 	var greenlock = require("./greenlock.js").create(opts); |  | ||||||
| 
 |  | ||||||
| 	var servers = Servers.create(greenlock); |  | ||||||
| 
 |  | ||||||
| 	var single = { |  | ||||||
| 		serve: function(fn) { |  | ||||||
| 			fn(servers); |  | ||||||
| 			return single; |  | ||||||
| 		}, |  | ||||||
| 		master: function(/*fn*/) { |  | ||||||
| 			// ignore
 |  | ||||||
| 			//fn(master);
 |  | ||||||
| 			return single; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 	return single; |  | ||||||
| }; |  | ||||||
							
								
								
									
										194
									
								
								sni.js
									
									
									
									
									
								
							
							
						
						
									
										194
									
								
								sni.js
									
									
									
									
									
								
							| @ -1,194 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var sni = module.exports; |  | ||||||
| var tls = require("tls"); |  | ||||||
| var servernameRe = /^[a-z0-9\.\-]+$/i; |  | ||||||
| 
 |  | ||||||
| // a nice, round, irrational number - about every 6¼ hours
 |  | ||||||
| var refreshOffset = Math.round(Math.PI * 2 * (60 * 60 * 1000)); |  | ||||||
| // and another, about 15 minutes
 |  | ||||||
| var refreshStagger = Math.round(Math.PI * 5 * (60 * 1000)); |  | ||||||
| // and another, about 30 seconds
 |  | ||||||
| var smallStagger = Math.round(Math.PI * (30 * 1000)); |  | ||||||
| 
 |  | ||||||
| //secureOpts.SNICallback = sni.create(greenlock, secureOpts);
 |  | ||||||
| sni.create = function(greenlock, secureOpts) { |  | ||||||
| 	var _cache = {}; |  | ||||||
| 	var defaultServername = greenlock.servername || ""; |  | ||||||
| 
 |  | ||||||
| 	if (secureOpts.cert) { |  | ||||||
| 		// Note: it's fine if greenlock.servername is undefined,
 |  | ||||||
| 		// but if the caller wants this to auto-renew, they should define it
 |  | ||||||
| 		_cache[defaultServername] = { |  | ||||||
| 			refreshAt: 0, |  | ||||||
| 			secureContext: tls.createSecureContext(secureOpts) |  | ||||||
| 		}; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return getSecureContext; |  | ||||||
| 
 |  | ||||||
| 	function notify(ev, args) { |  | ||||||
| 		try { |  | ||||||
| 			// TODO _notify() or notify()?
 |  | ||||||
| 			(greenlock.notify || greenlock._notify)(ev, args); |  | ||||||
| 		} catch (e) { |  | ||||||
| 			console.error(e); |  | ||||||
| 			console.error(ev, args); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	function getSecureContext(servername, cb) { |  | ||||||
| 		//console.log("debug sni", servername);
 |  | ||||||
| 		if ("string" !== typeof servername) { |  | ||||||
| 			// this will never happen... right? but stranger things have...
 |  | ||||||
| 			console.error("[sanity fail] non-string servername:", servername); |  | ||||||
| 			cb(new Error("invalid servername"), null); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		var secureContext = getCachedContext(servername); |  | ||||||
| 		if (secureContext) { |  | ||||||
| 			//console.log("debug sni got cached context", servername, getCachedMeta(servername));
 |  | ||||||
| 			cb(null, secureContext); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		getFreshContext(servername) |  | ||||||
| 			.then(function(secureContext) { |  | ||||||
| 				if (secureContext) { |  | ||||||
| 					//console.log("debug sni got fresh context", servername, getCachedMeta(servername));
 |  | ||||||
| 					cb(null, secureContext); |  | ||||||
| 					return; |  | ||||||
| 				} |  | ||||||
| 				// Note: this does not replace tlsSocket.setSecureContext()
 |  | ||||||
| 				// as it only works when SNI has been sent
 |  | ||||||
| 				//console.log("debug sni got default context", servername, getCachedMeta(servername));
 |  | ||||||
| 				cb(null, getDefaultContext()); |  | ||||||
| 			}) |  | ||||||
| 			.catch(function(err) { |  | ||||||
| 				if (!err.context) { |  | ||||||
| 					err.context = "sni_callback"; |  | ||||||
| 				} |  | ||||||
| 				notify("error", err); |  | ||||||
| 				//console.log("debug sni error", servername, err);
 |  | ||||||
| 				cb(err); |  | ||||||
| 			}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	function getCachedMeta(servername) { |  | ||||||
| 		var meta = _cache[servername]; |  | ||||||
| 		if (!meta) { |  | ||||||
| 			if (!_cache[wildname(servername)]) { |  | ||||||
| 				return null; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return meta; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	function getCachedContext(servername) { |  | ||||||
| 		var meta = getCachedMeta(servername); |  | ||||||
| 		if (!meta) { |  | ||||||
| 			return null; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// always renew in background
 |  | ||||||
| 		if (!meta.refreshAt || Date.now() >= meta.refreshAt) { |  | ||||||
| 			getFreshContext(servername).catch(function(e) { |  | ||||||
| 				if (!e.context) { |  | ||||||
| 					e.context = "sni_background_refresh"; |  | ||||||
| 				} |  | ||||||
| 				notify("error", e); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// under normal circumstances this would never be expired
 |  | ||||||
| 		// and, if it is expired, something is so wrong it's probably
 |  | ||||||
| 		// not worth wating for the renewal - it has probably failed
 |  | ||||||
| 		return meta.secureContext; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	function getFreshContext(servername) { |  | ||||||
| 		var meta = getCachedMeta(servername); |  | ||||||
| 		if (!meta && !validServername(servername)) { |  | ||||||
| 			return Promise.resolve(null); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		if (meta) { |  | ||||||
| 			// prevent stampedes
 |  | ||||||
| 			meta.refreshAt = Date.now() + randomRefreshOffset(); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// TODO don't get unknown certs at all, rely on auto-updates from greenlock
 |  | ||||||
| 		// Note: greenlock.get() will return an existing fresh cert or issue a new one
 |  | ||||||
| 		return greenlock.get({ servername: servername }).then(function(result) { |  | ||||||
| 			var meta = getCachedMeta(servername); |  | ||||||
| 			if (!meta) { |  | ||||||
| 				meta = _cache[servername] = { secureContext: { _valid: false } }; |  | ||||||
| 			} |  | ||||||
| 			// prevent from being punked by bot trolls
 |  | ||||||
| 			meta.refreshAt = Date.now() + smallStagger; |  | ||||||
| 
 |  | ||||||
| 			// nothing to do
 |  | ||||||
| 			if (!result) { |  | ||||||
| 				return null; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			// we only care about the first one
 |  | ||||||
| 			var pems = result.pems; |  | ||||||
| 			var site = result.site; |  | ||||||
| 			if (!pems || !pems.cert) { |  | ||||||
| 				// nothing to do
 |  | ||||||
| 				// (and the error should have been reported already)
 |  | ||||||
| 				return null; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			meta = { |  | ||||||
| 				refreshAt: Date.now() + randomRefreshOffset(), |  | ||||||
| 				secureContext: tls.createSecureContext({ |  | ||||||
| 					// TODO support passphrase-protected privkeys
 |  | ||||||
| 					key: pems.privkey, |  | ||||||
| 					cert: pems.cert + "\n" + pems.chain + "\n" |  | ||||||
| 				}) |  | ||||||
| 			}; |  | ||||||
| 			meta.secureContext._valid = true; |  | ||||||
| 
 |  | ||||||
| 			// copy this same object into every place
 |  | ||||||
| 			(result.altnames || site.altnames || [result.subject || site.subject]).forEach(function(altname) { |  | ||||||
| 				_cache[altname] = meta; |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			return meta.secureContext; |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	function getDefaultContext() { |  | ||||||
| 		return getCachedContext(defaultServername); |  | ||||||
| 	} |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // whenever we need to know when to refresh next
 |  | ||||||
| function randomRefreshOffset() { |  | ||||||
| 	var stagger = Math.round(refreshStagger / 2) - Math.round(Math.random() * refreshStagger); |  | ||||||
| 	return refreshOffset + stagger; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function validServername(servername) { |  | ||||||
| 	// format and (lightly) sanitize sni so that users can be naive
 |  | ||||||
| 	// and not have to worry about SQL injection or fs discovery
 |  | ||||||
| 
 |  | ||||||
| 	servername = (servername || "").toLowerCase(); |  | ||||||
| 	// hostname labels allow a-z, 0-9, -, and are separated by dots
 |  | ||||||
| 	// _ is sometimes allowed, but not as a "hostname", and not by Let's Encrypt ACME
 |  | ||||||
| 	// REGEX // https://www.codeproject.com/Questions/1063023/alphanumeric-validation-javascript-without-regex
 |  | ||||||
| 	return servernameRe.test(servername) && -1 === servername.indexOf(".."); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function wildname(servername) { |  | ||||||
| 	return ( |  | ||||||
| 		"*." + |  | ||||||
| 		servername |  | ||||||
| 			.split(".") |  | ||||||
| 			.slice(1) |  | ||||||
| 			.join(".") |  | ||||||
| 	); |  | ||||||
| } |  | ||||||
							
								
								
									
										62
									
								
								worker.js
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								worker.js
									
									
									
									
									
								
							| @ -1,62 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
| 
 |  | ||||||
| var Worker = module.exports; |  | ||||||
| // *very* generous, but well below the http norm of 120
 |  | ||||||
| var messageTimeout = 30 * 1000; |  | ||||||
| var msgPrefix = "greenlock:"; |  | ||||||
| 
 |  | ||||||
| Worker.create = function() { |  | ||||||
| 	var greenlock = {}; |  | ||||||
| 	["getAcmeHttp01ChallengeResponse", "get", "notify"].forEach(function(k) { |  | ||||||
| 		greenlock[k] = function(args) { |  | ||||||
| 			return rpc(k, args); |  | ||||||
| 		}; |  | ||||||
| 	}); |  | ||||||
| 
 |  | ||||||
| 	var worker = { |  | ||||||
| 		serve: function(fn) { |  | ||||||
| 			var servers = require("./servers.js").create(greenlock); |  | ||||||
| 			fn(servers); |  | ||||||
| 			return worker; |  | ||||||
| 		}, |  | ||||||
| 		master: function() { |  | ||||||
| 			// ignore
 |  | ||||||
| 			return worker; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 	return worker; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| function rpc(funcname, msg) { |  | ||||||
| 	return new Promise(function(resolve, reject) { |  | ||||||
| 		var rnd = Math.random() |  | ||||||
| 			.toString() |  | ||||||
| 			.slice(2) |  | ||||||
| 			.toString(16); |  | ||||||
| 		var id = msgPrefix + rnd; |  | ||||||
| 		var timeout; |  | ||||||
| 
 |  | ||||||
| 		function getResponse(msg) { |  | ||||||
| 			if (msg._id !== id) { |  | ||||||
| 				return; |  | ||||||
| 			} |  | ||||||
| 			process.removeListener("message", getResponse); |  | ||||||
| 			clearTimeout(timeout); |  | ||||||
| 			resolve(msg._result); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// TODO keep a single listener than just responds
 |  | ||||||
| 		// via a collection of callbacks? or leave as is?
 |  | ||||||
| 		process.on("message", getResponse); |  | ||||||
| 		process.send({ |  | ||||||
| 			_id: id, |  | ||||||
| 			_funcname: funcname, |  | ||||||
| 			_input: msg |  | ||||||
| 		}); |  | ||||||
| 
 |  | ||||||
| 		timeout = setTimeout(function() { |  | ||||||
| 			process.removeListener("message", getResponse); |  | ||||||
| 			reject(new Error("worker rpc request timeout")); |  | ||||||
| 		}, messageTimeout); |  | ||||||
| 	}); |  | ||||||
| } |  | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user