FirstCommit

This commit is contained in:
Fabian Stamm 2018-06-29 15:47:19 +02:00
commit 74492e3f6b
52 changed files with 3765 additions and 0 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules

22
.eslintrc Normal file
View File

@ -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"]

0
.npmignore Normal file
View File

4
.travis.yml Normal file
View File

@ -0,0 +1,4 @@
language: node_js
node_js:
- '4.5'

30
.vscode/launch.json vendored Normal file
View File

@ -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
}
]
}

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM node:4.5
ADD . /proxy
RUN cd /proxy; npm install --production
EXPOSE 8080

24
LICENSE Normal file
View File

@ -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.

564
README.md Normal file
View File

@ -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)
<a name="redbird"/>
### 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')
}
```
---------------------------------------
<a name="register"/>
#### 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
}
}
```
---------------------------------------
<a name="unregister"/>
#### 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.
```
---------------------------------------
<a name="notFound"/>
#### 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.
```
---------------------------------------
<a name="close"/>
#### Redbird.close()
Close the proxy stopping all the incoming connections.
---------------------------------------

36
gulpfile.js Normal file
View File

@ -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());
});

52
hl-tests/64/proxy.js Normal file
View File

@ -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);

36
hl-tests/letsencrypt/a.js Normal file
View File

@ -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);

View File

@ -0,0 +1 @@
{"creation_host":"Manuels-MacBook-Pro.local","creation_dt":"2016-08-30T15:22:20.371Z"}

View File

@ -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"}

View File

@ -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"}}

View File

@ -0,0 +1 @@
{"creation_host":"Manuels-MBP","creation_dt":"2016-09-18T14:25:30.785Z"}

View File

@ -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"}

View File

@ -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"}}

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -0,0 +1 @@
hello masda!

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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 = <function obtain_cert at 0x30c9500>
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

View File

@ -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 = <function obtain_cert at 0x30c9500>
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

View File

@ -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);

19
hl-tests/paths.js Normal file
View File

@ -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);
});
}

5
index.js Normal file
View File

@ -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');

162
lib/docker.js Normal file
View File

@ -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;

93
lib/etcd-backend.js Normal file
View File

@ -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;

137
lib/letsencrypt.js Normal file
View File

@ -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;

780
lib/proxy.js Normal file
View File

@ -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;

109
lib/redis-backend.js Normal file
View File

@ -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;

75
package.json Normal file
View File

@ -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"
}

View File

@ -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();
});
});

141
test/test_hostheader.js Normal file
View File

@ -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);
})
}

75
test/test_pathnames.js Normal file
View File

@ -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);
})
}

421
test/test_register.js Normal file
View File

@ -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();
});
});