Compare commits
	
		
			29 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d11b45c409 | |||
| 36abf769be | |||
| c93ecf307b | |||
| 3ea7d3e97b | |||
| fff5192fb4 | |||
| fceeb8c72c | |||
| a7526ffad8 | |||
| d324179cb1 | |||
| 18b36d7d23 | |||
| 7a2de022fa | |||
| e478d27628 | |||
|  | 6f2c1ec5ba | ||
| 894a01fa4e | |||
| df1259cd9d | |||
| 3f8a54a988 | |||
| 26adaf2037 | |||
| 2a9b463964 | |||
| bc4a5b44ae | |||
|  | 378b9310aa | ||
|  | 405e98620c | ||
| 3f437c6ebb | |||
| 00e9d96f8b | |||
| 31ba1186be | |||
| af7c75a0f7 | |||
| 8d464d6810 | |||
| de051dd3a2 | |||
| d14163d153 | |||
| 6df0dc2f76 | |||
| 0dd3641dc2 | 
							
								
								
									
										749
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										749
									
								
								README.md
									
									
									
									
									
								
							| @ -1,466 +1,363 @@ | |||||||
| # Greenlock v3 on its way (Nov 1st, 2019) | # New Documentation & [v2/v3 Migration Guide](https://git.rootprojects.org/root/greenlock.js/src/branch/v3/MIGRATION_GUIDE_V2_V3.md) | ||||||
| 
 | 
 | ||||||
| Greenlock v3 is in private beta (for backers) and will be available publicly by Nov 1st. | Greenlock v3 just came out of private beta **today** (Nov 1st, 2019). | ||||||
| 
 | 
 | ||||||
| You can keep an eye for updates on the [campaign page](https://indiegogo.com/at/greenlock) and, | The code is complete and we're working on great documentation. | ||||||
| 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/) | ||||||
| 
 | 
 | ||||||
| <table> | Free SSL, Automated HTTPS / HTTP2, served with Node via Express, Koa, hapi, etc. | ||||||
|   <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> |  | ||||||
| 
 | 
 | ||||||
| # [Greenlock](https://git.rootprojects.org/root/greenlock-express.js)™ for Express.js | a [Root](https://rootprojects.org) project | ### Let's Encrypt for Node, Express, etc | ||||||
| 
 | 
 | ||||||
| <small>formerly letsencrypt-express</small> | Greenlock Express is a **Web Server** with **Fully Automated HTTPS** and renewals. | ||||||
| 
 |  | ||||||
| Free SSL, Free Wildcard SSL, and Fully Automated HTTPS made dead simple<br> |  | ||||||
| <small>certificates issued by Let's Encrypt v2 via [ACME](https://git.rootprojects.org/root/acme-v2.js)</small> |  | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| <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> |  | ||||||
| 
 |  | ||||||
| [Greenlock™](https://git.rootprojects.org/root/greenlock.js) is for |  | ||||||
| [Web Servers](https://git.rootprojects.org/root/greenlock-cli.js), |  | ||||||
| [Web Browsers](https://greenlock.domains), |  | ||||||
| and **node.js middleware systems**. |  | ||||||
| 
 |  | ||||||
| # Features |  | ||||||
| 
 |  | ||||||
| - [x] Automatic HTTPS |  | ||||||
|   - [x] Free SSL |  | ||||||
|   - [x] Free Wildcard SSL |  | ||||||
|   - [x] Multiple domain support (up to 100 altnames per SAN) |  | ||||||
|   - [x] Dynamic Virtual Hosting (vhost) |  | ||||||
|   - [x] Automatical renewal (10 to 14 days before expiration) |  | ||||||
| - [x] Great ACME support |  | ||||||
|   - [x] ACME draft 11 |  | ||||||
|   - [x] Let's Encrypt v2 |  | ||||||
|   - [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 |  | ||||||
| 
 |  | ||||||
| # Install |  | ||||||
| 
 |  | ||||||
| ```bash |  | ||||||
| npm install --save greenlock-express@2.x |  | ||||||
| ``` |  | ||||||
| 
 |  | ||||||
| # QuickStart |  | ||||||
| 
 |  | ||||||
| <!-- TODO better quickstart (fewer options) --> |  | ||||||
| 
 |  | ||||||
| ### Screencast |  | ||||||
| 
 |  | ||||||
| Watch the QuickStart demonstration: [https://youtu.be/e8vaR4CEZ5s](https://youtu.be/e8vaR4CEZ5s&list=PLZaEVINf2Bq_lrS-OOzTUJB4q3HxarlXk) |  | ||||||
| 
 |  | ||||||
| <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"; | ||||||
| 
 | 
 | ||||||
| var express = require("express"); | function httpsWorker(glx) { | ||||||
| var app = express(); | 	// Serves on 80 and 443 | ||||||
|  | 	// Get's SSL certificates magically! | ||||||
| 
 | 
 | ||||||
| app.use("/", function(req, res) { | 	glx.serveApp(function(req, res) { | ||||||
| 	res.setHeader("Content-Type", "text/html; charset=utf-8"); | 		res.end("Hello, Encrypted World!"); | ||||||
| 	res.end("Hello, World!\n\n💚 🔒.js"); | 	}); | ||||||
| }); | } | ||||||
| 
 | 
 | ||||||
| // Don't do this: | var pkg = require("./package.json"); | ||||||
| // app.listen(3000) | require("greenlock-express") | ||||||
|  | 	.init(function getConfig() { | ||||||
|  | 		// Greenlock Config | ||||||
| 
 | 
 | ||||||
| // Do this instead: | 		return { | ||||||
| module.exports = app; | 			package: { name: pkg.name, version: pkg.version }, | ||||||
|  | 			maintainerEmail: pkg.author, | ||||||
|  | 			cluster: false | ||||||
|  | 		}; | ||||||
|  | 	}) | ||||||
|  | 	.serve(httpsWorker); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### `communityMember` | Manage via API or the config file: | ||||||
| 
 | 
 | ||||||
| If you're the kind of person that likes the kinds of stuff that I do, | `~/.config/greenlock/manage.json`: (default filesystem config) | ||||||
| well, I want to do more of it and I'd like to get you involved. |  | ||||||
| 
 | 
 | ||||||
| As expected, by default we keep your email private and only use it for | ```json | ||||||
| transactional messaging, urgent security or API updates | { | ||||||
| (such as the mandatory upgrade to Let's Encrypt v2), and ACME account registration. | 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||||
| 
 | 	"agreeToTerms": true, | ||||||
| However, when you set the `communityMember` option to `true` we'll also | 	"sites": { | ||||||
| inform you when there are meaningful and relavant feature updates (no bugfix noise), | 		"example.com": { | ||||||
| and give you early access to similar projects. | 			"subject": "example.com", | ||||||
| 
 | 			"altnames": ["example.com", "www.example.com"] | ||||||
| You can see our full privacy policy at <https://greenlock.domains/legal/#privacy>. | 		} | ||||||
| 
 | 	} | ||||||
| ### What if the example didn't work? |  | ||||||
| 
 |  | ||||||
| Double check the following: |  | ||||||
| 
 |  | ||||||
| - **Public Facing IP** for `http-01` challenges |  | ||||||
|   - Are you running this _as_ a public-facing webserver (good)? or localhost (bad)? |  | ||||||
|   - Does `ifconfig` show a public address (good)? or a private one - 10.x, 192.168.x, etc (bad)? |  | ||||||
|   - 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 |  | ||||||
| 
 |  | ||||||
| ### Production vs Staging |  | ||||||
| 
 |  | ||||||
| If at first you don't succeed, stop and switch to staging. |  | ||||||
| 
 |  | ||||||
| There are a number of common problems related to system configuration - |  | ||||||
| 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 }); |  | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ```javascript | # Let's Encrypt for... | ||||||
| // 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()); |  | ||||||
| 	}); |  | ||||||
| 
 | 
 | ||||||
| var app = require("express")(); | - IoT | ||||||
| app.use("/", function(req, res) { | - Enterprise On-Prem | ||||||
| 	res.end("Hello, World!"); | - Local Development | ||||||
| }); | - Home Servers | ||||||
|  | - Quitting Heroku | ||||||
| 
 | 
 | ||||||
| // handles your app | # Features | ||||||
| require("https") | 
 | ||||||
| 	.createServer(glx.httpsOptions, app) | - [x] Let's Encrypt v2 (November 2019) | ||||||
| 	.listen(443, function() { |   - [x] ACME Protocol (RFC 8555) | ||||||
| 		console.log("Listening for ACME tls-sni-01 challenges and serve app on", this.address()); |   - [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] **Wildcard** SSL | ||||||
|  |   - [x] **Localhost** certificates | ||||||
|  |   - [x] HTTPS-enabled Secure **WebSockets** (`wss://`) | ||||||
|  | - [x] Fully customizable | ||||||
|  |   - [x] **Reasonable defaults** | ||||||
|  |   - [x] Domain Management | ||||||
|  |   - [x] Key and Certificate Management | ||||||
|  |   - [x] ACME Challenge Plugins | ||||||
|  | 
 | ||||||
|  | # QuickStart Guide | ||||||
|  | 
 | ||||||
|  | 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 | ||||||
|  | mkdir ~/my-project | ||||||
|  | pushd ~/my-project | ||||||
|  | npm init | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| **Security**: | </details> | ||||||
| 
 | 
 | ||||||
| Greenlock will do a self-check on all domain registrations | <details> | ||||||
| to prevent you from hitting rate limits. | <summary>2. Create an http app (i.e. express)</summary> | ||||||
| 
 | 
 | ||||||
| # API | ## 2. Create an http app (i.e. express) | ||||||
| 
 | 
 | ||||||
| This module is an elaborate ruse (to provide an oversimplified example and to nab some SEO). | This example is shown with Express, but any node app will do. Greenlock | ||||||
|  | works with everything. | ||||||
|  | (or any node-style http app) | ||||||
| 
 | 
 | ||||||
| The API is actually located at [greenlock.js options](https://git.rootprojects.org/root/greenlock.js) | `my-express-app.js`: | ||||||
| (because all options are simply passed through to `greenlock.js` proper without modification). |  | ||||||
| 
 | 
 | ||||||
| The only "API" consists of two options, the rest is just a wrapper around `greenlock.js` to take LOC from 15 to 5: | ```js | ||||||
|  | "use strict"; | ||||||
| 
 | 
 | ||||||
| - `opts.app` An express app in the format `function (req, res) { ... }` (no `next`). | // A plain, node-style app | ||||||
| - `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')` |  | ||||||
| 
 | 
 | ||||||
| Brief overview of some simple options for `greenlock.js`: | function myPlainNodeHttpApp(req, res) { | ||||||
|  | 	res.end("Hello, Encrypted World!"); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| - `opts.server` set to https://acme-v02.api.letsencrypt.org/directory in production | // Wrap that plain app in express, | ||||||
| - `opts.version` set to `v01` for Let's Encrypt v1 or `draft-11` for Let's Encrypt v2 (mistakenly called ACME v2) | // because that's what you're used to | ||||||
| - `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. |  | ||||||
| 
 | 
 | ||||||
| ## Supported ACME versions | var express = require("express"); | ||||||
|  | var app = express(); | ||||||
|  | app.get("/", myPlainNodeHttpApp); | ||||||
| 
 | 
 | ||||||
| - Let's Encrypt v1 (aka v01) | // export the app normally | ||||||
| - Let's Encrypt v2 (aka v02 or ACME draft 11) | // do not .listen() | ||||||
| - ACME draft 11 (ACME v2 is a misnomer) |  | ||||||
| - Wildcard domains (via dns-01 challenges) |  | ||||||
|   - `*.example.com` |  | ||||||
| 
 | 
 | ||||||
| <small>tags: letsencrypt acme free ssl automated https node express.js</small> | module.exports = app; | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
|  | <details> | ||||||
|  | <summary>3. Serve with Greenlock Express</summary> | ||||||
|  | 
 | ||||||
|  | ## 3. Serve with Greenlock Express | ||||||
|  | 
 | ||||||
|  | Greenlock Express is designed with these goals in mind: | ||||||
|  | 
 | ||||||
|  | - Simplicity and ease-of-use | ||||||
|  | - Performance and scalability | ||||||
|  | - Configurability and control | ||||||
|  | 
 | ||||||
|  | You can start with **near-zero configuration** and | ||||||
|  | slowly add options for greater performance and customization | ||||||
|  | later, if you need them. | ||||||
|  | 
 | ||||||
|  | `server.js`: | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | require("greenlock-express") | ||||||
|  | 	.init(getConfig) | ||||||
|  | 	.serve(worker); | ||||||
|  | 
 | ||||||
|  | function getConfig() { | ||||||
|  | 	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) { | ||||||
|  | 	// Works with any Node app (Express, etc) | ||||||
|  | 	var app = require("my-express-app.js"); | ||||||
|  | 	server.serveApp(app); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | And start your server: | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # Allow non-root node to use ports 80 (HTTP) and 443 (HTTPS) | ||||||
|  | sudo setcap 'cap_net_bind_service=+ep' $(which node) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | # `npm start` will call `node ./server.js` by default | ||||||
|  | npm start | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```txt | ||||||
|  | Greenlock v3.0.0 | ||||||
|  | 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 | ||||||
|  | Listening on 0.0.0.0:443 for secure traffic | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | </details> | ||||||
|  | 
 | ||||||
|  | <details> | ||||||
|  | <summary>4. Manage SSL Certificates and Domains</summary> | ||||||
|  | 
 | ||||||
|  | ## 4. Manage domains | ||||||
|  | 
 | ||||||
|  | The management API is built to work with Databases, S3, etc. | ||||||
|  | 
 | ||||||
|  | HOWEVER, by default it starts with a simple config file. | ||||||
|  | 
 | ||||||
|  | <!-- | ||||||
|  | This will update the config file (assuming the default fs-based management plugin): | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | `~/.config/greenlock/manager.json`: | ||||||
|  | 
 | ||||||
|  | ```json | ||||||
|  | { | ||||||
|  | 	"subscriberEmail": "letsencrypt-test@therootcompany.com", | ||||||
|  | 	"agreeToTerms": true, | ||||||
|  | 	"sites": { | ||||||
|  | 		"example.com": { | ||||||
|  | 			"subject": "example.com", | ||||||
|  | 			"altnames": ["example.com", "www.example.com"] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | COMING SOON | ||||||
|  | 
 | ||||||
|  | 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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								demo.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,35 @@ | |||||||
|  | "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!"); | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								examples/cluster/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								examples/cluster/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | "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); | ||||||
|  | } | ||||||
| @ -1,75 +0,0 @@ | |||||||
| "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); |  | ||||||
							
								
								
									
										27
									
								
								examples/express/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								examples/express/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,27 @@ | |||||||
|  | "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); | ||||||
| @ -1,30 +0,0 @@ | |||||||
| "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); |  | ||||||
							
								
								
									
										44
									
								
								examples/http-proxy/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								examples/http-proxy/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | "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); | ||||||
							
								
								
									
										42
									
								
								examples/http/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								examples/http/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | "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); | ||||||
| @ -1,70 +0,0 @@ | |||||||
| "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); |  | ||||||
							
								
								
									
										48
									
								
								examples/http2/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								examples/http2/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | |||||||
|  | "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); | ||||||
							
								
								
									
										49
									
								
								examples/https/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								examples/https/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | "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); | ||||||
| @ -1,88 +0,0 @@ | |||||||
| "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); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @ -1,38 +0,0 @@ | |||||||
| "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); |  | ||||||
							
								
								
									
										22
									
								
								examples/quickstart/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								examples/quickstart/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | # 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"] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
							
								
								
									
										32
									
								
								examples/quickstart/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								examples/quickstart/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | |||||||
|  | "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); | ||||||
| @ -1,104 +0,0 @@ | |||||||
| "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; |  | ||||||
| } |  | ||||||
| @ -1,32 +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"; |  | ||||||
| 
 |  | ||||||
| //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); |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
							
								
								
									
										49
									
								
								examples/socket.io/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								examples/socket.io/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | |||||||
|  | // 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); | ||||||
| @ -1,64 +0,0 @@ | |||||||
| "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); |  | ||||||
							
								
								
									
										3
									
								
								examples/spdy/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								examples/spdy/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | // 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+
 | ||||||
| @ -1,134 +0,0 @@ | |||||||
| #!/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(); |  | ||||||
| 		}); |  | ||||||
| } |  | ||||||
| @ -1,46 +0,0 @@ | |||||||
| "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); |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
							
								
								
									
										42
									
								
								examples/websockets/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								examples/websockets/server.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | "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); | ||||||
| @ -1,77 +0,0 @@ | |||||||
| #!/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); |  | ||||||
| } |  | ||||||
							
								
								
									
										44
									
								
								greenlock-express.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								greenlock-express.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | |||||||
|  | "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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								greenlock.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | |||||||
|  | "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; | ||||||
|  | } | ||||||
							
								
								
									
										106
									
								
								http-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								http-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,106 @@ | |||||||
|  | "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) || ""; | ||||||
|  | }; | ||||||
							
								
								
									
										139
									
								
								https-middleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								https-middleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,139 @@ | |||||||
|  | "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
									
									
									
									
									
								
							
							
						
						
									
										334
									
								
								index.js
									
									
									
									
									
								
							| @ -1,334 +0,0 @@ | |||||||
| "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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								main.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | "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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								master.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | |||||||
|  | "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); | ||||||
|  | }; | ||||||
							
								
								
									
										684
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										684
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,507 +1,128 @@ | |||||||
| { | { | ||||||
| 	"name": "greenlock-express", | 	"name": "@root/greenlock-express", | ||||||
| 	"version": "2.7.18", | 	"version": "3.0.7", | ||||||
| 	"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.3.11", | 			"version": "1.4.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz", | 			"resolved": "https://registry.npmjs.org/@root/request/-/request-1.4.1.tgz", | ||||||
| 			"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw==" | 			"integrity": "sha512-2zSP1v9VhJ3gvm4oph0C4BYCoM3Sj84/Wx4iKdt0IbqbJzfON04EodBq5dsV65UxO/aHZciUBwY2GCZcHqaTYg==" | ||||||
| 		}, | 		}, | ||||||
| 		"accepts": { | 		"@root/x509": { | ||||||
| 			"version": "1.3.5", | 			"version": "0.7.2", | ||||||
| 			"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", | 			"resolved": "https://registry.npmjs.org/@root/x509/-/x509-0.7.2.tgz", | ||||||
| 			"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", | 			"integrity": "sha512-ENq3LGYORK5NiMFHEVeNMt+fTXaC7DTS6sQXoqV+dFdfT0vmiL5cDLjaXQhaklJQq0NiwicZegzJRl1ZOTp3WQ==", | ||||||
| 			"dev": true, |  | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"mime-types": "~2.1.18", | 				"@root/asn1": "^1.0.0", | ||||||
| 				"negotiator": "0.6.1" | 				"@root/encoding": "^1.0.1" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"acme": { | 		"acme-http-01-standalone": { | ||||||
| 			"version": "1.3.5", | 			"version": "3.0.5", | ||||||
| 			"resolved": "https://registry.npmjs.org/acme/-/acme-1.3.5.tgz", | 			"resolved": "https://registry.npmjs.org/acme-http-01-standalone/-/acme-http-01-standalone-3.0.5.tgz", | ||||||
| 			"integrity": "sha512-KIFVyMho7y3RxRSTzkuX031TmfXwzl0ioy8+r2pnfLz6YWFQ5q7a/cYUDTgIbrFMPe/syY26Qv1DOdHQ5ARWcw==", | 			"integrity": "sha512-W4GfK+39GZ+u0mvxRVUcVFCG6gposfzEnSBF20T/NUwWAKG59wQT1dUbS1NixRIAsRuhpGc4Jx659cErFQH0Pg==" | ||||||
| 			"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=" | ||||||
| 		}, | 		}, | ||||||
| 		"etag": { | 		"greenlock-manager-fs": { | ||||||
| 			"version": "1.8.1", | 			"version": "3.0.1", | ||||||
| 			"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", | 			"resolved": "https://registry.npmjs.org/greenlock-manager-fs/-/greenlock-manager-fs-3.0.1.tgz", | ||||||
| 			"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", | 			"integrity": "sha512-vZfGFq1TTKxaAqdGDUwNservrNzXx0xCwT/ovG/N378GrhS+U5S8B8LUlNtQU7Fdw6RToMiBcm22OOxSrvZ2zw==", | ||||||
| 			"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": { | 			"requires": { | ||||||
| 				"accepts": "~1.3.5", | 				"@root/mkdirp": "^1.0.0", | ||||||
| 				"array-flatten": "1.1.1", | 				"safe-replace": "^1.1.0" | ||||||
| 				"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": { | 		"greenlock-store-fs": { | ||||||
| 			"version": "3.0.2", | 			"version": "3.2.0", | ||||||
| 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.0.2.tgz", | 			"resolved": "https://registry.npmjs.org/greenlock-store-fs/-/greenlock-store-fs-3.2.0.tgz", | ||||||
| 			"integrity": "sha512-t4So75yKs1+7TqmxD5UKdf+zOQU0/4o0lb2auf5zUcAo7fwwNLOAXyWnnZRL3WuFBUiBGh1qXWleuMua0d3LPg==", | 			"integrity": "sha512-zqcPnF+173oYq5qU7FoGtuqeG8dmmvAiSnz98kEHAHyvgRF9pE1T0MM0AuqDdj45I3kXlCj2gZBwutnRi37J3g==", | ||||||
| 			"requires": { | 			"requires": { | ||||||
| 				"@root/mkdirp": "^1.0.0", | 				"@root/mkdirp": "^1.0.0", | ||||||
| 				"safe-replace": "^1.1.0" | 				"safe-replace": "^1.1.0" | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		"http-errors": { |  | ||||||
| 			"version": "1.6.3", |  | ||||||
| 			"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", |  | ||||||
| 			"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": { |  | ||||||
| 				"@root/mkdirp": "^1.0.0", |  | ||||||
| 				"pyconf": "^1.1.7", |  | ||||||
| 				"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", | ||||||
| @ -510,133 +131,10 @@ | |||||||
| 				"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,24 +1,26 @@ | |||||||
| { | { | ||||||
| 	"name": "greenlock-express", | 	"name": "@root/greenlock-express", | ||||||
| 	"version": "2.7.18", | 	"version": "3.0.10", | ||||||
| 	"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": "index.js", | 	"main": "greenlock-express.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": { | ||||||
| 		"greenlock": "^2.8.8", | 		"@root/greenlock": "^3.0.17", | ||||||
| 		"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", | ||||||
| @ -27,10 +29,7 @@ | |||||||
| 		"serve-static": "^1.13.2", | 		"serve-static": "^1.13.2", | ||||||
| 		"ws": "^5.2.1" | 		"ws": "^5.2.1" | ||||||
| 	}, | 	}, | ||||||
| 	"scripts": { | 	"devDependencies": {}, | ||||||
| 		"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
									
									
									
									
									
								
							
							
						
						
									
										325
									
								
								server.js
									
									
									
									
									
								
							| @ -1,325 +0,0 @@ | |||||||
| #!/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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								servers.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | |||||||
|  | "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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								single.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | "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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								sni.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,194 @@ | |||||||
|  | "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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								worker.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | |||||||
|  | "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