From 74492e3f6b95539d15325c3263b00b34a4623baf Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Fri, 29 Jun 2018 15:47:19 +0200 Subject: [PATCH] FirstCommit --- .dockerignore | 1 + .eslintrc | 22 + .npmignore | 0 .travis.yml | 4 + .vscode/launch.json | 30 + Dockerfile | 6 + LICENSE | 24 + README.md | 564 +++++++++++++ gulpfile.js | 36 + hl-tests/64/proxy.js | 52 ++ hl-tests/letsencrypt/a.js | 36 + .../meta.json | 1 + .../private_key.json | 1 + .../regr.json | 1 + .../meta.json | 1 + .../private_key.json | 1 + .../regr.json | 1 + .../letsencrypt/certs/api.com/privkey.pem | 27 + hl-tests/letsencrypt/certs/api/privkey.pem | 27 + .../archive/caturra.exactbytes.com/cert0.pem | 29 + .../archive/caturra.exactbytes.com/chain0.pem | 27 + .../caturra.exactbytes.com/fullchain0.pem | 56 ++ .../caturra.exactbytes.com/privkey0.pem | 27 + .../certs/caturra.exactbytes.com/cert.pem | 29 + .../certs/caturra.exactbytes.com/chain.pem | 27 + .../caturra.exactbytes.com/fullchain.pem | 56 ++ .../certs/caturra.exactbytes.com/privkey.pem | 27 + .../caturra.exactbytes.com/privkey.pem.bak | 27 + .../letsencrypt/certs/dash.com/privkey.pem | 27 + .../certs/dash/.well-known/acme-challenge/abc | 1 + hl-tests/letsencrypt/certs/dash/privkey.pem | 27 + hl-tests/letsencrypt/certs/dash_/privkey.pem | 27 + hl-tests/letsencrypt/certs/dev-cert.pem | 17 + hl-tests/letsencrypt/certs/dev-csr.pem | 13 + hl-tests/letsencrypt/certs/dev-key.pem | 15 + .../letsencrypt/certs/example.com/privkey.pem | 27 + .../letsencrypt/certs/localhost/privkey.pem | 27 + .../certs/renewal/caturra.exactbytes.com.conf | 67 ++ .../renewal/caturra.exactbytes.com.conf.bak | 68 ++ hl-tests/letsencrypt/proxy.js | 73 ++ hl-tests/paths.js | 19 + index.js | 5 + lib/docker.js | 162 ++++ lib/etcd-backend.js | 93 +++ lib/letsencrypt.js | 137 +++ lib/proxy.js | 780 ++++++++++++++++++ lib/redis-backend.js | 109 +++ package.json | 75 ++ test/test_custom_resolver.js | 219 +++++ test/test_hostheader.js | 141 ++++ test/test_pathnames.js | 75 ++ test/test_register.js | 421 ++++++++++ 52 files changed, 3765 insertions(+) create mode 100644 .dockerignore create mode 100644 .eslintrc create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 .vscode/launch.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 gulpfile.js create mode 100644 hl-tests/64/proxy.js create mode 100644 hl-tests/letsencrypt/a.js create mode 100644 hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/meta.json create mode 100644 hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/private_key.json create mode 100644 hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/regr.json create mode 100644 hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/meta.json create mode 100644 hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/private_key.json create mode 100644 hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/regr.json create mode 100644 hl-tests/letsencrypt/certs/api.com/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/api/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/cert0.pem create mode 100644 hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/chain0.pem create mode 100644 hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/fullchain0.pem create mode 100644 hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/privkey0.pem create mode 100644 hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem create mode 100644 hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem create mode 100644 hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem create mode 100644 hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem.bak create mode 100644 hl-tests/letsencrypt/certs/dash.com/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/dash/.well-known/acme-challenge/abc create mode 100644 hl-tests/letsencrypt/certs/dash/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/dash_/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/dev-cert.pem create mode 100644 hl-tests/letsencrypt/certs/dev-csr.pem create mode 100644 hl-tests/letsencrypt/certs/dev-key.pem create mode 100644 hl-tests/letsencrypt/certs/example.com/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/localhost/privkey.pem create mode 100644 hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf create mode 100644 hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf.bak create mode 100644 hl-tests/letsencrypt/proxy.js create mode 100644 hl-tests/paths.js create mode 100644 index.js create mode 100644 lib/docker.js create mode 100644 lib/etcd-backend.js create mode 100644 lib/letsencrypt.js create mode 100644 lib/proxy.js create mode 100644 lib/redis-backend.js create mode 100644 package.json create mode 100644 test/test_custom_resolver.js create mode 100644 test/test_hostheader.js create mode 100644 test/test_pathnames.js create mode 100644 test/test_register.js diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..b249e15 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,22 @@ +rules: + space-before-blocks: [2, "never"] + space-after-keywords: [2, "never"] + new-cap: 0 + no-underscore-dangle: 0 + indent: [2, 2] + brace-style: [2, "1tbs"] + comma-style: [2, "last"] + default-case: 2 + func-style: [2, "declaration"] + guard-for-in: 2 + no-floating-decimal: 2 + no-nested-ternary: 2 + no-undefined: 2 + radix: 2 + space-after-function-name: [2, "never"] + space-after-keywords: [2, "always"] + space-before-blocks: [2, "never"] + spaced-line-comment: [2, "always", { exceptions: ["-"]}] + valid-jsdoc: [2, { prefer: { "return": "returns"}}] + wrap-iife: 2 + quotes: [2, "single"] diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..e69de29 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d77e39c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: node_js + +node_js: + - '4.5' diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..76f858a --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "node", + "request": "launch", + "program": "hl-tests/paths.js", + "stopOnEntry": false, + "args": [], + "cwd": ".", + "runtimeExecutable": null, + "runtimeArgs": [ + "--nolazy" + ], + "env": { + "NODE_ENV": "development" + }, + "externalConsole": false, + "sourceMaps": false, + "outDir": null + }, + { + "name": "Attach", + "type": "node", + "request": "attach", + "port": 5858 + } + ] +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..30e3629 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM node:4.5 + +ADD . /proxy +RUN cd /proxy; npm install --production +EXPOSE 8080 + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e4e7d91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2014, OptimalBits +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..583aa75 --- /dev/null +++ b/README.md @@ -0,0 +1,564 @@ +# Redbird Reverse Proxy + +## With built in Cluster, HTTP2, [LetsEncrypt](https://letsencrypt.org/) and [Docker](https://www.docker.com/) support + + +![redbird](http://cliparts.co/cliparts/6cr/o9d/6cro9dRzi.jpg) + +Handling dynamic virtual hosts, load balancing, proxying web sockets and SSL encryption should be +easy and robust. + +With redbird you get a complete library to build dynamic reverse proxies with the speed and robustness of http-proxy. + +This light-weight package includes everything you need for easy reverse routing of your applications. +Great for routing many applications from different domains in one single host, handling SSL with ease, etc. + +Developed by [manast](http://twitter.com/manast) + +[![BuildStatus](https://secure.travis-ci.org/OptimalBits/redbird.png?branch=master)](http://travis-ci.org/OptimalBits/redbird) +[![NPM version](https://badge.fury.io/js/redbird.svg)](http://badge.fury.io/js/redbird) + +## SUPER HOT + +Support for HTTP2. You can now enable HTTP2 just by setting the HTTP2 flag to true. Keep in mind that HTTP2 requires +SSL/TLS certificates. Thankfully we also support LetsEncrypt so this becomes easy as pie. + +## HOT + +We have now support for automatic generation of SSL certificates using [LetsEncrypt](#letsencrypt). Zero config setup for your +TLS protected services that just works. + +## Features + +- Flexible and easy routing +- Websockets +- Seamless SSL Support (HTTPS -> HTTP proxy) +- Automatic HTTP to HTTPS redirects +- Automatic TLS Certificates generation and renewal +- Load balancer +- Register and unregister routes programatically without restart (allows zero downtime deployments) +- Docker support for automatic registration of running containers +- Cluster support that enables automatic multi-process +- Based on top of rock-solid node-http-proxy and battle tested on production in many sites +- Optional logging based on bunyan + +## Install + + +```sh +npm install redbird +``` + +## Example + + +You can programmatically register or unregister routes dynamically even if the proxy is already running: + +```js +var proxy = require('redbird')({port: 80}); + +// OPTIONAL: Setup your proxy but disable the X-Forwarded-For header +var proxy = require('redbird')({port: 80, xfwd: false}); + +// Route to any global ip +proxy.register("optimalbits.com", "http://167.23.42.67:8000"); + +// Route to any local ip, for example from docker containers. +proxy.register("example.com", "http://172.17.42.1:8001"); + +// Route from hostnames as well as paths +proxy.register("example.com/static", "http://172.17.42.1:8002"); +proxy.register("example.com/media", "http://172.17.42.1:8003"); + +// Subdomains, paths, everything just works as expected +proxy.register("abc.example.com", "http://172.17.42.4:8080"); +proxy.register("abc.example.com/media", "http://172.17.42.5:8080"); + +// Route to any href including a target path +proxy.register("foobar.example.com", "http://172.17.42.6:8080/foobar"); + +// You can also enable load balancing by registering the same hostname with different +// target hosts. The requests will be evenly balanced using a Round-Robin scheme. +proxy.register("balance.me", "http://172.17.40.6:8080"); +proxy.register("balance.me", "http://172.17.41.6:8080"); +proxy.register("balance.me", "http://172.17.42.6:8080"); +proxy.register("balance.me", "http://172.17.43.6:8080"); + + +// LetsEncrypt support +// With Redbird you can get zero conf and automatic SSL certificates for your domains +redbird.register('example.com', 'http://172.60.80.2:8082', { + ssl: { + letsencrypt: { + email: 'john@example.com', // Domain owner/admin email + production: true, // WARNING: Only use this flag when the proxy is verified to work correctly to avoid being banned! + } + } +}); + +// +// LetsEncrypt requires a minimal web server for handling the challenges, this is by default on port 3000 +// it can be configured when initiating the proxy. This web server is only used by Redbird internally so most of the time +// you do not need to do anything special other than avoid having other web services in the same host running +// on the same port. + +// +// HTTP2 Support using LetsEncrypt for the certificates +// +var proxy = require('redbird')({ + letsencrypt: { + path: __dirname + '/certs', + port:9999 + }, + ssl: { + http2: true, + } +}); + +``` +## About HTTPS + +The HTTPS proxy supports virtual hosts by using SNI (which most modern browsers support: IE7 and above). +The proxying is performed by hostname, so you must use the same SSL certificates for a given hostname independently of its paths. + +### LetsEncrypt + +Some important considerations when using LetsEncrypt. You need to agree to LetsEncrypt [terms of service](https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf). When using +LetsEncrypt, the obtained certificates will be copied to disk to the specified path. Its your responsibility to backup, or save persistently when applicable. Keep in mind that +these certificates needs to be handled with care so that they cannot be accessed by malicious users. The certificates will be renewed every +2 months automatically forever. + +## HTTPS Example + +(NOTE: This is a legacy example not needed when using LetsEncrypt) + +Conceptually HTTPS is easy, but it is also easy to struggle getting it right. With Redbird its straightforward, check this complete example: + +1) Generate a localhost development SSL certificate: + +```sh +/certs $ openssl genrsa -out dev-key.pem 1024 +/certs $ openssl req -new -key dev-key.pem -out dev-csr.pem + +// IMPORTANT: Do not forget to fill the field! Common Name (e.g. server FQDN or YOUR name) []:localhost + +/certs $ openssl x509 -req -in dev-csr.pem -signkey dev-key.pem -out dev-cert.pem + +``` + +Note: For production sites you need to buy valid SSL certificates from a trusted authority. + +2) Create a simple redbird based proxy: + +```js +var redbird = new require('redbird')({ + port: 8080, + + // Specify filenames to default SSL certificates (in case SNI is not supported by the + // user's browser) + ssl: { + port: 8443, + key: "certs/dev-key.pem", + cert: "certs/dev-cert.pem", + } +}); + +// Since we will only have one https host, we dont need to specify additional certificates. +redbird.register('localhost', 'http://localhost:8082', {ssl: true}); +``` + +3) Test it: + +Point your browser to ```localhost:8000``` and you will see how it automatically redirects to your https server and proxies you to +your target server. + + +You can define many virtual hosts, each with its own SSL certificate. And if you do not define any, they will use the default one +as in the example above: + +```js +redbird.register('example.com', 'http://172.60.80.2:8082', { + ssl: { + key: "../certs/example.key", + cert: "../certs/example.crt", + ca: "../certs/example.ca" + } +}); + +redbird.register('foobar.com', 'http://172.60.80.3:8082', { + ssl: { + key: "../certs/foobar.key", + cert: "../certs/foobar.crt", + } +}); +``` + +You can also specify https hosts as targets and also specify if you want the connection to the target host to be secure (default is true). + +```js +var redbird = require('redbird')({ + port: 80, + secure: false, + ssl: { + port: 443, + key: "../certs/default.key", + cert: "../certs/default.crt", + } +}); +redbird.register('tutorial.com', 'https://172.60.80.2:8083', { + ssl: { + key: "../certs/tutorial.key", + cert: "../certs/tutorial.crt", + } +}); + +``` + +Edge case scenario: you have an HTTPS server with two IP addresses assigned to it and your clients use old software without SNI support. In this case, both IP addresses will receive the same fallback certificate. I.e. some of the domains will get a wrong certificate. To handle this case you can create two HTTPS servers each one bound to its own IP address and serving the appropriate certificate. + +```js +var redbird = new require('redbird')({ + port: 8080, + + // Specify filenames to default SSL certificates (in case SNI is not supported by the + // user's browser) + ssl: [ + { + port: 443, + ip: '123.45.67.10', // assigned to tutorial.com + key: 'certs/tutorial.key', + cert: 'certs/tutorial.crt', + }, + { + port: 443, + ip: '123.45.67.11', // assigned to my-other-domain.com + key: 'certs/my-other-domain.key', + cert: 'certs/my-other-domain.crt', + } + ] +}); + +// These certificates will be served if SNI is supported +redbird.register('tutorial.com', 'http://192.168.0.10:8001', { + ssl: { + key: 'certs/tutorial.key', + cert: 'certs/tutorial.crt', + } +}); +redbird.register('my-other-domain.com', 'http://192.168.0.12:8001', { + ssl: { + key: 'certs/my-other-domain.key', + cert: 'certs/my-other-domain.crt', + } +}); +``` + +## Docker support +If you use docker, you can tell Redbird to automatically register routes based on image +names. You register your image name and then every time a container starts from that image, +it gets registered, and unregistered if the container is stopped. If you run more than one +container from the same image, Redbird will load balance following a round-robin algorithm: + +```js +var redbird = require('redbird')({ + port: 8080, +}); + +var docker = require('redbird').docker; +docker(redbird).register("old.api.com", 'company/api:v1.0.0'); +docker(redbird).register("stable.api.com", 'company/api:v2.*'); +docker(redbird).register("preview.api.com", 'company/api:v[3-9].*'); +``` + +## etcd backend +Redbird can use [node-etcd](https://github.com/stianeikeland/node-etcd) to automatically create proxy records from an etcd cluster. Configuration +is accomplished by passing an array of [options](https://github.com/stianeikeland/node-etcd#constructor-options), plus the hosts and path variables, +which define which etcd cluster hosts, and which directory within those hosts, that Redbird should poll for updates. + +```js +var redbird = require('redbird')({ + port:8080 +}); + +var options = { + hosts: ['localhost:2379'], // REQUIRED - you must define array of cluster hosts + path: ['redbird'], // OPTIONAL - path to etcd keys + ... // OPTIONAL - pass in node-etcd connection options +} +require('redbird').etcd(redbird,options); +``` +etcd records can be created in one of two ways, either as a target destination pair: +```/redbird/example.com "8.8.8.8"``` +or by passing a JSON object containing multiple hosts, and Redbird options: +``` +/redbird/derek.com { "hosts" : ["10.10.10.10", "11.11.11.11"]} +/redbird/johnathan.com { "ssl" : true } +/redbird/jeff.com { "docker" : "alpine/alpine:latest" } +``` + + +## Cluster support +Redbird support automatic support for node cluster. Just specify in the options object +the number of processes that you want Redbird to use. Redbird will automatically re-start +any thread thay may crash automatically, increasing even more its reliability. + +```js +var redbird = new require('redbird')({ + port: 8080, + cluster: 4 +}); +``` + +## NTLM support +If you need NTLM support, you can tell Redbird to add the required header handler. This +registers a response handler which makes sure the NTLM auth header is properly split into +two entries from http-proxy. + +```js +var redbird = new require('redbird')({ + port: 8080, + ntlm: true +}); +``` + +## Custom Resolvers + +With custom resolvers, you can decide how the proxy server handles request. Custom resolvers allow you to extend Redbird considerably. With custom resolvers, you can perform the following: + +- Do path-based routing +- Do wildcard domain routing. +- Use variable upstream servers based on availability, for example in conjunction with Etcd or any other service discovery platform. +- And more. + +Resolvers should be: + + 1. Be invokable function. The `this` context of such function is the Redbird Proxy object. The resolver function takes in two parameters : `host` and `url` + 2. Have a priority, resolvers with higher priorities are called before those of lower priorities. The default resolver, has a priority of 0. + 3. A resolver should return a route object or a string when matches it matches the parameters passed in. If string is returned, then it must be a valid upstream URL, if object, then the object must conform to the following: + +``` + { + url: string or array of string [required], when array, the urls will be load-balanced across. + path: path prefix for route, [optional], defaults to '/', + opts: {} // Redbird target options, see Redbird.register() [optional], + } +``` + +### Defining Resolvers + +Resolvers can be defined when initializing the proxy object with the `resolvers` parameter. An example is below: + +```javascript + // for every URL path that starts with /api/, send request to upstream API service + var customResolver1 = function(host, url) { + if(/^\/api\//.test(url)){ + return 'http://127.0.0.1:8888'; + } + }; + + // assign high priority + customResolver1.priority = 100; + + var proxy = new require('redbird')({ + port: 8080, + resolvers: [ + customResolver1, + // uses the same priority as default resolver, so will be called after default resolver + function(host, url) { + if(/\.example\.com/.test(host)){ + return 'http://127.0.0.1:9999' + } + }] + }) + +``` + +### Adding and Removing Resolvers at Runtime. + +You can add or remove resolvers at runtime, this is useful in situations where your upstream is tied to a service discovery service system. + +```javascript +var topPriority = function(host, url) { + return /app\.example\.com/.test(host) ? { + // load balanced + url: [ + 'http://127.0.0.1:8000', + 'http://128.0.1.1:9999' + ] + } : null; +}; + +topPriority.priority = 200; +proxy.addResolver(topPriority); + + +// remove top priority after 10 minutes, +setTimeout(function() { + proxy.removeResolver(topPriority); +}, 600000); +``` + +## Replacing the default HTTP/HTTPS server modules + +By passing `serverModule: module` or `ssl: {serverModule : module}` you can override the default http/https +servers used to listen for connections with another module. + +One application for this is to enable support for PROXY protocol: This is useful if you want to use a module like +[findhit-proxywrap](https://github.com/findhit/proxywrap) to enable support for the +[PROXY protocol](http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). + + +PROXY protocol is used in tools like HA-Proxy, and can be optionally enabled in Amazon ELB load balancers to pass the +original client IP when proxying TCP connections (similar to an X-Forwarded-For header, but for raw TCP). This is useful +if you want to run redbird on AWS behind an ELB load balancer, but have redbird terminate any HTTPS connections so you +can have SNI/Let's Encrypt/HTTP2support. With this in place Redbird will see the client's IP address rather +than the load-balancer's, and pass this through in an X-Forwarded-For header. + +````javascript +//Options for proxywrap. This means the proxy will also respond to regular HTTP requests without PROXY information as well. +proxy_opts = {strict: false}; +proxyWrap = require('findhit-proxywrap'); +var opts = { + port: process.env.HTTP_PORT, + serverModule = proxyWrap.proxy( require('http'), proxy_opts), + ssl: { + //Do this if you want http2: + http2: true, + serverModule = proxyWrap.proxy(require('spdy').server, proxy_opts), + //Do this if you only want regular https + // serverModule = proxyWrap.proxy( require('http'), proxy_opts), + port: process.env.HTTPS_PORT, + } +} + +// Create the proxy +var proxy = require('redbird')(opts); +```` + + +## Roadmap + +- Statistics (number of connections, load, response times, etc) +- CORS support. +- Rate limiter. +- Simple IP Filtering. +- Automatic routing via Redis. + +## Reference + +[constructor](#redbird) +[register](#register) +[unregister](#unregister) +[notFound](#notFound) +[close](#close) + + + +### Redbird(opts) + +This is the Proxy constructor. Creates a new Proxy and starts listening to +the given port. + +__Arguments__ + +``` + opts {Object} Options to pass to the proxy: + { + port: {Number} // port number that the proxy will listen to. + ssl: { // Optional SSL proxying. + port: {Number} // SSL port the proxy will listen to. + // Default certificates + key: keyPath, + cert: certPath, + ca: caPath // Optional. + redirect: true, // Disable HTTPS autoredirect to this route. + http2: false, //Optional, setting to true enables http2/spdy support + serverModule : require('https') // Optional, override the https server module used to listen for https or http2 connections. Default is require('https') or require('spdy') + } + bunyan: {Object} Bunyan options. Check [bunyan](https://github.com/trentm/node-bunyan) for info. + If you want to disable bunyan, just set this option to false. Keep in mind that + having logs enabled incours in a performance penalty of about one order of magnitude per request. + resolvers: {Function | Array} a list of custom resolvers. Can be a single function or an array of functions. See more details about resolvers above. + serverModule : {Module} Optional - Override the http server module used to listen for http connections. Default is require('http') + } +``` + +--------------------------------------- + + + +#### Redbird::register(src, target, opts) + +Register a new route. As soon as this method is called, the proxy will +start routing the sources to the given targets. + +__Arguments__ + +```javascript + src {String} {String|URL} A string or a url parsed by node url module. + Note that port is ignored, since the proxy just listens to one port. + + target {String|URL} A string or a url parsed by node url module. + opts {Object} route options: + examples: + {ssl : true} // Will use default ssl certificates. + {ssl: { + redirectPort: port, // optional https port number to be redirected if entering using http. + key: keyPath, + cert: certPath, + ca: caPath // optional + } + } +``` + +--------------------------------------- + + + +#### Redbird.unregister(src, [target]) + + Unregisters a route. After calling this method, the given route will not + be proxied anymore. + +__Arguments__ + +```javascript + src {String|URL} A string or a url parsed by node url module. + target {String|URL} A string or a url parsed by node url module. If not + specified, it will unregister all routes for the given source. +``` + +--------------------------------------- + + + +#### Redbird.notFound(callback) + + Gives Redbird a callback function with two parameters, the HTTP request + and response objects, respectively, which will be called when a proxy route is + not found. The default is +```javascript + function(req, res){ + res.statusCode = 404; + res.write('Not Found'); + res.end(); + }; +``` +. + +__Arguments__ + +```javascript + src {Function(req, res)} The callback which will be called with the HTTP + request and response objects when a proxy route is not found. +``` + +--------------------------------------- + + + +#### Redbird.close() + + Close the proxy stopping all the incoming connections. + +--------------------------------------- diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..b8c32a7 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,36 @@ +/*eslint-env node */ +'use strict'; + +var gulp = require('gulp'); +var eslint = require('gulp-eslint'); + +gulp.task('lint', function (){ + // Note: To have the process exit with an error code (1) on + // lint error, return the stream and pipe to failOnError last. + return gulp.src([ + './**/*.js', + '!./test/**', + '!./node_modules/**' + ]) + .pipe(eslint({ + rules: { + 'space-after-keywords': [2, 'never'], + indent: [2, 2], + 'valid-jsdoc': 0, + 'func-style': 0, + 'no-use-before-define': 0, + camelcase: 1, + 'no-unused-vars': 1, + 'no-alert': 1, + 'no-console': 1, + 'no-unused-expressions': 0, + 'consistent-return': 0 + }, + globals: { + 'define': true + } + })) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +}); + diff --git a/hl-tests/64/proxy.js b/hl-tests/64/proxy.js new file mode 100644 index 0000000..8f978ca --- /dev/null +++ b/hl-tests/64/proxy.js @@ -0,0 +1,52 @@ +'use strict'; + + +// If URL has/.well-known/, send request to upstream API service +var customResolver1 = function (host, url) { + if (/^\/.well-known\//.test(url)) { + return 'http://localhost:3000'; + } +}; + +// assign high priority +customResolver1.priority = 100; + +var proxy = new require('../../index.js')({ + port: 8080, + resolvers: [ + customResolver1 + ], + secure: true, + ssl: { port: 443 }, +}) + +proxy.register("www", "http://www.planetex.press:3000", {/* + ssl: { + key: "/home/planetex/ssl.key", + cert: "/home/planetex/ssl.cert", + } +*/}); +proxy.register("api", "http://api.planetex.press:3002", {/* + ssl: { + key: "/home/planetex/domains/api.planetex.press/ssl.key", + cert: "/home/planetex/domains/api.planetex.press/ssl.cert", + } +*/}); +proxy.register("dash", "http://dash.planetex.press:3001", {/* + ssl: { + key: "/home/planetex/domains/dash.planetex.press/ssl.key", + cert: "/home/planetex/domains/dash.planetex.press/ssl.cert", + } +*/}); + +var http = require('http'); + +http.createServer(function(req, res){ + + res.writeHead(200); + res.write(req.url); + res.end(); + + console.log(req.host); + +}).listen(3000); diff --git a/hl-tests/letsencrypt/a.js b/hl-tests/letsencrypt/a.js new file mode 100644 index 0000000..4e77221 --- /dev/null +++ b/hl-tests/letsencrypt/a.js @@ -0,0 +1,36 @@ +var spdy = require('spdy'), + fs = require('fs'), + path = require('path'); + +var options = { + // Private key + //key: fs.readFileSync(path.join(__dirname, "certs/dev-key.pem")), + //cert: fs.readFileSync(path.join(__dirname, "certs/dev-cert.pem")), + + // **optional** SPDY-specific options + spdy: { + protocols: [ 'h2', 'spdy/3.1', 'http/1.1' ], + plain: false, + + // **optional** + // Parse first incoming X_FORWARDED_FOR frame and put it to the + // headers of every request. + // NOTE: Use with care! This should not be used without some proxy that + // will *always* send X_FORWARDED_FOR + 'x-forwarded-for': true, + + connection: { + windowSize: 1024 * 1024, // Server's window size + + // **optional** if true - server will send 3.1 frames on 3.0 *plain* spdy + autoSpdy31: false + } + } +}; + +var server = spdy.createServer(options, function(req, res) { + res.writeHead(200); + res.end('hello world!'); +}); + +server.listen(3000); diff --git a/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/meta.json b/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/meta.json new file mode 100644 index 0000000..73d04da --- /dev/null +++ b/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/meta.json @@ -0,0 +1 @@ +{"creation_host":"Manuels-MacBook-Pro.local","creation_dt":"2016-08-30T15:22:20.371Z"} \ No newline at end of file diff --git a/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/private_key.json b/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/private_key.json new file mode 100644 index 0000000..7b98a3f --- /dev/null +++ b/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/private_key.json @@ -0,0 +1 @@ +{"kty":"RSA","n":"v7Q7VtM_s1obhDJrSef8oSZtT9-v91cHnqzeTyMf0-Sz3CjYXamzEz_v7ASMS4JsGoG1SeaDlk_GYMO4OyAUNTB5nUJSR5ImNPwNz0m6dmr42tqOQalGG20lbm0-ZA7UYrJes1WylLyeNE_sgMVkpI50f8GHfAbZlEsJz54Vt8jqv-DSbXXoK-PyuhvP0y-uMujJQs6cWvztgun_8xvAeR9EykxAhw-7n9h84P0j3zprARVI0JTMjKeZyJ14aupWLEZ350Hd_ryWz36D6dDQJgjWqHh6lvv36Md90KcR-CDB3I2qVHQT5kFJhUNVGK7-eJQs8C0APZvyHS9jynuX4Q","e":"AQAB","d":"iNfhG-OEL0T9K2rKR2GAZpCFq2Sjuc24NL51mswZ5in1cgz-Fi4TFISpgTLl6ujYvjsk6_HOsLeVhnFvy1Tk1-sYhPdYwJpFB8F9IiEhJ3LI3YDx11E8KEvLUn5M8SPc2-8zxpQ__AiAbhs3WdyOMSE3bBL74b8KBd9iy3-vRRa8SySu--eeR73LcAjWt6c-Mpt6mHBMT83sGQW-OXPAIDQK--vWXwEgZCgq-r6IwAyGcgV3IdTDu-XZwXyXviZ_D7c457YPN9bK4rZKlcW_FFoTEFN1Roq0UFmeIdOcYR6FeieEhICx3W09AvoLtvauPa8CPIzmH1kWLDNPViVigQ","p":"6ECuYDDcc2Z534pqB0tS8Pc89yE0jH1txMFEw-CpbUi8rykKGiEhqZq7xism0nBym2Zr3i6IhhEKRszQkNNR2fXA20L3OjRgDijRemzfO1-MU1m_PSlIQq9O85zB2F0GIvjLMXgvR_vgHNUOyutV0fWM2p8W1IHmPDds4OCGoyk","q":"004r_QTpQCdxWLS4zjVJgVikiUZkVPe8pQOFQmFjdv0C4V1_S23pQhdzrrdGB7gZ00-xeepK2MMHZiPPp-noMouRs7fmC3_-jOlBTe8R4mGjd5hHJZ_fAphyUiBy2uk971aPjiAQAoFlIk_VjLi-p464p14xFx-ls5eWQT1ZXfk","dp":"4xFp6u2aetDz0pP2-c6w9poiZtN2Fu0Cht0WKBPcUdZNc0tCby15ReLcNvE1cYUy57AJQh5op_q8-19_gji4y8ozlasxHxzZ1L4fn_wVfGz8OvmBuYBE_716CT93XdwFBegMcP856rzc7hN39PiE3VOfNJdZsMaXnMPrlSivOZE","dq":"NfoBCJgJkUbCEHvRvXMlPLJNDXf6xy2lda2Ji-ReyRVmd_UvQDBqZmShO187t1sS1cTEvDTaO7bOHAxHzkfU9ZxrcrImRIfEmXA4K1VHh0GTxUgT3IuTJxGUGmCJllwAYzQEZbTRAiLVl8c28MR8h0bQ6ogIGDUQWej-C9pFCtk","qi":"3eTbAKe0oiRQSs456A58jKac4UYS-VN6oGKHkROBq8w8YAYkhQI9jeTQbUm7N7g8bwEIruGUdyq-SXAb8WOS166GdEqB_5bhP9xJw1MLFVaerXVOrDYMCBB-hTKw3kY7SIDoCgVBP3-1GL-fhXbvz3-CdOprwDCf8JMExXcop8k"} \ No newline at end of file diff --git a/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/regr.json b/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/regr.json new file mode 100644 index 0000000..5a6bb75 --- /dev/null +++ b/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/regr.json @@ -0,0 +1 @@ +{"body":{"id":304846,"key":{"kty":"RSA","n":"v7Q7VtM_s1obhDJrSef8oSZtT9-v91cHnqzeTyMf0-Sz3CjYXamzEz_v7ASMS4JsGoG1SeaDlk_GYMO4OyAUNTB5nUJSR5ImNPwNz0m6dmr42tqOQalGG20lbm0-ZA7UYrJes1WylLyeNE_sgMVkpI50f8GHfAbZlEsJz54Vt8jqv-DSbXXoK-PyuhvP0y-uMujJQs6cWvztgun_8xvAeR9EykxAhw-7n9h84P0j3zprARVI0JTMjKeZyJ14aupWLEZ350Hd_ryWz36D6dDQJgjWqHh6lvv36Md90KcR-CDB3I2qVHQT5kFJhUNVGK7-eJQs8C0APZvyHS9jynuX4Q","e":"AQAB"},"contact":["mailto:manuel@optimalbits.com"],"agreement":"https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf","initialIp":"85.235.1.31","createdAt":"2016-08-30T15:22:19Z"}} \ No newline at end of file diff --git a/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/meta.json b/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/meta.json new file mode 100644 index 0000000..1e83483 --- /dev/null +++ b/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/meta.json @@ -0,0 +1 @@ +{"creation_host":"Manuels-MBP","creation_dt":"2016-09-18T14:25:30.785Z"} \ No newline at end of file diff --git a/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/private_key.json b/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/private_key.json new file mode 100644 index 0000000..5236a98 --- /dev/null +++ b/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/private_key.json @@ -0,0 +1 @@ +{"kty":"RSA","n":"1sfAvmeOBdJelhXoG0HUrSiiY2PORPkEz9cDnfEVW8-G0w_584kpJHsKm2ixhtmZnjR0lismnaaE9wsqGXCZQ0Gn5dTUkcVKqRyntLKUh1emMY2SjEfTorEZqh6mq-7fUr6NtJU24QEJHWkDFxPp6PsQA8FREcrDIr5gt50xgaK65FxJ4YWCHH8kfCBY9XG5lD8NKya6S9upJFWJtldj7Qplx4JTf-se2YXCqvhVNXGkU_f23ZO9zOt9aPWtEekcZLTHjwTMUhq64vOFV7YlIuUPvQJhLG9AXVszYKjrWak-B_Eq6E74onOyI7W3aT7YjRIZl1RmBnnKmBYgysr2ww","e":"AQAB","d":"0mEDOP4yLR2srJJ0sg4_hgVhWr1uVD0fK35O-qwk4bNbOu5RRO07MZKcBzH7gj0urbpv4JAP2Sg84cc7y4NxfGGZVhSsysRXp2J8GxE5T4DZN3yW6XWJpbiXjP1NAOiQM3qXTyVBhg__n6E296n32s_hFeyLvkO_9A5Kqk_9KB40AtLBmgD99apZo43cvjQWoQTha28vx6MdKSG7cMGbKEoAulCufjYFoUvV8CUETo_vg5Vjzj4rZ0HpyOIfOxjzfLlEBoEPWWSc5NWV4toFU7uimlFW1eTKduyhRwnj9PiJ9aOtmNvvDH20X6BTunCLXM6OKp51jd7M207O8VNg0Q","p":"85Sc7ZWMD5mgx7ggzEPtehpsV1q1M0iVVOZi0955IsU7Fpe4avq4-kBg-9PPJCFrMnNpZOmr9Xc0e84SuShkT_5hmTZH-d8f8aHRknbJHNKerzABfHYgUhza0bpYR0u5Lf77fiRTIFnsyFykotMCP3E10B4HM7bPxN-j9KumDm8","q":"4bs3-irM80lGIW8Q0OkOkcADJUh4HlnK-tL08wIEJ-2rXb_PfPZ3aeMMvZrDoDL2YZerlKRe3vj1ceCkuZaNsnawNUM480y8V6AE4cjhEWo7fSLmvnqmkU2HlTLgUkJKAJXKTCLl3zBNKi9fKpK57EiGgtjjqOT4W6TDU0pXBu0","dp":"8Km1I0jOydsQcEQMo8W5rRrOUMDep3zfjrLSkmMNbL1SVFAzdf-jJB7Xs_jigOBD-eTuDTaTIERXJrvE5Ax0kFTWOXrYQpmiBivL1NpoeoHfJ1hXH5HW_UplKTLkZgz7OebktQ1O1HgE6zIduIKjhetlL-t7Ui0du3b7l5LAzyE","dq":"EPW8EvO8SlsrBcAOh2O7UIAYvGhhfgZJFedbuBZisY1N3tFWiZELD82bW3ORVyv9DwASSCzBZAdYiaHTPo5tPwdj3dybHsyZKgw_0acCIgjVR2Wj6JPWh_xHP5J_AC8y2DBo7qeAlfBPG-hLQiucBIC-en5JPJtXfas3cb6YI1E","qi":"1GV96yL6GV5S7B3rj5TWJUuKpkgTyKe4Q4sOTWh1pTsQ2PVL1Vr--FKM2CT_PvuAf4jWcaZgZoEZiaBGoLAPWBXYjuvaqXZ1dtdNqb-tLhOVSZJ3vAzA-jPJ-OUwQ_-BXl2FGD66plaPK0EvbaufK5a0h8D_9oK8E2TXtdSMtJ0"} \ No newline at end of file diff --git a/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/regr.json b/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/regr.json new file mode 100644 index 0000000..0fccc33 --- /dev/null +++ b/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/regr.json @@ -0,0 +1 @@ +{"body":{"id":4407503,"key":{"kty":"RSA","n":"1sfAvmeOBdJelhXoG0HUrSiiY2PORPkEz9cDnfEVW8-G0w_584kpJHsKm2ixhtmZnjR0lismnaaE9wsqGXCZQ0Gn5dTUkcVKqRyntLKUh1emMY2SjEfTorEZqh6mq-7fUr6NtJU24QEJHWkDFxPp6PsQA8FREcrDIr5gt50xgaK65FxJ4YWCHH8kfCBY9XG5lD8NKya6S9upJFWJtldj7Qplx4JTf-se2YXCqvhVNXGkU_f23ZO9zOt9aPWtEekcZLTHjwTMUhq64vOFV7YlIuUPvQJhLG9AXVszYKjrWak-B_Eq6E74onOyI7W3aT7YjRIZl1RmBnnKmBYgysr2ww","e":"AQAB"},"contact":["mailto:manuel@optimalbits.com"],"agreement":"https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf","initialIp":"217.72.54.127","createdAt":"2016-09-18T14:25:30Z"}} \ No newline at end of file diff --git a/hl-tests/letsencrypt/certs/api.com/privkey.pem b/hl-tests/letsencrypt/certs/api.com/privkey.pem new file mode 100644 index 0000000..b4e258f --- /dev/null +++ b/hl-tests/letsencrypt/certs/api.com/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwx0q3noqTM8+/XURJxJ02oHxf8MdqY0M46VsCw7u6dXqeVF/ +K/piezQZ2ZKMmdyzHBnrifbkN6oaLsNuW1xEO12wKl3W6j4VlE9NR3CL7ZfmKrTA +EVR/ynW7KOlXvNnN5p6vkuPM4xCu7YgtxpDVzkebE33FuhcgzQdElKuUqdQm7BDB +FWV+S3W9UQyG01APIZ9MjM39NroYdz31U/tRTKwlffLpVGjphIhCivPUP++E/7LL +vB5JAYjJaoZy98JsQ/tvzQ1EsPCkWQaKhtfuPFOaUviLGp7F2NV+wHrzA+b88v8z +BNd2HunVSNp7GpFmBj+gv6syB2O5oBoxGBibgQIDAQABAoIBAFYK9c24qBDJUCjr +yE2nuPpnVX2XKOyNdEKrv5K82iUqncU0aFWXjHhyiHfHRdPQXPgmghWMWCYoEHXQ +30jQzpIzha2ZRl50VIXb1uOLQVnco7bvkMfTsKsy8f9fr75ren6aOikX5lG4GLxN +Uop/cpoOP9f/ngOrkV55NwgtBllBnOu1P9VoUR6gSdZ8i9b8UZT1FjzKpudYMWmT +pOR8k62OKeze7jnxrqAhL/WzpsNM+rwE2pTvjo+lP3zu4sJqdhNzOK2irkDYHqrf +rvdnkXlnMNCVhtD9qiU6cVijPwTU24hw7R1aY2qpwWdSm2tkw2oNnBD911X3H32E +26tGeHECgYEA8Cct9KSLUZeE+bllwI/uhIWRYpyH6Rn0FlAHIo/+Dfgl/QX9yTNk +GI8/pUJ+tYClfWPQq+hmUx28zsyUFhqoCEFJDRbORFphGkJsod4RMBNAZokr2kuv +J3qKt3DaX1QQXA+NI6gfg92sJYDv0gAJWrc5QhIVGbRuUypgMUcrjrUCgYEAz/0o +fvtwwMMOoyp3bS8oPnccffi74+0FI+mSccn5GS734SLe2yd6G0SiOmv9W7LGfZ8q +EjSfOZ1uknBBN8OyZ4BVZR9b1aOF5caTlfP3NsKOuQVUekHcrexyNWCuoyblw2VZ +r+ztp0FT+OtWyA2JDkBJBh7+ba4Flpr2No5pTR0CgYEAqwCa/pq0AZNMwq07QRS8 +GG0rivY+6MLsRX8StY+mrbfHBRZhEgWf/sTx4vEoXIGQVWrfyakgQ4rnSLHvuJWe +lNI3/DQDCDT688HcrJ39yyfKMbj3GufNfuUJJXocZMjtJUCFlaA/YJxV2hanrfcM +siXJhbxuffE2pc2E1VICOAkCgYBUmIOAIoUZ4jxx4TPyFNwpjAjqs+C4NA+DK92E +qsHGnHP1/ljmiof/z0qsuH+0bGKPdc2G2iBpLr9qkH32UIKf1nLlTnvryTcM3lfp +BfHnM/sZBjH2CBPaKfHKBCkD8y5A61gvVg7TmJ6vAAmsFNVKFpudAb46ni1ntF+w +kPwDgQKBgQDgR0Y4zWHD5HbmhRFs6SZl8WUF3UA4XXdsuyi3CoUDmxw3d7fTmqKb +twyFuVrlm6I5ArTx6IGyYwidFaZglKDeQnLCToarM8GcMipquJzd4tmlkZJb9pOi +cxJjNA6+D1uSGC2LW7gsXsAf/wJ6J7GPH2SzEiezhhgvALfujlX1Vw== +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/api/privkey.pem b/hl-tests/letsencrypt/certs/api/privkey.pem new file mode 100644 index 0000000..6eb2cbb --- /dev/null +++ b/hl-tests/letsencrypt/certs/api/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2xcbvINnnA0EuQo3vt6VZYl7Bcy078pybFnDe2Wo2BJTtoJp +n2k4xUUHibMuwxLCZC4iCcQY/4RzMLp3ZDcYvrlB9+THrx6xKusdOgdhktTuDTbA +F6f0W4EthfNpJPBGkwaI9JU8UvHcd9kukiLj9Kv7AbhTsQjGBHT+6PVqp8Gzkf75 +lVV4iQyzi1zexw2GMe1XQboykgPU7B1x8YIdV5J7vO7/TsLu/fRvCOy33g1kZ62m +ksMvp+L1WtYwLCVCGgwmMbLndNcGfpHHM82aZL1yJxBcrNgH2m7S3mgfaHlZxe5O +8WlwxGbVAPZiDaCFDF+4JXaLCvfG41ERRj0bTQIDAQABAoIBAF6tWMYZPw/3rD/O +g5KPG556T9iMwvAQ22upSsmrf9CH8vce2kgSL39IOl6uORoBpFGogfsYa/kXorO/ +ENMU4DOjWTen/QbXS5aRbdriz66lJ448R7yxTu6wHx0QuDJHRyhIHa0cRKpPbIe6 +Kd7rBvl3zIvMvRX3BaNtb676RzHgvSrkp4f8c5kCFZ8YHPKsEJml0mIRKW4py7R7 +ZwCNOX1Lnbb5z2ZeqCM2RkFgf+UeL40TpFTizYzCETXEWBUn1F37t2i4XM0rwQt1 +KCfyhKb7ylsvp6W5wlnZ5cBl80LLnllcpa+GA+u5/5+QCZ1I1jr3SbsBhmEoyco7 +DUywyp0CgYEA+L7GXjrDfFzvuPX8v8khEswpSAriE0LCcUCnQMQ3qup/zxzNjodU +Nr4VNGos3Y7+ZF/EoVluhY+cVAl8iTo5i06nWhkWYe7DCUzF4dk1qgXPCPSLyk/W +KN9PZI7xaCSuaWWC5CMqoflDNqQc7ocgCX7QhKztpJmW4gYEMyRhvlMCgYEA4Xrr +Iwbd/Zr7cle2VjuI73j6vMlY3REy9sM74CryldZJ4sYwqMHhxDWnaFNa/Zem46eF +8A7vflJiLpfnyUxwvVmB+ztVSalA9Q6rD1TEIqbKBmPFw5+oriVAm8QZf1UV+zlQ +4KqmzNNGcY8n4hAu72IoSHwA5se6jfhdeh2SS98CgYEAxOBCI0zBcsogFrXjgWxQ +mA8tUU8D1pjNS1QPzOxA3y9RT30NmRS1a8qQ//ZVYlsOMCW4fLhLCL08zyre/cIu +z3rGbEJU+9g9WDwClxoTJmoIjp73kX4VFC6DKSUWHwaBYPwuWCEZWi/uqe3E1Gnw +ynMr2QcB5HiH+ocmhc/y6O8CgYAi+CFHiWUcU9DzZs8MiKcwHJ8mcEOr5WL2Ckla +9s4wls9WsE4Tnh4ZhAi2kVbnRYHIhM6s8GQMP1Kiz0RPX9+MPjl+cTFE/07nsqKs ++gSBK0ThwM+HC1fpyjU+8ybRLK0ADV+RuGWuFoYyTnVtBf2BesOsmi65m/g+1GoK +6lMqGQKBgAD2RyL/KrbxKGMmqrkthYo4iw2/zAJ7XQ8yaP6OIuzW4pC/OeL52VZP +LjDsegmQeHD4VcCRB/jdtGFZFmGlhtkzQ2iX5+Vbrt6udP80X5CrrK51k0d6LSdg +nsDKs0appoZbfE/oaO2WpSPytyvlBQgoquq/kagYnOc+G2di5VBS +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/cert0.pem b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/cert0.pem new file mode 100644 index 0000000..1898098 --- /dev/null +++ b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/cert0.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8TCCA9mgAwIBAgITAPoaP9spbg6rEZeE9ftc0nGrejANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjA5MTgx +MzI4MDBaFw0xNjEyMTcxMzI4MDBaMCExHzAdBgNVBAMTFmNhdHVycmEuZXhhY3Ri +eXRlcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6IzRaeBrY +QCA6iaoKAG2aODktRuw9ZEKZluXVNQosnlhUzkcClyj/O6mM4RUqXZw4SzdDPYLL +f8qRxBvvGtOX/WfsMxkJk6ZwrFHp8GwYchgKehVhuuvPmzXx4sTZCyFOs2sK5PNg +g17lI8Qdzw1cyI2zT+WVK9BMWRWw4UM+TEHLeb8LWn0Ljw5sk5krgffx8HCmC8Ho +JruFOXUiRAl2+sWq02JM83FfB6lRXtp1yt+U/XdCpsbcDQyUjLfzcN0Wu/zXLPFN +AaVDKTzdhSU/q9ze+RkT4DSJHXD272ekvIdgbDoOpXbChZqvqoIqQOI6EkFGTHE8 +sAsnJBm9sRcLAgMBAAGjggIfMIICGzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBdA +wGk8f15CB4YLr6G1JhTTrxUJMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm +9Wg6MHgGCCsGAQUFBwEBBGwwajAzBggrBgEFBQcwAYYnaHR0cDovL29jc3Auc3Rn +LWludC14MS5sZXRzZW5jcnlwdC5vcmcvMDMGCCsGAQUFBzAChidodHRwOi8vY2Vy +dC5zdGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wIQYDVR0RBBowGIIWY2F0dXJy +YS5leGFjdGJ5dGVzLmNvbTCB/gYDVR0gBIH2MIHzMAgGBmeBDAECATCB5gYLKwYB +BAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQu +b3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkg +YmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFj +Y29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0 +dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUA +A4IBAQCugwNrp0wmMHoU33jzeOlCWaHLnYplYxGp2zL0uU0/DxWLyVn5u6dj7hcq +UggYh+8PL0iVLPSfLId4t1aI7gor1qmF6L9yzAQq0vderwXMXvLurOsVfEaGJjrA +mpmTews20NdDFJaYly/4GrDfb07D8NTBlzu9sUkRUznBdJ/8u/SjBkFAK5ICxkJ7 +/6KiKwk7g0k9vyMMfAsLCNEPueY17mCnlzj7A2N90np4QXjgcFdFqibAQ6J/+La0 +flU1PWfms+UwRjt4vBebAmInd50lofObm6OPnB9X9IH0vVvnZBUIhk8BE+wxnlw9 +piOf4l9a4N8YiiR+VTUGSOOO7jsN +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/chain0.pem b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/chain0.pem new file mode 100644 index 0000000..29a54e2 --- /dev/null +++ b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/chain0.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/fullchain0.pem b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/fullchain0.pem new file mode 100644 index 0000000..cd55bd9 --- /dev/null +++ b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/fullchain0.pem @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIE8TCCA9mgAwIBAgITAPoaP9spbg6rEZeE9ftc0nGrejANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjA5MTgx +MzI4MDBaFw0xNjEyMTcxMzI4MDBaMCExHzAdBgNVBAMTFmNhdHVycmEuZXhhY3Ri +eXRlcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6IzRaeBrY +QCA6iaoKAG2aODktRuw9ZEKZluXVNQosnlhUzkcClyj/O6mM4RUqXZw4SzdDPYLL +f8qRxBvvGtOX/WfsMxkJk6ZwrFHp8GwYchgKehVhuuvPmzXx4sTZCyFOs2sK5PNg +g17lI8Qdzw1cyI2zT+WVK9BMWRWw4UM+TEHLeb8LWn0Ljw5sk5krgffx8HCmC8Ho +JruFOXUiRAl2+sWq02JM83FfB6lRXtp1yt+U/XdCpsbcDQyUjLfzcN0Wu/zXLPFN +AaVDKTzdhSU/q9ze+RkT4DSJHXD272ekvIdgbDoOpXbChZqvqoIqQOI6EkFGTHE8 +sAsnJBm9sRcLAgMBAAGjggIfMIICGzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBdA +wGk8f15CB4YLr6G1JhTTrxUJMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm +9Wg6MHgGCCsGAQUFBwEBBGwwajAzBggrBgEFBQcwAYYnaHR0cDovL29jc3Auc3Rn +LWludC14MS5sZXRzZW5jcnlwdC5vcmcvMDMGCCsGAQUFBzAChidodHRwOi8vY2Vy +dC5zdGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wIQYDVR0RBBowGIIWY2F0dXJy +YS5leGFjdGJ5dGVzLmNvbTCB/gYDVR0gBIH2MIHzMAgGBmeBDAECATCB5gYLKwYB +BAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQu +b3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkg +YmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFj +Y29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0 +dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUA +A4IBAQCugwNrp0wmMHoU33jzeOlCWaHLnYplYxGp2zL0uU0/DxWLyVn5u6dj7hcq +UggYh+8PL0iVLPSfLId4t1aI7gor1qmF6L9yzAQq0vderwXMXvLurOsVfEaGJjrA +mpmTews20NdDFJaYly/4GrDfb07D8NTBlzu9sUkRUznBdJ/8u/SjBkFAK5ICxkJ7 +/6KiKwk7g0k9vyMMfAsLCNEPueY17mCnlzj7A2N90np4QXjgcFdFqibAQ6J/+La0 +flU1PWfms+UwRjt4vBebAmInd50lofObm6OPnB9X9IH0vVvnZBUIhk8BE+wxnlw9 +piOf4l9a4N8YiiR+VTUGSOOO7jsN +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/privkey0.pem b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/privkey0.pem new file mode 100644 index 0000000..7d97be4 --- /dev/null +++ b/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/privkey0.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuiM0Wnga2EAgOomqCgBtmjg5LUbsPWRCmZbl1TUKLJ5YVM5H +Apco/zupjOEVKl2cOEs3Qz2Cy3/KkcQb7xrTl/1n7DMZCZOmcKxR6fBsGHIYCnoV +Ybrrz5s18eLE2QshTrNrCuTzYINe5SPEHc8NXMiNs0/llSvQTFkVsOFDPkxBy3m/ +C1p9C48ObJOZK4H38fBwpgvB6Ca7hTl1IkQJdvrFqtNiTPNxXwepUV7adcrflP13 +QqbG3A0MlIy383DdFrv81yzxTQGlQyk83YUlP6vc3vkZE+A0iR1w9u9npLyHYGw6 +DqV2woWar6qCKkDiOhJBRkxxPLALJyQZvbEXCwIDAQABAoIBADLNbvmONE13WxR/ +BEDMkx13YOuhotKyrZa736jMXCWHZjZnQmxLk23t+72upRc2C3A13zRj8nHWRBR6 +wOEGol+mUxndbGT3voKcFZNTAj29zh/16CYPXVMBWrzVFsLiTcnsIDgN+vsJf/ns +RBearlv3hO0+zjtjhOuBmPD42mCC+nb3r3nrshCCYrOdjlbpqhMWfga9oQYMhunN +PtG7NLdxQVcFLS5I0utpAQHopNBZnOhygogLBPely8F3s5d/qSgjEFHQXeDNyG4P +Wr2YPJdQNfspMngx2gXrmWBgTGjdM4+hCh0byO5mS3IyQT1vAm25S4E/Eavf4sLm +qHocGPkCgYEA3ueaFyaZKlZCTcfXCfR8fU75lki6arDi6I78Ed4pWGCMUarp2jDR +JI94JwQEYTtYJhkLTGIMBjY/6INBUuqIQpzbAXogTjyRPmZHyLj4HUtprUSSVri5 +HvFT16L9JbBZMfVLmwtgTTBVe+X2bIEACDfyQ9Q3BoJB44EwGVlCUE0CgYEA1cYd +4Nx69xRTVUKQ/n+2XLMDqSCus6IuuobGbmayOLa9yb1EkWClUvnW65U/eHvyyQa1 +tq8eqq7zO91gylUwNi4UNmq1IytLGei4R9ypZRqA017upK55Ezhq0fYMqp3R42kz +daJr/Y8M5oUnJbzxc0BfQqpWENIUhgoWs/DIcLcCgYEAzz7ciKvNeoyKxxCHwey7 +ljJIYk8qa6ocvoa6nM5G+LGDpSbYmJIM0gZGe1gDzndDpOBiHdmHPntP/hmTMcl3 +eR+ni/8FbFhp3m9wTJKVtX75OSzpNpI3JCrSfko/Pbxxob5kVjpEhl/rCvArpoRm +CD4kFKaJppaTNjhWBSt1OX0CgYEAnzqNYML5OHbER3poo5gfDlcsv9ofJqAD7F2d +CfimgUXkgZLfsuVo3zBHHHyzpRu10HSV/zfbQMlFVW7kvHDNk12pIotC1qpVqzvD +n4tGBY/DKy3H1ZQ7jMx2DGQYNTGOd7QRZ2qOw3O86St+6EYfFnh5PB/CMY85SEnV +dTxBIGsCgYEArqD7jlmSVc4ivkrsUqjecVZ9ZnZClS7W1CIYPX6lD38djfz1lXVH +2LtfYd3YNDcZy83jayhgr6wbyLGNKH7b3/o//U7bHqNeb4qpK4wKlPRZ4EB+ueeS +d1rGUJ7FE5qUuM9sZ43yEdrrqDr3/Ywyt9760/hnGZo3CNjVRBPrWy4= +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem new file mode 100644 index 0000000..1898098 --- /dev/null +++ b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE8TCCA9mgAwIBAgITAPoaP9spbg6rEZeE9ftc0nGrejANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjA5MTgx +MzI4MDBaFw0xNjEyMTcxMzI4MDBaMCExHzAdBgNVBAMTFmNhdHVycmEuZXhhY3Ri +eXRlcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6IzRaeBrY +QCA6iaoKAG2aODktRuw9ZEKZluXVNQosnlhUzkcClyj/O6mM4RUqXZw4SzdDPYLL +f8qRxBvvGtOX/WfsMxkJk6ZwrFHp8GwYchgKehVhuuvPmzXx4sTZCyFOs2sK5PNg +g17lI8Qdzw1cyI2zT+WVK9BMWRWw4UM+TEHLeb8LWn0Ljw5sk5krgffx8HCmC8Ho +JruFOXUiRAl2+sWq02JM83FfB6lRXtp1yt+U/XdCpsbcDQyUjLfzcN0Wu/zXLPFN +AaVDKTzdhSU/q9ze+RkT4DSJHXD272ekvIdgbDoOpXbChZqvqoIqQOI6EkFGTHE8 +sAsnJBm9sRcLAgMBAAGjggIfMIICGzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBdA +wGk8f15CB4YLr6G1JhTTrxUJMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm +9Wg6MHgGCCsGAQUFBwEBBGwwajAzBggrBgEFBQcwAYYnaHR0cDovL29jc3Auc3Rn +LWludC14MS5sZXRzZW5jcnlwdC5vcmcvMDMGCCsGAQUFBzAChidodHRwOi8vY2Vy +dC5zdGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wIQYDVR0RBBowGIIWY2F0dXJy +YS5leGFjdGJ5dGVzLmNvbTCB/gYDVR0gBIH2MIHzMAgGBmeBDAECATCB5gYLKwYB +BAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQu +b3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkg +YmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFj +Y29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0 +dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUA +A4IBAQCugwNrp0wmMHoU33jzeOlCWaHLnYplYxGp2zL0uU0/DxWLyVn5u6dj7hcq +UggYh+8PL0iVLPSfLId4t1aI7gor1qmF6L9yzAQq0vderwXMXvLurOsVfEaGJjrA +mpmTews20NdDFJaYly/4GrDfb07D8NTBlzu9sUkRUznBdJ/8u/SjBkFAK5ICxkJ7 +/6KiKwk7g0k9vyMMfAsLCNEPueY17mCnlzj7A2N90np4QXjgcFdFqibAQ6J/+La0 +flU1PWfms+UwRjt4vBebAmInd50lofObm6OPnB9X9IH0vVvnZBUIhk8BE+wxnlw9 +piOf4l9a4N8YiiR+VTUGSOOO7jsN +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem new file mode 100644 index 0000000..29a54e2 --- /dev/null +++ b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem new file mode 100644 index 0000000..cd55bd9 --- /dev/null +++ b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIE8TCCA9mgAwIBAgITAPoaP9spbg6rEZeE9ftc0nGrejANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0xNjA5MTgx +MzI4MDBaFw0xNjEyMTcxMzI4MDBaMCExHzAdBgNVBAMTFmNhdHVycmEuZXhhY3Ri +eXRlcy5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6IzRaeBrY +QCA6iaoKAG2aODktRuw9ZEKZluXVNQosnlhUzkcClyj/O6mM4RUqXZw4SzdDPYLL +f8qRxBvvGtOX/WfsMxkJk6ZwrFHp8GwYchgKehVhuuvPmzXx4sTZCyFOs2sK5PNg +g17lI8Qdzw1cyI2zT+WVK9BMWRWw4UM+TEHLeb8LWn0Ljw5sk5krgffx8HCmC8Ho +JruFOXUiRAl2+sWq02JM83FfB6lRXtp1yt+U/XdCpsbcDQyUjLfzcN0Wu/zXLPFN +AaVDKTzdhSU/q9ze+RkT4DSJHXD272ekvIdgbDoOpXbChZqvqoIqQOI6EkFGTHE8 +sAsnJBm9sRcLAgMBAAGjggIfMIICGzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYw +FAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBdA +wGk8f15CB4YLr6G1JhTTrxUJMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm +9Wg6MHgGCCsGAQUFBwEBBGwwajAzBggrBgEFBQcwAYYnaHR0cDovL29jc3Auc3Rn +LWludC14MS5sZXRzZW5jcnlwdC5vcmcvMDMGCCsGAQUFBzAChidodHRwOi8vY2Vy +dC5zdGctaW50LXgxLmxldHNlbmNyeXB0Lm9yZy8wIQYDVR0RBBowGIIWY2F0dXJy +YS5leGFjdGJ5dGVzLmNvbTCB/gYDVR0gBIH2MIHzMAgGBmeBDAECATCB5gYLKwYB +BAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQu +b3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkg +YmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFj +Y29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0 +dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkvMA0GCSqGSIb3DQEBCwUA +A4IBAQCugwNrp0wmMHoU33jzeOlCWaHLnYplYxGp2zL0uU0/DxWLyVn5u6dj7hcq +UggYh+8PL0iVLPSfLId4t1aI7gor1qmF6L9yzAQq0vderwXMXvLurOsVfEaGJjrA +mpmTews20NdDFJaYly/4GrDfb07D8NTBlzu9sUkRUznBdJ/8u/SjBkFAK5ICxkJ7 +/6KiKwk7g0k9vyMMfAsLCNEPueY17mCnlzj7A2N90np4QXjgcFdFqibAQ6J/+La0 +flU1PWfms+UwRjt4vBebAmInd50lofObm6OPnB9X9IH0vVvnZBUIhk8BE+wxnlw9 +piOf4l9a4N8YiiR+VTUGSOOO7jsN +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw +GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 +MDUyMzIyMDc1OVowIjEgMB4GA1UEAwwXRmFrZSBMRSBJbnRlcm1lZGlhdGUgWDEw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtWKySDn7rWZc5ggjz3ZB0 +8jO4xti3uzINfD5sQ7Lj7hzetUT+wQob+iXSZkhnvx+IvdbXF5/yt8aWPpUKnPym +oLxsYiI5gQBLxNDzIec0OIaflWqAr29m7J8+NNtApEN8nZFnf3bhehZW7AxmS1m0 +ZnSsdHw0Fw+bgixPg2MQ9k9oefFeqa+7Kqdlz5bbrUYV2volxhDFtnI4Mh8BiWCN +xDH1Hizq+GKCcHsinDZWurCqder/afJBnQs+SBSL6MVApHt+d35zjBD92fO2Je56 +dhMfzCgOKXeJ340WhW3TjD1zqLZXeaCyUNRnfOmWZV8nEhtHOFbUCU7r/KkjMZO9 +AgMBAAGjgeMwgeAwDgYDVR0PAQH/BAQDAgGGMBIGA1UdEwEB/wQIMAYBAf8CAQAw +HQYDVR0OBBYEFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHoGCCsGAQUFBwEBBG4wbDA0 +BggrBgEFBQcwAYYoaHR0cDovL29jc3Auc3RnLXJvb3QteDEubGV0c2VuY3J5cHQu +b3JnLzA0BggrBgEFBQcwAoYoaHR0cDovL2NlcnQuc3RnLXJvb3QteDEubGV0c2Vu +Y3J5cHQub3JnLzAfBgNVHSMEGDAWgBTBJnSkikSg5vogKNhcI5pFiBh54DANBgkq +hkiG9w0BAQsFAAOCAgEABYSu4Il+fI0MYU42OTmEj+1HqQ5DvyAeyCA6sGuZdwjF +UGeVOv3NnLyfofuUOjEbY5irFCDtnv+0ckukUZN9lz4Q2YjWGUpW4TTu3ieTsaC9 +AFvCSgNHJyWSVtWvB5XDxsqawl1KzHzzwr132bF2rtGtazSqVqK9E07sGHMCf+zp +DQVDVVGtqZPHwX3KqUtefE621b8RI6VCl4oD30Olf8pjuzG4JKBFRFclzLRjo/h7 +IkkfjZ8wDa7faOjVXx6n+eUQ29cIMCzr8/rNWHS9pYGGQKJiY2xmVC9h12H99Xyf +zWE9vb5zKP3MVG6neX1hSdo7PEAb9fqRhHkqVsqUvJlIRmvXvVKTwNCP3eCjRCCI +PTAvjV+4ni786iXwwFYNz8l3PmPLCyQXWGohnJ8iBm+5nk7O2ynaPVW0U2W+pt2w +SVuvdDM5zGv2f9ltNWUiYZHJ1mmO97jSY/6YfdOUH66iRtQtDkHBRdkNBsMbD+Em +2TgBldtHNSJBfB3pm9FblgOcJ0FSWcUDWJ7vO0+NTXlgrRofRT6pVywzxVo6dND0 +WzYlTWeUVsO40xJqhgUQRER9YLOLxJ0O6C8i0xFxAMKOtSdodMB3RIwt7RFQ0uyt +n5Z5MqkYhlMI3J1tPRTp1nEt9fyGspBOO05gi148Qasp+3N+svqKomoQglNoAxU= +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem new file mode 100644 index 0000000..7d97be4 --- /dev/null +++ b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuiM0Wnga2EAgOomqCgBtmjg5LUbsPWRCmZbl1TUKLJ5YVM5H +Apco/zupjOEVKl2cOEs3Qz2Cy3/KkcQb7xrTl/1n7DMZCZOmcKxR6fBsGHIYCnoV +Ybrrz5s18eLE2QshTrNrCuTzYINe5SPEHc8NXMiNs0/llSvQTFkVsOFDPkxBy3m/ +C1p9C48ObJOZK4H38fBwpgvB6Ca7hTl1IkQJdvrFqtNiTPNxXwepUV7adcrflP13 +QqbG3A0MlIy383DdFrv81yzxTQGlQyk83YUlP6vc3vkZE+A0iR1w9u9npLyHYGw6 +DqV2woWar6qCKkDiOhJBRkxxPLALJyQZvbEXCwIDAQABAoIBADLNbvmONE13WxR/ +BEDMkx13YOuhotKyrZa736jMXCWHZjZnQmxLk23t+72upRc2C3A13zRj8nHWRBR6 +wOEGol+mUxndbGT3voKcFZNTAj29zh/16CYPXVMBWrzVFsLiTcnsIDgN+vsJf/ns +RBearlv3hO0+zjtjhOuBmPD42mCC+nb3r3nrshCCYrOdjlbpqhMWfga9oQYMhunN +PtG7NLdxQVcFLS5I0utpAQHopNBZnOhygogLBPely8F3s5d/qSgjEFHQXeDNyG4P +Wr2YPJdQNfspMngx2gXrmWBgTGjdM4+hCh0byO5mS3IyQT1vAm25S4E/Eavf4sLm +qHocGPkCgYEA3ueaFyaZKlZCTcfXCfR8fU75lki6arDi6I78Ed4pWGCMUarp2jDR +JI94JwQEYTtYJhkLTGIMBjY/6INBUuqIQpzbAXogTjyRPmZHyLj4HUtprUSSVri5 +HvFT16L9JbBZMfVLmwtgTTBVe+X2bIEACDfyQ9Q3BoJB44EwGVlCUE0CgYEA1cYd +4Nx69xRTVUKQ/n+2XLMDqSCus6IuuobGbmayOLa9yb1EkWClUvnW65U/eHvyyQa1 +tq8eqq7zO91gylUwNi4UNmq1IytLGei4R9ypZRqA017upK55Ezhq0fYMqp3R42kz +daJr/Y8M5oUnJbzxc0BfQqpWENIUhgoWs/DIcLcCgYEAzz7ciKvNeoyKxxCHwey7 +ljJIYk8qa6ocvoa6nM5G+LGDpSbYmJIM0gZGe1gDzndDpOBiHdmHPntP/hmTMcl3 +eR+ni/8FbFhp3m9wTJKVtX75OSzpNpI3JCrSfko/Pbxxob5kVjpEhl/rCvArpoRm +CD4kFKaJppaTNjhWBSt1OX0CgYEAnzqNYML5OHbER3poo5gfDlcsv9ofJqAD7F2d +CfimgUXkgZLfsuVo3zBHHHyzpRu10HSV/zfbQMlFVW7kvHDNk12pIotC1qpVqzvD +n4tGBY/DKy3H1ZQ7jMx2DGQYNTGOd7QRZ2qOw3O86St+6EYfFnh5PB/CMY85SEnV +dTxBIGsCgYEArqD7jlmSVc4ivkrsUqjecVZ9ZnZClS7W1CIYPX6lD38djfz1lXVH +2LtfYd3YNDcZy83jayhgr6wbyLGNKH7b3/o//U7bHqNeb4qpK4wKlPRZ4EB+ueeS +d1rGUJ7FE5qUuM9sZ43yEdrrqDr3/Ywyt9760/hnGZo3CNjVRBPrWy4= +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem.bak b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem.bak new file mode 100644 index 0000000..7d97be4 --- /dev/null +++ b/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem.bak @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAuiM0Wnga2EAgOomqCgBtmjg5LUbsPWRCmZbl1TUKLJ5YVM5H +Apco/zupjOEVKl2cOEs3Qz2Cy3/KkcQb7xrTl/1n7DMZCZOmcKxR6fBsGHIYCnoV +Ybrrz5s18eLE2QshTrNrCuTzYINe5SPEHc8NXMiNs0/llSvQTFkVsOFDPkxBy3m/ +C1p9C48ObJOZK4H38fBwpgvB6Ca7hTl1IkQJdvrFqtNiTPNxXwepUV7adcrflP13 +QqbG3A0MlIy383DdFrv81yzxTQGlQyk83YUlP6vc3vkZE+A0iR1w9u9npLyHYGw6 +DqV2woWar6qCKkDiOhJBRkxxPLALJyQZvbEXCwIDAQABAoIBADLNbvmONE13WxR/ +BEDMkx13YOuhotKyrZa736jMXCWHZjZnQmxLk23t+72upRc2C3A13zRj8nHWRBR6 +wOEGol+mUxndbGT3voKcFZNTAj29zh/16CYPXVMBWrzVFsLiTcnsIDgN+vsJf/ns +RBearlv3hO0+zjtjhOuBmPD42mCC+nb3r3nrshCCYrOdjlbpqhMWfga9oQYMhunN +PtG7NLdxQVcFLS5I0utpAQHopNBZnOhygogLBPely8F3s5d/qSgjEFHQXeDNyG4P +Wr2YPJdQNfspMngx2gXrmWBgTGjdM4+hCh0byO5mS3IyQT1vAm25S4E/Eavf4sLm +qHocGPkCgYEA3ueaFyaZKlZCTcfXCfR8fU75lki6arDi6I78Ed4pWGCMUarp2jDR +JI94JwQEYTtYJhkLTGIMBjY/6INBUuqIQpzbAXogTjyRPmZHyLj4HUtprUSSVri5 +HvFT16L9JbBZMfVLmwtgTTBVe+X2bIEACDfyQ9Q3BoJB44EwGVlCUE0CgYEA1cYd +4Nx69xRTVUKQ/n+2XLMDqSCus6IuuobGbmayOLa9yb1EkWClUvnW65U/eHvyyQa1 +tq8eqq7zO91gylUwNi4UNmq1IytLGei4R9ypZRqA017upK55Ezhq0fYMqp3R42kz +daJr/Y8M5oUnJbzxc0BfQqpWENIUhgoWs/DIcLcCgYEAzz7ciKvNeoyKxxCHwey7 +ljJIYk8qa6ocvoa6nM5G+LGDpSbYmJIM0gZGe1gDzndDpOBiHdmHPntP/hmTMcl3 +eR+ni/8FbFhp3m9wTJKVtX75OSzpNpI3JCrSfko/Pbxxob5kVjpEhl/rCvArpoRm +CD4kFKaJppaTNjhWBSt1OX0CgYEAnzqNYML5OHbER3poo5gfDlcsv9ofJqAD7F2d +CfimgUXkgZLfsuVo3zBHHHyzpRu10HSV/zfbQMlFVW7kvHDNk12pIotC1qpVqzvD +n4tGBY/DKy3H1ZQ7jMx2DGQYNTGOd7QRZ2qOw3O86St+6EYfFnh5PB/CMY85SEnV +dTxBIGsCgYEArqD7jlmSVc4ivkrsUqjecVZ9ZnZClS7W1CIYPX6lD38djfz1lXVH +2LtfYd3YNDcZy83jayhgr6wbyLGNKH7b3/o//U7bHqNeb4qpK4wKlPRZ4EB+ueeS +d1rGUJ7FE5qUuM9sZ43yEdrrqDr3/Ywyt9760/hnGZo3CNjVRBPrWy4= +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/dash.com/privkey.pem b/hl-tests/letsencrypt/certs/dash.com/privkey.pem new file mode 100644 index 0000000..a9b0dfc --- /dev/null +++ b/hl-tests/letsencrypt/certs/dash.com/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA1F+sb4pKuXmfYub7BHyu3qtTJp47IAv4ywcIXi1Q5ZANwZPV +1yzJ7r61RYqBG6N/RGr0xuXOkO/Dx3hJvPYYVtDRLq9lx6S2VycI/CO+lgESqJ4T +qn76P5SHrPCYfA1G66t5GY5p8TjxIVyCYeFgMI5xJ6cavKck+LaMmgT1ZKuQLpzD +2+Fc172WpUItcZcNDByRe9FKQzi0hw2vDxhpF+Te9qYGAlEmn9ZosY7W1vqbpUoc +kVVp8+5l3GJrToKdUoCznU/Z6d8A61oz52BMqDDDN7ar9Cgv3frnJIsG9b/p1n4q +bYjjDgnuGWorG+V0YfLyXmpGYyMNRUrJzwunjQIDAQABAoIBAGoMlHjmX8Yam6Kx +oOtur4v1lYIVRYUNNWF804rjqh/YPWZKwl++t9+GT9K4BRyeGjE6D506qFnrwKHc +yUWYxjKj97EOWQ7Gi18d2Pi/iK4zPvQAa+WZnrEdJFsRkbNwldedOs2uNe0E+DQm +k9z4xCW7G3onkcxe0rF3xuIqU7e+oukPHFW8eYSH/Vd5eMHpdrt8ky+6fB6OCMHy +3uRsud2L+lTA+73TXl1Q28YODQOrqW3ZwxnIxeA0/BMh9+/WWbp5vne2YRog0Ucy +1BwP8spoNCAFGogXSqCJZ5lM/FLOrAvRe71Zqh07buW28gTCskpQnwn8FmWrVAKG +U+a5ZoECgYEA95Tp0oOtBp5ZIPqjcmZmIeegAG9SLjiSLIv2riWS4JvoQ6fE7CQC +msR/yGxK95gfxeNYxGqSfS6RfabQ5CrS7n+XQy8nBngypyWOAcS+5HxWZqXdEdri +3fEQiC50lNFLSvADTBEEjBUhPRL/lzUiC3s2mCvZMrYpga43fBb5MpECgYEA25hK +cEQoI7bOmL8z24MT0E3HgEpkE3nCrpfLKliGuEsg43UXApEK+U3hw78UMXZjtHf+ +sqFbZ3urAUWOsBGBJGvYrQsio8kFLBrH3hlGOIuDwxcK2SeSXcUlQgMtknt7L1jl +Wi96wOXfWbN3rLt/yJweC4uMBUC6k5H89YkIaz0CgYA2mIwHdCoPr5OQBjVM4O/c +wisybVn9/1OcpzC6rmZ5SWgqozB1smswnexf0iGl/9Hh3YSRq76qBD7pKoQeDKN/ +HHvKwPcmIhNpcIqkMTK3SAP1ltXtPguRTbuLjFMBDjZATDUt7QLHsVEnGq1qNrlP +NtiPLfwzhqAYjMaHgrlpQQKBgQCon8ABh4TfL+BjUOesV3Iekatxqy4/+k7xrOQ4 +xzPkTuSZZW9e7CvmFtUXcCI8fTHBAifV2awLwd4lotkYAMkPQ7Vl49gctx8+p+30 +caoHf7KVW5tb91Qgp2Od1jznb+S/Dd9Iqo7zk1E4W2S5gl10mdVEfkruOa9L5F2/ +2hNZ7QKBgQCCrgCOMCExxA7jf0WSCCtX/pgZ1fdyI2QXxjso900WwOD2vNfAF2Ih +/2b/8CbFjA68H02990lDxJrH/XEUauEGRWHdwGffiRCQzdVy9vAz1+aYUe0jdA3q +neYlPwFl0tut3e4ouh4fsjTm8OpNRaLeT2JtfgS96ZirxGf2Df9Ugw== +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/dash/.well-known/acme-challenge/abc b/hl-tests/letsencrypt/certs/dash/.well-known/acme-challenge/abc new file mode 100644 index 0000000..5d8338d --- /dev/null +++ b/hl-tests/letsencrypt/certs/dash/.well-known/acme-challenge/abc @@ -0,0 +1 @@ +hello masda! diff --git a/hl-tests/letsencrypt/certs/dash/privkey.pem b/hl-tests/letsencrypt/certs/dash/privkey.pem new file mode 100644 index 0000000..c1b0dd2 --- /dev/null +++ b/hl-tests/letsencrypt/certs/dash/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApaKYlIm3YhX9em4il9cOZiEJZKoKW9O15rm897gPBKpZcnq1 +j7kxk6Ah5LCBl2Tlv/R3KoXYOmgvAp3nyM+rail1OURi9TT+RVTHFDPSkg2xzqDD +/WfSMulkrhXowCP/0WCxIsxu1G1Uxt+3GnkWW9uB8MnpOEOoSzVlp2ou93DUqw24 +Izqhwt5OsPqZxCrkcF+dAY/9vf8yu+cPNXwSzneYWnx14oiMQ+bZNFlijzXOsIP2 +0ogrdFV6V/LObcd+eMtudbr5Ay0BwHuJoOAr9pN6jk3ANnZCqJZyBoucFQ5ht3kQ +8xu6rbVXSx3O3kSqCXiOzVRrtsWd3MJS8sQ21QIDAQABAoIBAQCGw3Y1VJ96FL46 +AKXwuK8kdTi5SIhJEkXrxa90NbMybo98T06t81Xc8OrisKGf1h4AQh064c9+Jgop +rORLRHwJUlXoFDYXn9hJ/KJFU6y93JqQrckIwpIJjk+2PGk0+5VGe89jqsV53MKf +VKIbze1dK4nbqcwxVQr2tilRP1mbREhfVXV2BIog7rwVYkKKOQPMNaooEaI74sYd +Iy7w8kdfbj1/h4wGFxCgIFxv5HO44C+nxRf4F6XSYJ0hDgMqz9NCOwWC3+deN10k +2oBN28m57I1wuvjdPHJWfpeq79dchKLCPXiJlA2Nw/1cePvHltz2AXqaV0/FQxPh +reytGvvBAoGBANuFIsm3qZPITDwrKmHbaTXeOcaWE1omycgjM071c0EbBjjwWE2h +ltPPEXLtjmJsZVItQsqvmghLLVAozNt+wqAKM72h4VFZh9sibhSIhjl8x1jODJsF +7U0yve8AZHcNy3m5/W2/CfoLMD1R/wHR/GQsQ2USQ65EMFoU416veoVJAoGBAMEp +E9xrOwUFyo5oXLwqVzRtU5lhh9LuV6NYUHJAjA7sycFKEL56tueT1XbKc65x74b4 +NC6eIFFp5r+aZcXM1LvHKREeB158Jarpy5AJZomVn22DuKKP+4rCCJG6HeCS3Yzw +fYQKLD0z9teBbeHVacWToAfiZ8W7PNWGx5TBiYEtAoGAFY3uC4Z4JSWerq3CXJdx +rjNi0uf7gHecioVCTXd2WKcxpjebRAwgxi1n1jQTLgDctgPxsfsqEbRn/53x939r +1tEJoY4alKVI3LB1xJhfLZfd7w9UV4huc40O7z/HnZUCLLKherwuW5nro6nAc0pO +EPvzpiHz+VGCueWhKbPrQNkCgYAY63rDehYQLNIYE0C7id7cRap+ZEXIobUuvqsK +QmePWV8iD7MfT+ee8sScYbwQ6mQTjpv007OprTb2yy2MGkPrweL7cVtUBkI5zZXU +jFHtOB9sWo0Mc/TozuWaH1/RZEEu+KvIyCMy9ixHW0xY0VanBceknMH0kZZkmdHP +0DQ0NQKBgCmVjyRG7qqkG0C38vV+z3Jte45rYUmeurnlmbl2ZcmtEMtuGEwhRfyf +zv/lHSywKkt/7xnIeeYvyIyMZI8GE3TXXkWmZGJUTwC+EGo7Z8T60vEAeN81eVk8 +RBMp1Js9mfxU6BOs04d11Qx9rYn8CxJvAfwCUGtebSBQq2fnMd/T +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/dash_/privkey.pem b/hl-tests/letsencrypt/certs/dash_/privkey.pem new file mode 100644 index 0000000..6e977d6 --- /dev/null +++ b/hl-tests/letsencrypt/certs/dash_/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtXi0Lf6/K2vy0FQJJY3rRK5J/sPezKWdmw0CgYzhhUqdy2HV +fenpNRVxs7hQEMhtgWcsaGw7a3m9gNc25BgbNCJDS9gXYl+3+M6wjE7xBRR/EruQ +0SoL1euDfhBRmP9J/I5AeTHzaFlHGqgJ7QayJmo9IpU/A7AvFxpiWOrXk7I4rp2n +c/r9jL1FeyDbcVRbbcy6euwb2tnPPU9LtWcp/oNw+ehZApxtLUxx2+hLgLntafzl +OvjC3jUTAVG4AxK2CH3ka18LCjTFDKgswKnxGIj9O4sQNjUUeH8xQvr/HXQQ/bM3 +SAR8xJTsj9EMXSm4PiLfgFPj8C3ORE8HAe9V1wIDAQABAoIBAQCGpG0DJ1zNqcU5 +nvA+ZeLmTW5nUQqQylx6exed6Vo2XFthWFBfoNq/4Q5AqwL0wNDGzzcarxsTLftV +idiXOe+NKdLGhikrextzxl8lazjWbROvYW0cU9b+PESOlysDmn5ZnW3mvhH8HSlt +dLoQnAQ4DmEXtKZRZTo7KP9JO80BLlzHZ2LcvrCOLaPMsN9RzXap6ARGurBbMWPd +8pl4XeyYQelxA+7fkXP74TJOKiY4hkEL60XAQbZDIdeWLJFyhKf2Ky3YggNX6AVm +2ATptgEmQOCq1LRyVtmcz3+CuMNi/L8u74xQ8FqBstZDDA4d2FabdWj6ydcKpqvx +p2LFMfqJAoGBAOeWbQBn5xwh4gf72vIvB/JWD3ZOSIZu532svZYv953qigiSZy52 +vzJIDvLmqWQoQ2o0+LyDder19vKwIJ6ij4E0IA/4iziKWPi5vRyIaVQoHDiobSkY +B6khAwOPq0YNdiq9UoqUZIEYGbN+p1LiTHpO1ryUyKejIBgeB4mfzSX7AoGBAMiZ +2wDv91FIwAgt0XA31U7kqtgIJiOn1w1PcP3T8oT0LXUA2ZFWrCC3nAIgzIIsiU9f +2UzZ/NtzgurXuioY9VVyH/1biBFvYL4m3ZY4aoFx8uo4Km54TGMSkuJCngk0TAnV +IVPTBpzGmnbe9NQSp/Bb+RJXGrT+V2Dj01Cby3TVAoGALntw0V4JcwoR9gxE+8sY +yzkezV4VDHaCHCVpwBVMm/ORVPsdnqPS6GKyLWrCoQm7zjtnmV7BcjGAKWHUikKS +jxpJPStjtit+hB2zqWBv06ZhU7Xqgw8Bqp6nnjVd6SeWiimJwarbKVYPAonvR6GI +PBxK2Xr7czo4nN6aILNkV7UCgYB1VsHrL3LMYjCp2Bs9d/tXFZz1lvawPpolmAVx +BExFBwub+C5LvJYc4SnpeMQHlQOQoXFbadtlhpDay+uCemzvWT1rFuJlyG+fat6M +410xcLT12nq/ebC89v1iSjNlEOk1iyzeen7Qr79krxApCOyhRTtRRhBCNNBpxXoz +GZ6OLQKBgGKBdvOFEptWeSv7V8zqqGv8YEiDon2QILu2EtZV7K8PQ8Y/Iz1CZyJT +gIzO0Y5nD1+ycVLcHkF51pRUjjHsXJI0RFgj/Jv+EiIlNnxGz/eHoQIa5+dbAnEh +bJRbnhcAnAhdcvJ+nY7YQgP6z2Gz6Z5R8X/UCOfG84NTMMnWt5x0 +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/dev-cert.pem b/hl-tests/letsencrypt/certs/dev-cert.pem new file mode 100644 index 0000000..ff33714 --- /dev/null +++ b/hl-tests/letsencrypt/certs/dev-cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICszCCAhwCCQDz5KVzUsFJSzANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMC +U0UxDzANBgNVBAgTBlNjYW5pYTENMAsGA1UEBxMETHVuZDEfMB0GA1UEChMWT3B0 +aW1hbCBCaXRzIFN3ZWRlbiBBQjEUMBIGA1UECxMLT3B0aW1hbEJpdHMxEjAQBgNV +BAMTCWxvY2FsaG9zdDEjMCEGCSqGSIb3DQEJARYUaW5mb0BvcHRpbWFsYml0cy5j +b20wHhcNMTQwODI2MTIxMTIwWhcNMTQwOTI1MTIxMTIwWjCBnTELMAkGA1UEBhMC +U0UxDzANBgNVBAgTBlNjYW5pYTENMAsGA1UEBxMETHVuZDEfMB0GA1UEChMWT3B0 +aW1hbCBCaXRzIFN3ZWRlbiBBQjEUMBIGA1UECxMLT3B0aW1hbEJpdHMxEjAQBgNV +BAMTCWxvY2FsaG9zdDEjMCEGCSqGSIb3DQEJARYUaW5mb0BvcHRpbWFsYml0cy5j +b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMzWfYITX3co6A2c7sSSGSm8 +3/FraE9FgU9TtIOrPc4AQgNW7dSMw8rGVo3CZ9BcutB+GxBfOfthIcprlP8QNVkO +hfI26hSajgqagT3r0rAX95FPPjQZVcRHNcHsHO6Q5E0NrXD/Ii2brQ4MzTmIgDAK +k+58LwQCTTWGnHdUe2LFAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEACPsKiw83ouKT +wZg4lt2kUhLS/Zrly4jNKuFyeQaUU1RHTTVagpGFmzLpH0vOyMGtiinjfHK0oHa9 +18Aomq6t1V1WTyn46gL10mFWBuAbCVKypbGx4QmQ/uFEmz/A9x3lMytzGt3Ww5WK +gybUbmCM9mi5IB+wco8miTpOWwOWRIw= +-----END CERTIFICATE----- diff --git a/hl-tests/letsencrypt/certs/dev-csr.pem b/hl-tests/letsencrypt/certs/dev-csr.pem new file mode 100644 index 0000000..299b9e8 --- /dev/null +++ b/hl-tests/letsencrypt/certs/dev-csr.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIB3jCCAUcCAQAwgZ0xCzAJBgNVBAYTAlNFMQ8wDQYDVQQIEwZTY2FuaWExDTAL +BgNVBAcTBEx1bmQxHzAdBgNVBAoTFk9wdGltYWwgQml0cyBTd2VkZW4gQUIxFDAS +BgNVBAsTC09wdGltYWxCaXRzMRIwEAYDVQQDEwlsb2NhbGhvc3QxIzAhBgkqhkiG +9w0BCQEWFGluZm9Ab3B0aW1hbGJpdHMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDM1n2CE193KOgNnO7EkhkpvN/xa2hPRYFPU7SDqz3OAEIDVu3UjMPK +xlaNwmfQXLrQfhsQXzn7YSHKa5T/EDVZDoXyNuoUmo4KmoE969KwF/eRTz40GVXE +RzXB7BzukORNDa1w/yItm60ODM05iIAwCpPufC8EAk01hpx3VHtixQIDAQABoAAw +DQYJKoZIhvcNAQEFBQADgYEAVOF8HV93E8PvVqqDVG30dZ6PvzT7okZqBNeqh511 +DdowLC07r5qIyF/sBtGa9ESBW4H+/Cz58tTea7UzoJZrpCos7J090bM9el1Lzp+J +/VOh9qd0smin7icssVQAlF5wzMsOpR4bL85RPDewx2wALShEYuJfKAOqhlVhh3qj +C8U= +-----END CERTIFICATE REQUEST----- diff --git a/hl-tests/letsencrypt/certs/dev-key.pem b/hl-tests/letsencrypt/certs/dev-key.pem new file mode 100644 index 0000000..a7499fa --- /dev/null +++ b/hl-tests/letsencrypt/certs/dev-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDM1n2CE193KOgNnO7EkhkpvN/xa2hPRYFPU7SDqz3OAEIDVu3U +jMPKxlaNwmfQXLrQfhsQXzn7YSHKa5T/EDVZDoXyNuoUmo4KmoE969KwF/eRTz40 +GVXERzXB7BzukORNDa1w/yItm60ODM05iIAwCpPufC8EAk01hpx3VHtixQIDAQAB +AoGAcKwXE4K2g2Qj6MEG8Wdvoe67vB8ZnGkeDNV9OOPrtjGcHhwl7EGVvSdGGunx +ksI/HEoRdvr6eNTf8mkk5vwyaxND9aJ6Y6Iq9SlLFofgF3lJ3SkoCbbCdvXod33k +o9wu09NtdxaqFQVaP7MrlwSKTNtiFWME14c0npTzZvBHkOECQQDpNGUcGOofBMUx +6BhEb2gM2tW4DEFQ2rA/0F821mASrRNYldndcwTR0raOARIHgiKVrkvipWzjMVE2 +OZpmZATdAkEA4NxB4EP1w7t/T96FuPPOu9uS5jCva5XjX85VwnZJhJhUnOqw/K1o +UnCvpH866USLuPqfHovAm5p0j8YXoDEjCQJAU0QJ9gZPUdP6NN+SCp1coXphZN27 +VItA8wgLdyEEHKb/iVm3+IHg7qo11G49acDlaFxbbAl034n0XVAj+PstYQJATpIo +Iqkck0xM7CehKkNnFZVf+zc/1KQHU07SAKU8gyyHRF1tgp1FOqlNdnlOqHvfJr/M +IexLXRPXbvWVA9CnoQJBAOUrDiGmCWbA9+AqkD10nmj6I4BeEhX0e3IHf+SzyXBu +Xkrbej3pJr7XCNkAFq1usvGppKzl52zZCfDtWJlehgs= +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/example.com/privkey.pem b/hl-tests/letsencrypt/certs/example.com/privkey.pem new file mode 100644 index 0000000..27f91f4 --- /dev/null +++ b/hl-tests/letsencrypt/certs/example.com/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEArqJMYsysjp7CdtZQtXdxqUuuuCcx2eFjHObFbulKjf0k7ewj +2Qfxay6rKpBMZd5EgJl3ServMjrOW1Mv1OK3K1pPl2dUnVVO5+mU/eP6sH2WLFBF +0Wo7lXcZuDDKx73n5W4gj5DITJlqjQyFa6EXrMYivUWjjQtsh4mbHJ1U8kH2RHwI +7I2ceNeorSfCBnv5jkJXb8wrUw5KFuT6FqTQV72eJcHsziE5VOxbYzrrjlwWfQ0L +v25LRFdINPG+7phU6HazgjOR4MuW5aMN+O/0DZhXmQ/6sQSLtePrCPURH4ll1WDD +yzdR6oYYm/xQ3XeLGDkKkNU2FsB4INafqwfq/wIDAQABAoIBAE2aW71v3KKIFDyA +4l6xlHW55wt4h2OeD9AxEL1HuFS+kGGWFRwFtpd9ppyEgR6nleNbzzGuz9qPXVIh +9lhw4xrFyCasyWIdHbJbD1V/sNArDsfkaBI2VgIGagx6yjHWxy0iMh/6I7g+WKYT +UVrRvFaPubJINvSfhfv4/0/I7o20XEzm2xSAu9YqA9gcTuytqATrRuKAMkHVvwQq +CvRG4FiA+fPzRDETls5BXHDvwdWfzop9ypmqDZMPPH6ohzCi8Pd4obn/P7W9Y6nf +YGVO6uocT1M+Zf5YJxjhgklJnGNJKC6hj7pp9OCNQB/PIE7nMboW0R/zwBKzYwRh +vIr3ixkCgYEA1uPybWgkiDIzwWKOduf17yZ8hzq8f9a8DZHhgVXrPTSDoB1TLNzU +jKl0oopFJqpjCwIR/js9MbhqQC7dhcNnbDQr6sUgwQAFioOkINi8N/QOAt4mpo1O +jDIl9EXwkL466z5dmFk10RVQ/G3OeMcBCY3YzGIvG0DYDaURUV4oa2UCgYEA0ArU +qMUVFbosKXI04al+QU32NuXFn1rQBKYySa2OA2mtkC5g4J81c5iBaWFrQOaYcl5H +BI4XvNfSsxM/Jc+fDhO24rMYwsCJ6Q1hR1NroV5CzypW+hFJANU2y0BnQ23WNLD2 +ry+ST+kLyiqU5owrYOiPLCWETk7Av29caW4FQJMCgYArPF3QiX2gMYmcRToozm77 +GSFBDB5VEl1v1YQrw5+7Bs/c7UmI4z2Yt5eSBIP5TZrz4gzAvCaJ1HL8SvGjMjei +27RiXhtC+cAjqGzjdvgXwfD3vr7ED/ZX2tcsGM5YMQ4luryWJIzhboqG34kFX/Tj +eTi/lpmnwBo4VJfxaSJ0yQKBgQDP7MM2GiNEn9lbYwVvNFM3OZAGtgaZic21l9VS +xd7Vkl0haPjyBq0JZzaP+AmVx+I9C/S2nL0kxB+VUnsecy9ohlOWp6DrpDsxbWn4 +O2uqz+a93ncnXvczmeU1ppyOS8x2xRcHZ+g3bZeW6o//C8CfDk9ps+VTzmnd6pLV +3FvreQKBgH9zcS8feTJ0VLxIDsc1VELOTjnTLVxoXavFYnIX9dRI8Lio1vTuX9Dg +c7wXYQtHQ82LdsS1EdxGAuf2ozwU0wkGeYhVMVJA/ZNh1TSwqHpzE16+gXzVCttp +wNoXVJbAJfPpo25JUlP6ARy5/HrJR1MRjA9KmkR2xDS7UELyG7NQ +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/localhost/privkey.pem b/hl-tests/letsencrypt/certs/localhost/privkey.pem new file mode 100644 index 0000000..497c0f2 --- /dev/null +++ b/hl-tests/letsencrypt/certs/localhost/privkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxgdSCza1Q/SWJEOj4+2S09Zd0s/WTtVyVdVqiimwzG7rfu7q +GENRrh6vK0dVJ2EDKmLAv42fU20Dl90dqJuCAEqFWk8D7ySURbmLCJK5XzG6u7u1 +zuqTx9lWWn8FV4lw4J8Xwwc/tDZ7AFtQB4+rchc/cknhZnkwKS63O1Me2SREMoYj ++Hq07bq5a87vQ7ccCqkjqVkZwxtmKkotgrGCj8IzZH0JxbDE+Ok5mfnQtKdjik2E +mKvWxQiVmJbcpmJVl+9Tmd3Bwt6hMQ6GW445TX9EJYpLQZW4782oNoF/EvEV0ycg +tTZlQYDlYRh3yZZaFzH56huPWOlfRdo9sXcB4QIDAQABAoIBAEaXYXXB4MgCrmrq ++cdMbyS4q+V0VU7w47sZQstRpUaa27P58tUHWlyZb5Qb740EFh7L6S3fjEYu/DLs +jaAHH0Z/Sh4xQJPFFF3ukFNUCmSW05wMg/jowhlhrljAIuVbhzNrQwsw0FKrgRlV +c6feXR1kkCdrkr/2v2ZO0t6A+OQqWuPtXfpcUjjIgZncxf2pe+4+1nlZMijtZ/9G +RERxKNdkSZphY/i1jQZCV7InA3pEpkkF1Q3sh5KcMHBk1kl4ZCYW6nUnsoHjmxYh +ax9hHYtyxviKfLaK4aIR+km6hNm3CC5s6sk0d7IV+ol+inKoczrFBWOuVYNxqFJ9 +F4+edCkCgYEA48SKpXBr/s2TOkayepkOgrFBQo5+jpZQKZdzMp0YqJXdqaQxV8wG +afSb3uMCjcRm6k0R+v897GjIXSvUIxETMlHF8y+tdWhU6Wsh+eQHYhqT/Dsak/9e +QTtN0aT7j3i1Aald1BexM9ceC5u6jqe2qaFI6xsTchNuUQeReK3mbKMCgYEA3pMa +7DlEM6t84H3Sn2BCf0Wx50h8fGFf/eiSbprvjwoevGQ85eo4TtVotLfQFHDUy2fV ++mzbXoCXD87oeocZpSoeloZC4vyEcoWNKCYg2wzIJSSFASBAXREZM7FFQXS3RPQ7 +0ZPzggnIxlv2GHUk/RvxVx+kOvhNRpwGbfS/26sCgYEAoMsNfgHBm94RA+EI+te2 +kLkF8zCZU5v194a9gou47rruA2awluSn2oEe0Ni94ss2RE4oVWN/mbfXSz83wZG6 +VZm4/xc3g10mJKrHD5zVQYK12ij3eGedaLuvEkNAfGagkg24+ZPIO2qwAU3tA+yO +XW5JBgDVV4E0LewD3IgX1bUCgYBg2Tfc1CpWJWeoM3fDu9oTkVsRHZx1btWbIWke +UbKt1iR7q05IaPtpajkucdFMI7CkdaFJX7awz1lsGodUUZcaJFK9Atz18hUb0/sR +Hk4rosswRkzNqZ/4HymNMbTF/6iDi5a/4hYSXnmLvpY+HDMlI9SHKZCHzGWrtNaj +X91gEwKBgCkztWNAGpJosIkOkbxohDdGMoY1JQgl6Dd7gWNU8aXU49m9Vdf3urm5 +Bw+g20iI5DwZ3u2LQKTplA0u9CU3f0q2KxirQ6tYdxGISBIEh5TugqtgIaf4LKAJ +C639wizq7anvWsmuc2MGr8kQjqdLVQx6ScCv7bMZnyw5LGfQugCn +-----END RSA PRIVATE KEY----- diff --git a/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf b/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf new file mode 100644 index 0000000..77f2c72 --- /dev/null +++ b/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf @@ -0,0 +1,67 @@ +#cert = :configDir/live/:hostname/cert.pem +cert = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem +privkey = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem +chain = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem +fullchain = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem + +# Options and defaults used in the renewal process +[renewalparams] +apache_enmod = a2enmod +no_verify_ssl = False +ifaces = None +apache_dismod = a2dismod +register_unsafely_without_email = False +uir = None +installer = none +config_dir = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs +text_mode = True +# junk? +# https://github.com/letsencrypt/letsencrypt/issues/1955 +func = +prepare = False +work_dir = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/letsencrypt/var/lib +tos = True +init = False +duplicate = False# this is for the domain +key_path = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem +key_path = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem +nginx = False +fullchain_path = :fullchain_path +email = manuel@optimalbits.com +csr = None +agree_dev_preview = None +redirect = None +verbose_count = -3 +config_file = None +renew_by_default = True +hsts = False +authenticator = webroot #comma,delimited,list +domains = caturra.exactbytes.com, +rsa_key_size = 2048# starts at 0 and increments at every renewal +checkpoints = 0 +checkpoints = 0 +manual_test_mode = False +apache = False +cert_path = :cert_path # comma,delimited,list +webroot_path = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/.well-known/acme-challenge, +strict_permissions = False +apache_server_root = /etc/apache2# https://github.com/letsencrypt/letsencrypt/issues/1948 +account = 367e0270a5d31ab031561f9f284ca350 +account = 367e0270a5d31ab031561f9f284ca350 +manual_public_ip_logging_ok = False +chain_path = :chain_path +standalone = False +manual = False +server = https://acme-staging.api.letsencrypt.org/directory +standalone_supported_challenges = "http-01,tls-sni-01" +webroot = True +apache_init_script = None +user_agent = None +apache_ctl = apache2ctl +apache_le_vhost_ext = -le-ssl.conf +debug = False +tls_sni_01_port = 443 +logs_dir = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/letsencrypt/var/log +configurator = None +# :hostname = :webroot_path +caturra.exactbytes.com = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/.well-known/acme-challenge diff --git a/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf.bak b/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf.bak new file mode 100644 index 0000000..d519dfe --- /dev/null +++ b/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf.bak @@ -0,0 +1,68 @@ +#cert = :configDir/live/:hostname/cert.pem +cert = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem +privkey = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem +chain = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem +fullchain = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem + +# Options and defaults used in the renewal process +[renewalparams] +apache_enmod = a2enmod +no_verify_ssl = False +ifaces = None +apache_dismod = a2dismod +register_unsafely_without_email = False +uir = None +installer = none +config_dir = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs +text_mode = True +# junk? +# https://github.com/letsencrypt/letsencrypt/issues/1955 +func = +prepare = False +work_dir = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/letsencrypt/var/lib +tos = True +init = False +duplicate = False +# this is for the domain +key_path = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem +nginx = False +fullchain_path = :fullchain_path +email = manuel@optimalbits.com +csr = None +agree_dev_preview = None +redirect = None +verbose_count = -3 +config_file = None +renew_by_default = True +hsts = False +authenticator = webroot +domains = caturra.exactbytes.com, #comma,delimited,list +rsa_key_size = 2048 +# starts at 0 and increments at every renewal +checkpoints = 0 +manual_test_mode = False +apache = False +cert_path = :cert_path +webroot_path = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/.well-known/acme-challenge, # comma,delimited,list +strict_permissions = False +apache_server_root = /etc/apache2 +# https://github.com/letsencrypt/letsencrypt/issues/1948 +account = 367e0270a5d31ab031561f9f284ca350 +manual_public_ip_logging_ok = False +chain_path = :chain_path +standalone = False +manual = False +server = https://acme-staging.api.letsencrypt.org/directory +standalone_supported_challenges = "http-01,tls-sni-01" +webroot = True +apache_init_script = None +user_agent = None +apache_ctl = apache2ctl +apache_le_vhost_ext = -le-ssl.conf +debug = False +tls_sni_01_port = 443 +logs_dir = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/letsencrypt/var/log +configurator = None +[[webroot_map]] +# :hostname = :webroot_path +caturra.exactbytes.com = /Users/manuel/dev/redbird/hl-tests/letsencrypt/certs/caturra.exactbytes.com/.well-known/acme-challenge diff --git a/hl-tests/letsencrypt/proxy.js b/hl-tests/letsencrypt/proxy.js new file mode 100644 index 0000000..d1b0a31 --- /dev/null +++ b/hl-tests/letsencrypt/proxy.js @@ -0,0 +1,73 @@ +'use strict'; +var path = require('path'); + +var proxy = new require('../../index.js')({ + /* + letsencrypt: { + path: __dirname + '/certs', + port: 9999 + }, + */ + // bunyan: true, + port: 8080, + secure: true, + // http2: true, + // cluster: 8 + ssl: { port: 4443 }, +}) + +/* +proxy.register("caturra.exactbytes.com", "127.0.0.1:3000", { + ssl: { + key: path.join(__dirname, "certs/dev-key.pem"), + cert: path.join(__dirname, "certs/dev-cert.pem"), + } +}); +*/ +proxy.register("localhost", "127.0.0.1:3000", { + ssl: { + key: path.join(__dirname, "certs/dev-key.pem"), + cert: path.join(__dirname, "certs/dev-cert.pem"), + } +}); + + +// proxy.register("localhost", "127.0.0.1:3000"); + +var http = require('http'); +var keepAliveAgent = new http.Agent({ keepAlive: true, maxSockets: 1000 }); +// http.globalAgent = keepAliveAgent; + +/* +var httpProxy = require('http-proxy'); +httpProxy.createProxyServer({target:'http://localhost:3000', agent: keepAliveAgent}).listen(8090); +// httpProxy.createProxyServer({target:'http://localhost:3000'}).listen(8080); + +// var reqFast = require('req-fast'); +// var request = require('request'); +var needle = require('needle'); + +http.createServer(function(req, res){ + // request.get('http://127.0.0.1:3000').pipe(res); + // reqFast('http://127.0.0.1:3000').pipe(res); + // needle.request('get', 'http://127.0.0.1:3000', null, {agent: keepAliveAgent, connection: 'keep-alive'}).pipe(res); + http.get({ + hostname: 'localhost', + port: 3000, + path: '/', + agent: keepAliveAgent + }, function(upstreamRes) { + upstreamRes.pipe(res); + }); +}).listen(8080); +*/ + +var size = 32; +console.log("SIZE:", size); +var randomstring = require("randomstring"); +var msg = randomstring.generate(size); +http.createServer(function(req, res){ + res.writeHead(200); + res.write(msg); + res.end(); +}).listen(3000); diff --git a/hl-tests/paths.js b/hl-tests/paths.js new file mode 100644 index 0000000..6c93ede --- /dev/null +++ b/hl-tests/paths.js @@ -0,0 +1,19 @@ +var proxy = require('../index')({port: 8080}); + +proxy.register("http://127.0.0.1/a", "http://127.0.0.1:3000"); +proxy.register("http://127.0.0.1/b", "http://127.0.0.1:4000"); + +startServer(3000); +startServer(4000); + +function startServer(port){ + var http = require('http'); + function handleRequest(request, response){ + response.end('Path Hit: ' + request.url); + } + var server = http.createServer(handleRequest); + + server.listen(port, function(){ + console.log("Server listening on: http://localhost:%s", port); + }); +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..1a8b5da --- /dev/null +++ b/index.js @@ -0,0 +1,5 @@ +/*eslint-env node */ +'use strict'; +module.exports = require('./lib/proxy'); +module.exports.docker = require('./lib/docker'); +module.exports.etcd = require('./lib/etcd-backend'); diff --git a/lib/docker.js b/lib/docker.js new file mode 100644 index 0000000..69a12ad --- /dev/null +++ b/lib/docker.js @@ -0,0 +1,162 @@ +/*eslint-env node */ +'use strict'; + +/** + Redbird Docker Module. + + This module handles automatic regitration and de-registration of + services running on docker containers. +*/ +var Dolphin = require('dolphin'); + +function DockerModule(redbird, url) { + if (!(this instanceof DockerModule)) { + //TODO: should it return a new instance per redbird proxy? + //because every time we run -> docker(redbird).register("localhost", "tomcat*") + //a new DockerModule is created + return new DockerModule(redbird, url); + } + + this.redbird = redbird; + var log = redbird.log; + + var targets = this.targets = {}; + this.ports = {}; + + // We keep an up-to-date table with all the images having + // containers running on the system. + var images = this.images = {}; + var dolphin = this.dolphin = new Dolphin(url); + + var _this = this; + + function registerIfNeeded(imageName, containerId, containerName) { + var image = images[imageName] = images[imageName] || {}; + + for (var targetName in targets) { + var match = isMatchingImageName(targetName, imageName); + + if (match && image[containerId] !== 'running') { + var target = targets[targetName]; + log && log.info('Registering container %s for target %s', containerName, target.src); + _this.registerContainer(target.src, containerId, target.opts); + } + } + + image[containerId] = 'running'; + } + + // Start docker event listener + this.events = dolphin.events(); + + this.events.on('connected', function () { + + // Fetch all running containers and register them if + // necessary. + dolphin.containers({ filters: {status:["running"]}}).then(function (containers) { + for (var i = 0; i < containers.length; i++) { + var container = containers[i]; + registerIfNeeded(container.Image, container.Id, container.Names[0].replace("/", "")); + } + }); + }); + + this.events.on('event', function (evt) { + var image, target; + + log && log.info('Container %s changed to status %s', evt.Actor.Attributes.name, evt.status); + + switch (evt.status) { + case 'start': + case 'restart': + case 'unpause': + registerIfNeeded(evt.from, evt.id, evt.Actor.Attributes.name); + break; + case 'stop': + case 'die': + case 'pause': + image = images[evt.from]; + if (image) { + for (var targetName in targets) { + var match = isMatchingImageName(targetName, evt.from); + if (image[evt.id] === 'running' && match && _this.ports[evt.id]) { + target = targets[targetName]; + log && log.info('Un-registering container %s for target %s', evt.Actor.Attributes.name, target.src); + _this.redbird.unregister(target.src, _this.ports[evt.id]); + } + image[evt.id] = 'stopped'; + } + } + break; + default: + // Nothing + } + }); + + this.events.on('error', function (err) { + log && log.error(err, 'dolphin docker event error'); + }); +} + +/** + * Register route from a source to a given target. + * The target should be an image name. Starting several containers + * from the same image will automatically deliver the requests + * to each container in a round-robin fashion. + * + * @param src See {@link ReverseProxy.register} + * @param target Docker image (this string is evaluated as regexExp) + * @param opts Options like ssl and etc... + */ +DockerModule.prototype.register = function (src, target, opts) { + var storedTarget = this.targets[target]; + + if (storedTarget && storedTarget.src == src) { + throw Error('Cannot register the same src and target twice'); + } + + this.targets[target] = { + src: src, + opts: opts + }; + + for (var imageName in this.images) { + var image = images[imageName]; + for (var containerId in image) { + //TODO: Changed registerIfNeeded to be reusable here + registerIfNeeded(imageName, containerId, containerId); + } + } +}; + +DockerModule.prototype.registerContainer = function (src, containerId, opts) { + var _this = this; + containerPort(this.dolphin, containerId).then(function (targetPort) { + _this.redbird.register(src, targetPort, opts); + _this.ports[containerId] = targetPort; + }); +}; + +function isMatchingImageName(targetName, imageName) { + var regex = new RegExp("^" + targetName + "$"); + return regex.test(imageName); +} + +function containerPort(dolphin, containerId) { + return dolphin.containers.inspect(containerId).then(function (container) { + var port = Object.keys(container.NetworkSettings.Ports)[0].split('/')[0]; + + var netNames = Object.keys(container.NetworkSettings.Networks); + if (netNames.length === 1) { + var ip = container.NetworkSettings.Networks[netNames[0]].IPAddress; + if (port && ip) { + return 'http://' + ip + ':' + port; + } + } else { + //TODO: Implements opts for manually choosing the network/ip/port... + } + throw Error('No valid address or port ' + container.IPAddress + ':' + port); + }); +} + +module.exports = DockerModule; diff --git a/lib/etcd-backend.js b/lib/etcd-backend.js new file mode 100644 index 0000000..bac6638 --- /dev/null +++ b/lib/etcd-backend.js @@ -0,0 +1,93 @@ +/*eslint-env node */ +'use strict'; + +/** + Redbird ETCD Module + This module handles automatic proxy registration via etcd +*/ +var Etcd = require('node-etcd'); + +function ETCDModule(redbird, options){ + if (!(this instanceof ETCDModule)){ + return new ETCDModule(redbird, options); + } + + // Create Redbird Instance and Log + this.redbird = redbird; + var log = redbird.log; + var _this = this; + + // Create node-etcd Instance + this.etcd = new Etcd(options.hosts,options.ssloptions); + this.etcd_dir = (typeof options.path !== 'undefined') ? options.path : "redbird"; + + // Create directory if not created + this.etcd.get(this.etcd_dir,function(err, body, header){ + if (err && err.errorCode == 100){ + _this.etcd.mkdir(_this.etcd_dir, function(err){ + if (err){ + log.error(err, 'etcd backend error'); + } + else{ + createWatcher(); + } + }); + } + else if(!err && body.node.dir){ + createWatcher(); + } + else{ + log.error(err, 'etcd backend error'); + } + }); + + // Helper function to check if values contain settings + function IsJsonString(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } + + // Helper function to pretify etcd directory strings + function removeEtcDir(str) { + return str.replace(_this.etcd_dir, '').replace(/^\/+|\/+$/g, ''); + } + + function createWatcher(){ + // Watch etcd directory + _this.watcher = _this.etcd.watcher(_this.etcd_dir, null, {recursive:true}); + + // On Add/Update + _this.watcher.on("change", function(body,headers){ + if(body.node.key && body.node.value && !IsJsonString(body.node.value)){ + _this.redbird.register(removeEtcDir(body.node.key),body.node.value); + } + else if(body.node.key && body.node.value && IsJsonString(body.node.value)){ + var config = JSON.parse(body.node.value); + if (typeof config.docker !== 'undefined'){ + require('../').docker(_this.redbird).register(body.node.key,body.node.value.docker,body.node.value); + } + else { + _this.redbird.register(removeEtcDir(body.node.key),config.hosts,config); + } + } + }); + + // On Delete + _this.watcher.on("delete", function(body,headers){ + if(body.node.key){ + _this.redbird.unregister(removeEtcDir(body.node.key)); + } + }); + + // Handle Errors + _this.watcher.on("error", function(err){ + log.error(err, 'etcd backend error'); + }); + } +} + +module.exports = ETCDModule; diff --git a/lib/letsencrypt.js b/lib/letsencrypt.js new file mode 100644 index 0000000..d9fc3e7 --- /dev/null +++ b/lib/letsencrypt.js @@ -0,0 +1,137 @@ +/** + * Letsecript module for Redbird (c) Optimalbits 2016 + * + * + * + */ +var letsencrypt = require('letsencrypt'); + +/** + * LetsEncrypt certificates are stored like the following: + * + * /example.com + * / + * + * + * + */ +var leStoreConfig = {}; +var webrootPath = ':configDir/:hostname/.well-known/acme-challenge'; + +function init(certPath, port, logger){ + var http = require('http'); + var path = require('path'); + var url = require('url'); + var fs = require('fs'); + + logger && logger.info('Initializing letsencrypt, path %s, port: %s', certPath, port); + + leStoreConfig = { + configDir: certPath, + privkeyPath: ':configDir/:hostname/privkey.pem', + fullchainPath: ':configDir/:hostname/fullchain.pem', + certPath: ':configDir/:hostname/cert.pem', + chainPath: ':configDir/:hostname/chain.pem', + + workDir: ':configDir/letsencrypt/var/lib', + logsDir: ':configDir/letsencrypt/var/log', + + webrootPath: webrootPath, + debug: false + }; + + // we need to proxy for example: 'example.com/.well-known/acme-challenge' -> 'localhost:port/example.com/' + http.createServer(function (req, res){ + var uri = url.parse(req.url).pathname; + var filename = path.join(certPath, uri); + var isForbiddenPath = uri.length < 3 || filename.indexOf(certPath) !== 0; + + if (isForbiddenPath) { + logger && logger.info('Forbidden request on LetsEncrypt port %s: %s', port, filename); + res.writeHead(403); + res.end(); + return; + } + + logger && logger.info('LetsEncrypt CA trying to validate challenge %s', filename); + + fs.stat(filename, function(err, stats) { + if (err || !stats.isFile()) { + res.writeHead(404, {"Content-Type": "text/plain"}); + res.write("404 Not Found\n"); + res.end(); + return; + } + + res.writeHead(200); + fs.createReadStream(filename, "binary").pipe(res); + }); + + }).listen(port); +} + +/** + * Gets the certificates for the given domain. + * Handles all the LetsEncrypt protocol. Uses + * existing certificates if any, or negotiates a new one. + * Returns a promise that resolves to an object with the certificates. + * TODO: We should use something like https://github.com/PaquitoSoft/memored/blob/master/index.js + * to avoid + */ +function getCertificates(domain, email, production, renew, logger){ + var LE = require('letsencrypt'); + var le; + + // Storage Backend + var leStore = require('le-store-certbot').create(leStoreConfig); + + // ACME Challenge Handlers + var leChallenge = require('le-challenge-fs').create({ + webrootPath: webrootPath, + debug: false + }); + + le = LE.create({ + server: production ? LE.productionServerUrl : LE.stagingServerUrl, + store: leStore, // handles saving of config, accounts, and certificates + challenges: { 'http-01': leChallenge }, // handles /.well-known/acme-challege keys and tokens + challengeType: 'http-01', // default to this challenge type + debug: false, + log: function (debug) { + logger && logger.info(arguments, 'Lets encrypt debugger'); + } + }); + + // Check in-memory cache of certificates for the named domain + return le.check({ domains: [domain] }).then(function (cert){ + var opts = { + domains: [domain], + email: email, + agreeTos: true, + rsaKeySize: 2048, // 2048 or higher + challengeType: 'http-01' + } + + if (cert){ + if (renew){ + logger && logger.info('renewing cert for ' + domain); + opts.duplicate = true; + return le.renew(opts, cert).catch(function(err){ + logger && logger.error(err, 'Error renewing certificates for ', domain); + }); + } else { + logger && logger.info('Using cached cert for ' + domain); + return cert; + } + } else { + // Register Certificate manually + logger && logger.info('Manually registering certificate for %s', domain); + return le.register(opts).catch(function (err) { + logger && logger.error(err, 'Error registering LetsEncrypt certificates'); + }); + } + }); +} + +module.exports.init = init; +module.exports.getCertificates = getCertificates; diff --git a/lib/proxy.js b/lib/proxy.js new file mode 100644 index 0000000..2a1093b --- /dev/null +++ b/lib/proxy.js @@ -0,0 +1,780 @@ +/*eslint-env node */ +'use strict'; + +var + http = require('http'), + httpProxy = require('http-proxy'), + validUrl = require('valid-url'), + parseUrl = require('url').parse, + path = require('path'), + _ = require('lodash'), + bunyan = require('bunyan'), + cluster = require('cluster'), + hash = require('object-hash'), + LRUCache = require("lru-cache"), + routeCache = LRUCache({ max: 5000 }), + safe = require('safetimeout'), + letsencrypt = require('./letsencrypt.js'); + +var ONE_DAY = 60 * 60 * 24 * 1000; +var ONE_MONTH = ONE_DAY * 30; + +function ReverseProxy(opts) { + if (!(this instanceof ReverseProxy)) { + return new ReverseProxy(opts); + } + + this.opts = opts = opts || {}; + + var log; + if (opts.bunyan !== false) { + log = this.log = bunyan.createLogger(opts.bunyan || { + name: 'redbird' + }); + } + + var _this = this; + + if (opts.cluster && typeof opts.cluster !== 'number' || opts.cluster > 32) { + throw Error('cluster setting must be an integer less than 32'); + } + + if (opts.cluster && cluster.isMaster) { + for (var i = 0; i < opts.cluster; i++) { + cluster.fork(); + } + + cluster.on('exit', function (worker, code, signal) { + // Fork if a worker dies. + log && log.error({ + code: code, + signal: signal + }, + 'worker died un-expectedly... restarting it.'); + cluster.fork(); + }); + } else { + this.resolvers = [this._defaultResolver]; + + opts.port = opts.port || 8080; + + if (opts.letsencrypt) { + this.setupLetsencrypt(log, opts); + } + + if (opts.resolvers) { + this.addResolver(opts.resolvers); + } + + // + // Routing table. + // + this.routing = {}; + + // + // Create a proxy server with custom application logic + // + var proxy = this.proxy = httpProxy.createProxyServer({ + xfwd: (opts.xfwd != false), + prependPath: false, + secure: (opts.secure !== false), + changeOrigin: true, + /* + agent: new http.Agent({ + keepAlive: true + }) + */ + }); + + proxy.on('proxyReq', function (p, req) { + if (req.host != null) { + p.setHeader('host', req.host); + } + }); + + // + // Support NTLM auth + // + if (opts.ntlm) { + proxy.on('proxyRes', function (proxyRes) { + var key = 'www-authenticate'; + proxyRes.headers[key] = proxyRes.headers[key] && proxyRes.headers[key].split(','); + }); + } + + // + // Optionally create an https proxy server. + // + if (opts.ssl) { + if (_.isArray(opts.ssl)) { + opts.ssl.forEach(function (sslOpts) { + _this.setupHttpsProxy(proxy, websocketsUpgrade, log, sslOpts); + }) + } else { + this.setupHttpsProxy(proxy, websocketsUpgrade, log, opts.ssl); + } + } + + // + // Plain HTTP Proxy + // + var server = this.setupHttpProxy(proxy, websocketsUpgrade, log, opts); + + server.listen(opts.port, opts.host); + + if (opts.errorHandler && _.isFunction(opts.errorHandler)) { + proxy.on('error', opts.errorHandler); + } else { + proxy.on('error', handleProxyError); + } + + log && log.info('Started a Redbird reverse proxy server on port %s', opts.port); + } + + function websocketsUpgrade(req, socket, head) { + var src = _this._getSource(req); + var target = _this._getTarget(src, req); + log && log.info({ headers: req.headers, target: target }, 'upgrade to websockets'); + if (target) { + proxy.ws(req, socket, head, { target: target }); + } else { + respondNotFound(req, socket); + } + } + + function handleProxyError(err, req, res) { + // + // Send a 500 http status if headers have been sent + // + + if (err.code === 'ECONNREFUSED') { + res.writeHead && res.writeHead(502); + } else if (!res.headersSent) { + res.writeHead && res.writeHead(500); + } + + // + // Do not log this common error + // + if (err.message !== 'socket hang up') { + log && log.error(err, 'Proxy Error'); + } + + // + // TODO: if err.code=ECONNREFUSED and there are more servers + // for this route, try another one. + // + res.end(err.code) + } +} + +ReverseProxy.prototype.setupHttpProxy = function (proxy, websocketsUpgrade, log, opts) { + var _this = this; + var httpServerModule = opts.serverModule || http; + var server = this.server = httpServerModule.createServer(function (req, res) { + var src = _this._getSource(req); + var target = _this._getTarget(src, req); + if (target) { + if (shouldRedirectToHttps(_this.certs, src, target, _this)) { + redirectToHttps(req, res, target, opts.ssl, log); + } else { + proxy.web(req, res, { target: target }); + } + } else { + respondNotFound(req, res); + } + }); + + // + // Listen to the `upgrade` event and proxy the + // WebSocket requests as well. + // + server.on('upgrade', websocketsUpgrade); + + server.on('error', function (err) { + log && log.error(err, 'Server Error'); + }); + + return server; +} + +function shouldRedirectToHttps(certs, src, target, proxy) { + return certs && src in certs && target.sslRedirect && target.host != proxy.letsencryptHost; +} + +ReverseProxy.prototype.setupLetsencrypt = function (log, opts) { + if (!opts.letsencrypt.path) { + throw Error('Missing certificate path for Lets Encrypt'); + } + var letsencryptPort = opts.letsencrypt.port || 3000; + letsencrypt.init(opts.letsencrypt.path, letsencryptPort, log); + + opts.resolvers = opts.resolvers || []; + this.letsencryptHost = '127.0.0.1:' + letsencryptPort; + var targetHost = 'http://' + this.letsencryptHost; + var challengeResolver = function (host, url) { + if (/^\/.well-known\/acme-challenge/.test(url)) { + return targetHost + '/' + host; + } + } + challengeResolver.priority = 9999; + this.addResolver(challengeResolver); +} + +ReverseProxy.prototype.setupHttpsProxy = function (proxy, websocketsUpgrade, log, sslOpts) { + var _this = this; + var https; + + this.certs = this.certs || {}; + + var certs = this.certs; + + var ssl = { + SNICallback: function (hostname, cb) { + if (cb) { + cb(null, certs[hostname]); + } else { + return certs[hostname]; + } + }, + // + // Default certs for clients that do not support SNI. + // + key: getCertData(sslOpts.key), + cert: getCertData(sslOpts.cert) + }; + + if (sslOpts.ca) { + ssl.ca = getCertData(sslOpts.ca, true); + } + + if (sslOpts.opts) { + ssl = _.defaults(ssl, sslOpts.opts); + } + + if (sslOpts.http2) { + https = sslOpts.serverModule || require('spdy'); + if (_.isObject(sslOpts.http2)) { + sslOpts.spdy = sslOpts.http2; + } + } else { + https = sslOpts.serverModule || require('https'); + } + + var httpsServer = this.httpsServer = https.createServer(ssl, function (req, res) { + + var src = _this._getSource(req); + + var target = _this._getTarget(src, req); + if (target) { + proxy.web(req, res, { target: target }); + } else { + respondNotFound(req, res); + } + }); + + httpsServer.on('upgrade', websocketsUpgrade); + + httpsServer.on('error', function (err) { + log && log.error(err, 'HTTPS Server Error'); + }); + + httpsServer.on('clientError', function (err) { + log && log.error(err, 'HTTPS Client Error'); + }); + + log && log.info('Listening to HTTPS requests on port %s', sslOpts.port); + httpsServer.listen(sslOpts.port, sslOpts.ip); +} + +ReverseProxy.prototype.addResolver = function (resolver) { + if (this.opts.cluster && cluster.isMaster) return this; + + if (!_.isArray(resolver)) { + resolver = [resolver]; + } + + var _this = this; + resolver.forEach(function (resolveObj) { + if (!_.isFunction(resolveObj)) { + throw new Error("Resolver must be an invokable function."); + } + + if (!resolveObj.hasOwnProperty('priority')) { + resolveObj.priority = 0; + } + + _this.resolvers.push(resolveObj); + }); + + _this.resolvers = _.sortBy(_.uniq(_this.resolvers), function (r) { + return -r.priority; + }); + +}; + +ReverseProxy.prototype.removeResolver = function (resolver) { + if (this.opts.cluster && cluster.isMaster) return this; + // since unique resolvers are not checked for performance, + // just remove every existence. + this.resolvers = this.resolvers.filter(function (resolverFn) { + return resolverFn !== resolver; + }); +}; + +ReverseProxy.buildTarget = function (target, opts) { + opts = opts || {}; + target = prepareUrl(target); + target.sslRedirect = !opts.ssl || opts.ssl.redirect !== false; + target.useTargetHostHeader = opts.useTargetHostHeader === true; + return target; +}; + +/** + Register a new route. + + @src {String|URL} A string or a url parsed by node url module. + Note that port is ignored, since the proxy just listens to one port. + + @target {String|URL} A string or a url parsed by node url module. + @opts {Object} Route options. + */ +ReverseProxy.prototype.register = function (src, target, opts) { + if (this.opts.cluster && cluster.isMaster) return this; + + if (!src || !target) { + throw Error('Cannot register a new route with unspecified src or target'); + } + + var routing = this.routing; + + src = prepareUrl(src); + + if (opts) { + var ssl = opts.ssl; + if (ssl) { + if (!this.httpsServer) { + throw Error('Cannot register https routes without defining a ssl port'); + } + + if (!this.certs[src.hostname]) { + if (ssl.key || ssl.cert || ssl.ca) { + this.certs[src.hostname] = createCredentialContext(ssl.key, ssl.cert, ssl.ca); + } else if (ssl.letsencrypt) { + if (!this.opts.letsencrypt || !this.opts.letsencrypt.path) { + console.error('Missing certificate path for Lets Encrypt'); + return; + } + this.log && this.log.info('Getting Lets Encrypt certificates for %s', src.hostname); + this.updateCertificates( + src.hostname, + ssl.letsencrypt.email, + ssl.letsencrypt.production, + this.opts.letsencrypt.renewWithin || ONE_MONTH); + } else { + // Trigger the use of the default certificates. + this.certs[src.hostname] = void 0; + } + } + } + } + target = ReverseProxy.buildTarget(target, opts); + + var host = routing[src.hostname] = routing[src.hostname] || []; + var pathname = src.pathname || '/'; + var route = _.find(host, { path: pathname }); + + if (!route) { + route = { path: pathname, rr: 0, urls: [] }; + host.push(route); + + // + // Sort routes + // + routing[src.hostname] = _.sortBy(host, function (_route) { + return -_route.path.length; + }); + } + + route.urls.push(target); + + this.log && this.log.info({ from: src, to: target }, 'Registered a new route'); + return this; +}; + +ReverseProxy.prototype.updateCertificates = function (domain, email, production, renewWithin, renew) { + var _this = this; + return letsencrypt.getCertificates(domain, email, production, renew, this.log).then(function (certs) { + if (certs) { + var opts = { + key: certs.privkey, + cert: certs.cert + certs.chain + } + _this.certs[domain] = tls.createSecureContext(opts).context; + + // + // TODO: cluster friendly + // + var renewTime = (certs.expiresAt - Date.now()) - renewWithin; + renewTime = renewTime > 0 ? renewTime : _this.opts.letsencrypt.minRenewTime || 60 * 60 * 1000; + + _this.log && _this.log.info('Renewal of %s in %s days', domain, Math.floor(renewTime / ONE_DAY)); + + function renewCertificate() { + _this.log && _this.log.info('Renewing letscrypt certificates for %s', domain); + _this.updateCertificates(domain, email, production, renewWithin, true); + } + + _this.certs[domain].renewalTimeout = safe.setTimeout(renewCertificate, renewTime); + } else { + // + // TODO: Try again, but we need an exponential backof to avoid getting banned. + // + _this.log && _this.log.info('Could not get any certs for %s', domain); + } + }, function (err) { + console.error('Error getting LetsEncrypt certificates', err); + }); +}; + +ReverseProxy.prototype.unregister = function (src, target) { + if (this.opts.cluster && cluster.isMaster) return this; + + if (!src) { + return this; + } + + src = prepareUrl(src); + var routes = this.routing[src.hostname] || []; + var pathname = src.pathname || '/'; + var i; + + for (i = 0; i < routes.length; i++) { + if (routes[i].path === pathname) { + break; + } + } + + if (i < routes.length) { + var route = routes[i]; + + if (target) { + target = prepareUrl(target); + _.remove(route.urls, function (url) { + return url.href === target.href; + }); + } else { + route.urls = []; + } + + if (route.urls.length === 0) { + routes.splice(i, 1); + var certs = this.certs; + if (certs) { + if (certs[src.hostname] && certs[src.hostname].renewalTimeout) { + safe.clearTimeout(certs[src.hostname].renewalTimeout); + } + delete certs[src.hostname]; + } + } + + this.log && this.log.info({ from: src, to: target }, 'Unregistered a route'); + } + return this; +}; + +ReverseProxy.prototype._defaultResolver = function (host, url) { + // Given a src resolve it to a target route if any available. + if (!host) { + return; + } + + url = url || '/'; + + var routes = this.routing[host]; + var i = 0; + + if (routes) { + var len = routes.length; + + // + // Find path that matches the start of req.url + // + for (i = 0; i < len; i++) { + var route = routes[i]; + + if (route.path === '/' || startsWith(url, route.path)) { + return route; + } + } + } +}; + +ReverseProxy.prototype._defaultResolver.priority = 0; + +/** + * Resolves to route + * @param host + * @param url + * @returns {*} + */ +ReverseProxy.prototype.resolve = function (host, url) { + var route; + + host = host && host.toLowerCase(); + for (var i = 0; i < this.resolvers.length; i++) { + route = this.resolvers[i].call(this, host, url); + if (route && (route = ReverseProxy.buildRoute(route))) { + // ensure resolved route has path that prefixes URL + // no need to check for native routes. + if (!route.isResolved || route.path === '/' || startsWith(url, route.path)) { + return route; + } + } + } +}; + +ReverseProxy.buildRoute = function (route) { + if (!_.isString(route) && !_.isObject(route)) { + return null; + } + + if (_.isObject(route) && route.hasOwnProperty('urls') && route.hasOwnProperty('path')) { + // default route type matched. + return route; + } + + var cacheKey = _.isString(route) ? route : hash(route); + var entry = routeCache.get(cacheKey); + if (entry) { + return entry; + } + + var routeObject = { rr: 0, isResolved: true }; + if (_.isString(route)) { + routeObject.urls = [ReverseProxy.buildTarget(route)]; + routeObject.path = '/'; + } else { + if (!route.hasOwnProperty('url')) { + return null; + } + + routeObject.urls = (_.isArray(route.url) ? route.url : [route.url]).map(function (url) { + return ReverseProxy.buildTarget(url, route.opts || {}); + }); + + routeObject.path = route.path || '/'; + } + routeCache.set(cacheKey, routeObject); + return routeObject; +}; + +ReverseProxy.prototype._getTarget = function (src, req) { + var url = req.url; + var route = this.resolve(src, url); + + if (!route) { + this.log && this.log.warn({ src: src, url: url }, 'no valid route found for given source'); + return; + } + + var pathname = route.path; + if (pathname.length > 1) { + // + // remove prefix from src + // + req._url = url; // save original url + req.url = url.substr(pathname.length) || '/'; + } + + // + // Perform Round-Robin on the available targets + // TODO: if target errors with EHOSTUNREACH we should skip this + // target and try with another. + // + var urls = route.urls; + var j = route.rr; + route.rr = (j + 1) % urls.length; // get and update Round-robin index. + var target = route.urls[j]; + + // + // Fix request url if targetname specified. + // + if (target.pathname) { + req.url = path.join(target.pathname, req.url); + } + + // + // Host headers are passed through from the source by default + // Often we want to use the host header of the target instead + // + if (target.useTargetHostHeader === true) { + req.host = target.host; + } + + this.log && this.log.info('Proxying %s to %s', src + url, path.join(target.host, req.url)); + + return target; +}; + +ReverseProxy.prototype._getSource = function (req) { + if (this.opts.preferForwardedHost === true && req.headers['x-forwarded-host']) { + return req.headers['x-forwarded-host'].split(':')[0]; + } + if (req.headers.host) { + return req.headers.host.split(':')[0]; + } +} + +ReverseProxy.prototype.close = function () { + try { + this.server.close(); + this.httpsServer && this.httpsServer.close(); + } catch (err) { + // Ignore for now... + } +}; + +// +// Helpers +// +/** + Routing table structure. An object with hostname as key, and an array as value. + The array has one element per path associated to the given hostname. + Every path has a Round-Robin value (rr) and urls array, with all the urls available + for this target route. + + { + hostA : + [ + { + path: '/', + rr: 3, + urls: [] + } + ] + } +*/ + +var respondNotFound = function (req, res) { + res.statusCode = 404; + res.write('Not Found'); + res.end(); +}; + +ReverseProxy.prototype.notFound = function (callback) { + if (typeof callback == "function") + respondNotFound = callback; + else + throw Error('notFound callback is not a function'); +}; + +// +// Redirect to the HTTPS proxy +// +function redirectToHttps(req, res, target, ssl, log) { + req.url = req._url || req.url; // Get the original url since we are going to redirect. + + var targetPort = ssl.redirectPort || ssl.port; + var hostname = req.headers.host.split(':')[0] + (targetPort ? ':' + targetPort : ''); + var url = 'https://' + path.join(hostname, req.url); + log && log.info('Redirecting %s to %s', path.join(req.headers.host, req.url), url); + // + // We can use 301 for permanent redirect, but its bad for debugging, we may have it as + // a configurable option. + // + res.writeHead(302, { Location: url }); + res.end(); +} + +function startsWith(input, str) { + return input.slice(0, str.length) === str && + (input.length === str.length || input[str.length] === '/') +} + +function prepareUrl(url) { + url = _.clone(url); + if (_.isString(url)) { + url = setHttp(url); + + if (!validUrl.isHttpUri(url) && !validUrl.isHttpsUri(url)) { + throw Error('uri is not a valid http uri ' + url); + } + + url = parseUrl(url); + } + return url; +} + +function getCertData(pathname, unbundle) { + var fs = require('fs'); + + // TODO: Support input as Buffer, Stream or Pathname. + + if (pathname) { + if (_.isArray(pathname)) { + var pathnames = pathname; + return _.flatten(_.map(pathnames, function (_pathname) { + return getCertData(_pathname, unbundle); + })); + } else if (fs.existsSync(pathname)) { + if (unbundle) { + return unbundleCert(fs.readFileSync(pathname, 'utf8')); + } else { + return fs.readFileSync(pathname, 'utf8'); + } + } + } +} + +/** + Unbundles a file composed of several certificates. + http://www.benjiegillam.com/2012/06/node-dot-js-ssl-certificate-chain/ + */ +function unbundleCert(bundle) { + var chain = bundle.trim().split('\n'); + + var ca = []; + var cert = []; + + for (var i = 0, len = chain.length; i < len; i++) { + var line = chain[i].trim(); + if (!(line.length !== 0)) { + continue; + } + cert.push(line); + if (line.match(/-END CERTIFICATE-/)) { + var joined = cert.join('\n'); + ca.push(joined); + cert = []; + } + } + return ca; +} + +var tls = require('tls'); +function createCredentialContext(key, cert, ca) { + var opts = {}; + + opts.key = getCertData(key); + opts.cert = getCertData(cert); + if (ca) { + opts.ca = getCertData(ca, true); + } + + var credentials = tls.createSecureContext(opts); + + return credentials.context; +} + +// +// https://stackoverflow.com/questions/18052919/javascript-regular-expression-to-add-protocol-to-url-string/18053700#18053700 +// Adds http protocol if non specified. +function setHttp(link) { + if (link.search(/^http[s]?\:\/\//) === -1) { + link = 'http://' + link; + } + return link; +} + +module.exports = ReverseProxy; diff --git a/lib/redis-backend.js b/lib/redis-backend.js new file mode 100644 index 0000000..a431b63 --- /dev/null +++ b/lib/redis-backend.js @@ -0,0 +1,109 @@ +"use strict"; + +var redis = require('redis'); +var Promise = require('bluebird'); +var _ = require('lodash'); + +Promise.promisifyAll(redis); + +/** + Instantiates a Redis Redbird backend. + + opts: { + prefix: '', + port: 6739, + host: 'localhost', + opts: {} + } +*/ +function RedisBackend(port, hostname, opts) +{ + if(!(this instanceof RedisBackend)){ + return new RedisBackend(port, hostname, opts); + } + + opts = opts || {}; + port = port || 6379; + hostname = hostname || 'localhost'; + + this.redis = redis.createClient(port, hostname, opts); + this.publish = redis.createClient(port, hostname, opts); + + this.prefix = opts.prefix + ''; + + this.baseKey = baseKey(this.prefix); +} + +/** + Returns a Promise that resolves to an array with all the + registered services and removes the expired ones. +*/ +RedisBackend.prototype.getServices = function(){ + var _this = this; + var redis = this.redis; + var baseKey = this.baseKey; + + // + // Get all members in the service set. + // + return redis.smembersAsync(baseKey + 'ids').then(function(serviceIds){ + return Promise.all(_.map(serviceIds, function(id){ + return _this.getService(id); + })); + }).then(function(services){ + // Clean expired services + return _.compact(services); + }); +} + +RedisBackend.prototype.getService = function(id){ + var redis = this.redis; + // + // Get service hash + // + return redis.hgetallAsync(this.baseKey + id).then(function(service){ + if(service){ + return service; + }else{ + // + // Service has expired, we must delete it from the service set. + // + return redis.sremAsync(id); + } + }); +} + +RedisBackend.prototype.register = function(service){ + var redis = this.redis; + var publish = this.publish; + var baseKey = this.baseKey; + + // + // Get unique service ID. + // + return redis.incrAsync(baseKey + 'counter').then(function(id){ + // Store it + redis.hset(baseKey + id, service).then(function(){ + return id; + }) + }).then(function(id){ + // + // // Publish a meesage so that the proxy can react faster to a new registration. + // + return publish.publishAsync(baseKey + 'registered', id).then(function(){ + return id; + }) + }); +} + +RedisBackend.prototype.ping = function(id){ + return this.redis.pexpireAsync(id, 5000); +} + +function baseKey(prefix){ + return 'redbird-' + prefix + '-services-'; +} + +module.exports = RedisBackend; + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..bddcd81 --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "_from": "redbird", + "_id": "redbird@0.7.0", + "_inBundle": false, + "_integrity": "sha1-q9X0OAL5HXHYQHc+Ml2xXA+e/u8=", + "_location": "/redbird", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "redbird", + "name": "redbird", + "escapedName": "redbird", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#USER", + "/" + ], + "_resolved": "https://registry.npmjs.org/redbird/-/redbird-0.7.0.tgz", + "_shasum": "abd5f43802f91d71d840773e325db15c0f9efeef", + "_spec": "redbird", + "_where": "C:\\Users\\micro\\Desktop\\temp", + "author": { + "name": "Manuel Astudillo" + }, + "bugs": { + "url": "https://github.com/OptimalBits/redbird/issues" + }, + "bundleDependencies": false, + "dependencies": { + "bluebird": "^3.4.0", + "bunyan": "^1.8.1", + "dolphin": "*", + "http-proxy": "^1.14.0", + "le-challenge-fs": "^2.0.4", + "le-store-certbot": "^2.0.3", + "letsencrypt": "^2.1.6", + "lodash": "^4.13.1", + "lru-cache": "^4.0.1", + "node-etcd": "^4.2.1", + "object-hash": "^1.1.2", + "safetimeout": "^0.1.1", + "spdy": "^3.4.0", + "valid-url": "^1.0.9" + }, + "deprecated": false, + "description": "A reverse proxy with support for dynamic tables", + "devDependencies": { + "chai": "^1.9.1", + "gulp": "^3.8.11", + "gulp-eslint": "^0.9.0", + "mocha": "^2.5.3" + }, + "homepage": "https://github.com/OptimalBits/redbird", + "keywords": [ + "proxy", + "reverse", + "docker", + "etcd" + ], + "license": "BSD-3-Clause-Attribution", + "main": "index.js", + "name": "redbird", + "repository": { + "type": "git", + "url": "git://github.com/OptimalBits/redbird.git" + }, + "scripts": { + "test": "mocha test/* --reporter spec" + }, + "version": "0.7.0" +} diff --git a/test/test_custom_resolver.js b/test/test_custom_resolver.js new file mode 100644 index 0000000..68489c4 --- /dev/null +++ b/test/test_custom_resolver.js @@ -0,0 +1,219 @@ +"use strict"; + +var Redbird = require('../'); +var expect = require('chai').expect; +var _ = require('lodash'); + +var opts = { + bunyan: false, + port: 10000 + Math.ceil(Math.random() * 55535) + /* { + name: 'test', + streams: [{ + path: '/dev/null', + }] + } */ +}; + + +describe("Custom Resolver", function(){ + + it("Should contain one resolver by default", function () { + + var redbird = Redbird(opts); + expect(redbird.resolvers).to.be.an('array'); + expect(redbird.resolvers.length).to.be.eq(1); + expect(redbird.resolvers[0]).to.be.eq(redbird._defaultResolver); + + redbird.close(); + }); + + it("Should register resolver with right priority", function(){ + var resolver = function () { + return 'http://127.0.0.1:8080'; + }; + + resolver.priority = 1; + + var options = _.extend({ + resolvers: resolver + }, opts); + + var redbird = Redbird(options); + + expect(redbird.resolvers.length).to.be.eq(2); + expect(redbird.resolvers[0]).to.be.eql(resolver); + + redbird.close(); + + + // test when an array is sent in as resolvers. + options.resolvers = [resolver]; + redbird = new Redbird(options); + expect(redbird.resolvers.length).to.be.eq(2); + expect(redbird.resolvers[0]).to.be.eql(resolver); + redbird.close(); + + resolver.priority = -1; + redbird = new Redbird(options); + expect(redbird.resolvers.length).to.be.eq(2); + expect(redbird.resolvers[1]).to.be.eql(resolver); + redbird.close(); + + + // test when invalid resolver is added + options.resolvers = {}; + expect(function () { + new Redbird(options) + }).to.throw(Error); + + + }); + + + it('Should add and remove resolver after launch', function () { + + var resolver = function () {}; + resolver.priority = 1; + + var redbird = Redbird(opts); + redbird.addResolver(resolver); + expect(redbird.resolvers.length).to.be.eq(2); + expect(redbird.resolvers[0]).to.be.eq(resolver); + + redbird.addResolver(resolver); + expect(redbird.resolvers.length, 'Only allows uniques.').to.be.eq(2); + + + redbird.removeResolver(resolver); + expect(redbird.resolvers.length).to.be.eq(1); + expect(redbird.resolvers[0]).to.be.eq(redbird._defaultResolver); + + redbird.close(); + + }); + + + it('Should properly convert and cache route to routeObject', function () { + + var builder = Redbird.buildRoute; + + // invalid input + expect(builder(function () {})).to.be.null; + expect(builder([])).to.be.null; + expect(builder(2016)).to.be.null; + + var testRoute = {urls: [], path: '/'}; + var testRouteResult = builder(testRoute); + expect(testRouteResult, 'For route in the default format').to.be.eq(testRoute); + expect(testRouteResult.isResolved).to.be.undefined; + + + // case string: + var testString = 'http://127.0.0.1:8888'; + var result = builder(testString); + expect(result.path).to.be.eq('/'); + expect(result.urls).to.be.an('array'); + expect(result.urls.length).to.be.eq(1); + expect(result.urls[0].hostname).to.be.eq('127.0.0.1'); + expect(result.isResolved).to.be.true; + + + var result2 = builder(testString); + expect(result2).to.be.eq(result); + + // case with object + + var testObject_1= {path:'/api', url: 'http://127.0.0.1'}, + testObjectResult_1 = builder(testObject_1); + + expect(testObjectResult_1.path).to.be.eq('/api'); + expect(testObjectResult_1.urls).to.be.an('array'); + expect(testObjectResult_1.urls.length).to.be.eq(1); + expect(testObjectResult_1.urls[0].hostname).to.be.eq('127.0.0.1'); + expect(testObjectResult_1.isResolved).to.be.true; + + + // test object caching. + var testObjectResult_2 = builder(testObject_1); + expect(testObjectResult_1).to.be.eq(testObjectResult_2); + + var testObject_2= {url: ['http://127.0.0.1', 'http://123.1.1.1']} + var testResult2 = builder(testObject_2); + expect(testResult2.urls).to.not.be.undefined; + expect(testResult2.urls.length).to.be.eq(testObject_2.url.length); + expect(testResult2.urls[0].hostname).to.be.eq('127.0.0.1'); + expect(testResult2.urls[1].hostname).to.be.eq('123.1.1.1'); + + + + }); + + it("Should resolve properly as expected", function () { + + var proxy = new Redbird(opts), resolver = function (host, url) { + return url.match(/\/ignore/i) ? null : 'http://172.12.0.1/home' + }, result; + + resolver.priority = 1; + + proxy.register('mysite.example.com', 'http://127.0.0.1:9999'); + proxy.addResolver(resolver); + + result = proxy.resolve('randomsite.example.com', '/anywhere'); + + // must match the resolver + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + expect(result.urls.length).to.be.above(0); + expect(result.urls[0].hostname).to.be.eq('172.12.0.1'); + + // expect route to match resolver even though it matches registered address + result = proxy.resolve('mysite.example.com', '/somewhere'); + expect(result.urls[0].hostname).to.be.eq('172.12.0.1'); + + // use default resolver, as custom resolver should ignore input. + result = proxy.resolve('mysite.example.com', '/ignore'); + expect(result.urls[0].hostname).to.be.eq('127.0.0.1'); + + + // make custom resolver low priority and test. + // result should match default resolver + resolver.priority = -1; + proxy.addResolver(resolver); + result = proxy.resolve('mysite.example.com', '/somewhere'); + expect(result.urls[0].hostname).to.be.eq('127.0.0.1'); + + + // both custom and default resolvers should ignore + result = proxy.resolve('somesite.example.com', '/ignore'); + expect(result).to.be.undefined; + + proxy.removeResolver(resolver); + // for path-based routing + // when resolver path doesn't match that of url, skip + + resolver = function () { + return { + path: '/notme', + url: 'http://172.12.0.1/home' + } + }; + resolver.priority = 1; + proxy.addResolver(resolver); + + result = proxy.resolve('somesite.example.com', '/notme'); + expect(result).to.not.be.undefined; + expect(result.urls[0].hostname).to.be.eq('172.12.0.1'); + + result = proxy.resolve('somesite.example.com', '/notme/somewhere'); + expect(result.urls[0].hostname).to.be.eq('172.12.0.1'); + + result = proxy.resolve('somesite.example.com', '/itsme/somewhere'); + expect(result).to.be.undefined; + + + proxy.close(); + }); + +}); diff --git a/test/test_hostheader.js b/test/test_hostheader.js new file mode 100644 index 0000000..0be90bc --- /dev/null +++ b/test/test_hostheader.js @@ -0,0 +1,141 @@ +"use strict"; + +var Redbird = require('../'); +var Promise = require('bluebird'); +var http = require('http'); +var expect = require('chai').expect; + +var TEST_PORT = 54674 +var PROXY_PORT = 53433 + +var opts = { + port: PROXY_PORT, + bunyan: false +} + +describe("Target with a hostname", function(){ + + it("Should have the host header passed to the target", function(done){ + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('127.0.0.1', '127.0.0.1.xip.io:'+TEST_PORT, { + useTargetHostHeader: true + }); + + expect(redbird.routing).to.have.property("127.0.0.1"); + + testServer().then(function(req){ + expect(req.headers['host']).to.be.eql('127.0.0.1.xip.io:'+TEST_PORT) + }) + + http.get('http://127.0.0.1:'+PROXY_PORT, function(res) { + redbird.close(); + done(); + }); + + }) + + it("Should not have the host header passed to the target", function(done){ + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('127.0.0.1', '127.0.0.1.xip.io:'+TEST_PORT); + + expect(redbird.routing).to.have.property("127.0.0.1"); + + testServer().then(function(req){ + expect(req.headers['host']).to.be.eql('127.0.0.1:'+PROXY_PORT) + }) + + http.get('http://127.0.0.1:'+PROXY_PORT, function(res) { + redbird.close(); + done(); + }); + + }) + + it("Should return 404 after route is unregister", function(done){ + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('127.0.0.1', '127.0.0.1.xip.io:'+TEST_PORT); + redbird.unregister('127.0.0.1', '127.0.0.1.xip.io:'+TEST_PORT); + + expect(redbird.routing).to.have.property("127.0.0.1"); + + testServer().then(function(req){ + expect(req.headers['host']).to.be.eql('127.0.0.1:'+PROXY_PORT) + }) + + http.get('http://127.0.0.1:'+PROXY_PORT, function(res) { + expect(res.statusCode).to.be.eql(404); + + redbird.close(); + done(); + }); + + }) + + it("Should return 502 after route with no backend", function(done){ + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('127.0.0.1', '127.0.0.1.xip.io:502'); + + expect(redbird.routing).to.have.property("127.0.0.1"); + + http.get('http://127.0.0.1:'+PROXY_PORT, function(res) { + expect(res.statusCode).to.be.eql(502); + + redbird.close(); + done(); + }); + }) +}) + +describe("Request with forwarded host header", function () { + it("should prefer forwarded hostname if desired", function () { + var redbird = Redbird({ + bunyan: false, + preferForwardedHost: true + }); + + expect(redbird.routing).to.be.an("object"); + var req = { headers: {host: '127.0.0.1', "x-forwarded-host": "subdomain.example.com"} } + + var source = redbird._getSource(req); + expect(source).to.be.eql('subdomain.example.com') + + redbird.close(); + }); + + it("should use original host if not further specified", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + var req = { headers: { host: '127.0.0.1', "x-forwarded-host": "subdomain.example.com"} } + + var source = redbird._getSource(req); + expect(source).to.be.eql('127.0.0.1') + + redbird.close(); + }); +}); + +function testServer(){ + return new Promise(function(resolve, reject){ + var server = http.createServer(function(req, res){ + res.write(""); + res.end(); + resolve(req); + server.close(); + }); + + server.listen(TEST_PORT); + }) +} diff --git a/test/test_pathnames.js b/test/test_pathnames.js new file mode 100644 index 0000000..7a72256 --- /dev/null +++ b/test/test_pathnames.js @@ -0,0 +1,75 @@ +"use strict"; + +var Redbird = require('../'); +var Promise = require('bluebird'); +var http = require('http'); +var expect = require('chai').expect; + +var TEST_PORT = 54673 +var PROXY_PORT = 53432 + +var opts = { + port: PROXY_PORT, + bunyan: false /* { + name: 'test', + streams: [{ + path: '/dev/null', + }] + } */ +} + +describe("Target with pathnames", function(){ + + it("Should be proxyed to target with pathname and source pathname concatenated", function(done){ + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('127.0.0.1', '127.0.0.1:'+TEST_PORT+'/foo/bar/qux'); + + expect(redbird.routing).to.have.property("127.0.0.1"); + + testServer().then(function(req){ + expect(req.url).to.be.eql('/foo/bar/qux/a/b/c') + }) + + http.get('http://127.0.0.1:'+PROXY_PORT+'/a/b/c', function(res) { + redbird.close(); + done(); + }); + + }) + + it("Should be proxyed to target with pathname and source pathname concatenated case 2", function(done){ + var redbird = new Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('127.0.0.1/path', '127.0.0.1:'+TEST_PORT+'/foo/bar/qux'); + + expect(redbird.routing).to.have.property("127.0.0.1"); + + testServer().then(function(req){ + expect(req.url).to.be.eql('/foo/bar/qux/a/b/c') + }) + + http.get('http://127.0.0.1:'+PROXY_PORT+'/path/a/b/c', function(err, res) { + redbird.close(); + done(); + }); + + }) +}) + + +function testServer(){ + return new Promise(function(resolve, reject){ + var server = http.createServer(function(req, res){ + res.write(""); + res.end(); + resolve(req); + server.close(); + }); + server.listen(TEST_PORT); + }) +} diff --git a/test/test_register.js b/test/test_register.js new file mode 100644 index 0000000..2e796dc --- /dev/null +++ b/test/test_register.js @@ -0,0 +1,421 @@ +"use strict"; + +var Redbird = require('../'); +var expect = require('chai').expect; + +var opts = { + bunyan: false /* { + name: 'test', + streams: [{ + path: '/dev/null', + }] + } */ +} + +describe("Route registration", function () { + it("should register a simple route", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + + expect(redbird.routing).to.have.property("example.com") + + expect(redbird.resolve('example.com')).to.be.an("object"); + + var host = redbird.routing["example.com"]; + expect(host).to.be.an("array"); + expect(host[0]).to.have.property('path') + expect(host[0].path).to.be.eql('/'); + expect(host[0].urls).to.be.an('array'); + expect(host[0].urls.length).to.be.eql(1); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.2:8080/'); + + redbird.unregister('example.com', '192.168.1.2:8080'); + expect(redbird.resolve('example.com')).to.be.an("undefined") + redbird.close(); + }); + + it("should resolve domains as case insensitive", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + + var target = redbird.resolve('Example.com'); + expect(target).to.be.an("object"); + expect(target.urls[0].hostname).to.be.equal('192.168.1.2'); + + redbird.close(); + }); + + + it("should register multiple routes", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example1.com', '192.168.1.2:8080'); + redbird.register('example2.com', '192.168.1.3:8081'); + redbird.register('example3.com', '192.168.1.4:8082'); + redbird.register('example4.com', '192.168.1.5:8083'); + redbird.register('example5.com', '192.168.1.6:8084'); + + expect(redbird.routing).to.have.property("example1.com") + expect(redbird.routing).to.have.property("example2.com") + expect(redbird.routing).to.have.property("example3.com") + expect(redbird.routing).to.have.property("example4.com") + expect(redbird.routing).to.have.property("example5.com") + + var host; + + host = redbird.routing["example1.com"]; + expect(host[0].path).to.be.eql('/'); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.2:8080/'); + + host = redbird.routing["example2.com"]; + expect(host[0].path).to.be.eql('/'); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.3:8081/'); + + host = redbird.routing["example3.com"]; + expect(host[0].path).to.be.eql('/'); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.4:8082/'); + + host = redbird.routing["example4.com"]; + expect(host[0].path).to.be.eql('/'); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.5:8083/'); + + host = redbird.routing["example5.com"]; + expect(host[0].path).to.be.eql('/'); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.6:8084/'); + + redbird.unregister('example1.com'); + expect(redbird.resolve('example1.com')).to.be.an("undefined") + + redbird.unregister('example2.com'); + expect(redbird.resolve('example2.com')).to.be.an("undefined") + + redbird.unregister('example3.com'); + expect(redbird.resolve('example3.com')).to.be.an("undefined") + + redbird.unregister('example4.com'); + expect(redbird.resolve('example4.com')).to.be.an("undefined") + + redbird.unregister('example5.com'); + expect(redbird.resolve('example5.com')).to.be.an("undefined") + + + redbird.close(); + }) + it("should register several pathnames within a route", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com/qux/baz', '192.168.1.5:8080'); + redbird.register('example.com/foo', '192.168.1.3:8080'); + redbird.register('example.com/bar', '192.168.1.4:8080'); + + expect(redbird.routing).to.have.property("example.com") + + var host = redbird.routing["example.com"]; + expect(host).to.be.an("array"); + expect(host[0]).to.have.property('path') + expect(host[0].path).to.be.eql('/qux/baz'); + expect(host[0].urls).to.be.an('array'); + expect(host[0].urls.length).to.be.eql(1); + expect(host[0].urls[0].href).to.be.eql('http://192.168.1.5:8080/'); + + expect(host[0].path.length).to.be.least(host[1].path.length) + expect(host[1].path.length).to.be.least(host[2].path.length) + expect(host[2].path.length).to.be.least(host[3].path.length) + + redbird.unregister('example.com'); + expect(redbird.resolve('example.com')).to.be.an("undefined") + + expect(redbird.resolve('example.com', '/foo')).to.be.an("object") + + redbird.unregister('example.com/foo'); + expect(redbird.resolve('example.com', '/foo')).to.be.an("undefined") + + redbird.close(); + }) + it("shouldnt crash process in unregister of unregisted host", function (done) { + var redbird = Redbird(opts); + + redbird.unregister('example.com'); + + done() + + redbird.close(); + }) +}) + +describe("Route resolution", function () { + it("should resolve to a correct route", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com/qux/baz', '192.168.1.5:8080'); + redbird.register('example.com/foo', '192.168.1.3:8080'); + redbird.register('example.com/bar', '192.168.1.4:8080'); + redbird.register('example.com/foo/baz', '192.168.1.3:8080'); + + var route = redbird.resolve('example.com', '/foo/asd/1/2'); + expect(route.path).to.be.eql('/foo') + expect(route.urls.length).to.be.eql(1); + expect(route.urls[0].href).to.be.eql('http://192.168.1.3:8080/'); + + redbird.close(); + }) + + it("should resolve to a correct route with complex path", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com/qux/baz', '192.168.1.5:8080'); + redbird.register('example.com/foo', '192.168.1.3:8080'); + redbird.register('example.com/bar', '192.168.1.4:8080'); + redbird.register('example.com/foo/baz', '192.168.1.7:8080'); + + var route = redbird.resolve('example.com', '/foo/baz/a/b/c'); + + expect(route.path).to.be.eql('/foo/baz') + expect(route.urls.length).to.be.eql(1); + expect(route.urls[0].href).to.be.eql('http://192.168.1.7:8080/'); + + redbird.close(); + }) + + it("should resolve to undefined if route not available", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com/qux/baz', '192.168.1.5:8080'); + redbird.register('example.com/foo', '192.168.1.3:8080'); + redbird.register('foobar.com/bar', '192.168.1.4:8080'); + redbird.register('foobar.com/foo/baz', '192.168.1.3:8080'); + + var route = redbird.resolve('wrong.com'); + expect(route).to.be.an('undefined') + + var route = redbird.resolve('foobar.com'); + expect(route).to.be.an('undefined') + + redbird.close(); + }) + + it("should get a target if route available", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com/qux/baz', '192.168.1.5:8080'); + redbird.register('example.com/foo', '192.168.1.3:8080'); + redbird.register('foobar.com/bar', '192.168.1.4:8080'); + redbird.register('foobar.com/foo/baz', '192.168.1.7:8080'); + redbird.register('foobar.com/media', '192.168.1.7:8080'); + + var route = redbird.resolve('example.com', '/qux/a/b/c'); + expect(route.path).to.be.eql('/'); + + var route = redbird.resolve('foobar.com', '/medias/'); + expect(route).to.be.undefined; + + var route = redbird.resolve('foobar.com', '/mediasa'); + expect(route).to.be.undefined; + + var route = redbird.resolve('foobar.com', '/media/sa'); + expect(route.path).to.be.eql('/media'); + + var target = redbird._getTarget('example.com', { url: '/foo/baz/a/b/c' }); + expect(target.href).to.be.eql('http://192.168.1.3:8080/') + + redbird.close(); + }) + + it("should get a target with path when necessary", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com/qux/baz', '192.168.1.5:8080'); + redbird.register('example.com/foo', '192.168.1.3:8080/a/b'); + redbird.register('foobar.com/bar', '192.168.1.4:8080'); + redbird.register('foobar.com/foo/baz', '192.168.1.7:8080'); + + var route = redbird.resolve('example.com', '/qux/a/b/c'); + expect(route.path).to.be.eql('/'); + + var req = { url: '/foo/baz/a/b/c' } + var target = redbird._getTarget('example.com', req); + expect(target.href).to.be.eql('http://192.168.1.3:8080/a/b') + expect(req.url).to.be.eql('/a/b/baz/a/b/c') + + redbird.close(); + }) +}) + +describe("TLS/SSL", function () { + it("should allow TLS/SSL certificates", function () { + var redbird = Redbird({ + ssl: { + port: 4430 + }, + bunyan: false + }); + + expect(redbird.routing).to.be.an("object"); + redbird.register('example.com', '192.168.1.1:8080', { + ssl: { + key: 'dummy', + cert: 'dummy' + } + }); + + redbird.register('example.com', '192.168.1.2:8080'); + + expect(redbird.certs).to.be.an("object"); + expect(redbird.certs['example.com']).to.be.an("object"); + + redbird.unregister('example.com', '192.168.1.1:8080'); + expect(redbird.resolve('example.com')).to.not.be.an("undefined") + expect(redbird.certs['example.com']).to.not.be.an("undefined"); + + redbird.unregister('example.com', '192.168.1.2:8080'); + expect(redbird.resolve('example.com')).to.be.an("undefined") + expect(redbird.certs['example.com']).to.be.an("undefined"); + + }) + it('Should bind https servers to different ip addresses', function(testDone) { + + var isPortTaken = function(port, ip, done) { + var net = require('net') + var tester = net.createServer() + .once('error', function (err) { + if (err.code != 'EADDRINUSE') return done(err) + done(null, true) + }) + .once('listening', function() { + tester.once('close', function() { done(null, false) }) + .close() + }) + .listen(port, ip) + } + + var redbird = Redbird({ + bunyan: false, + port: 8080, + + // Specify filenames to default SSL certificates (in case SNI is not supported by the + // user's browser) + ssl: [ + { + port: 4433, + key: 'dummy', + cert: 'dummy', + ip: '127.0.0.1' + }, + { + port: 4434, + key: 'dummy', + cert: 'dummy', + ip: '127.0.0.1' + } + ] + }); + + redbird.register('mydomain.com', 'http://127.0.0.1:8001', { + ssl: { + key: 'dummy', + cert: 'dummy', + ca: 'dummym' + } + }); + + var portsTaken = 0; + var portsChecked = 0; + + function portsTakenDone(err, taken) { + portsChecked++; + if (err) { throw err; } + if (taken) { portsTaken++; } + if ( portsChecked == 2 ) { + portsCheckDone(); + } + } + + function portsCheckDone() { + expect(portsTaken).to.be.eql(2); + redbird.close(); + testDone(); + } + + isPortTaken(4433, '127.0.0.1', portsTakenDone); + isPortTaken(4434, '127.0.0.1', portsTakenDone); + }); +}) + + +describe("Load balancing", function () { + it("should load balance between several targets", function () { + var redbird = Redbird(opts); + + expect(redbird.routing).to.be.an("object"); + + redbird.register('example.com', '192.168.1.1:8080'); + redbird.register('example.com', '192.168.1.2:8080'); + redbird.register('example.com', '192.168.1.3:8080'); + redbird.register('example.com', '192.168.1.4:8080'); + + expect(redbird.routing['example.com'][0].urls.length).to.be.eql(4); + expect(redbird.routing['example.com'][0].rr).to.be.eql(0); + + var route = redbird.resolve('example.com', '/foo/qux/a/b/c'); + expect(route.urls.length).to.be.eql(4); + + for (var i = 0; i < 1000; i++) { + var target = redbird._getTarget('example.com', { url: '/a/b/c' }); + expect(target.href).to.be.eql('http://192.168.1.1:8080/') + expect(redbird.routing['example.com'][0].rr).to.be.eql(1); + + var target = redbird._getTarget('example.com', { url: '/x/y' }); + expect(target.href).to.be.eql('http://192.168.1.2:8080/') + expect(redbird.routing['example.com'][0].rr).to.be.eql(2); + + var target = redbird._getTarget('example.com', { url: '/j' }); + expect(target.href).to.be.eql('http://192.168.1.3:8080/') + expect(redbird.routing['example.com'][0].rr).to.be.eql(3); + + var target = redbird._getTarget('example.com', { url: '/k/' }); + expect(target.href).to.be.eql('http://192.168.1.4:8080/') + expect(redbird.routing['example.com'][0].rr).to.be.eql(0); + } + + redbird.unregister('example.com', '192.168.1.1:8080'); + expect(redbird.resolve('example.com')).to.not.be.an("undefined") + + redbird.unregister('example.com', '192.168.1.2:8080'); + expect(redbird.resolve('example.com')).to.not.be.an("undefined") + + redbird.unregister('example.com', '192.168.1.3:8080'); + expect(redbird.resolve('example.com')).to.not.be.an("undefined") + + redbird.unregister('example.com', '192.168.1.4:8080'); + expect(redbird.resolve('example.com')).to.be.an("undefined") + + + redbird.close(); + }); +});