tg-redbird 0.12.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +23 -16
  2. package/lib/docker.js +22 -18
  3. package/lib/etcd-backend.js +32 -35
  4. package/lib/letsencrypt.js +41 -38
  5. package/lib/proxy.js +197 -238
  6. package/lib/redis-backend.js +84 -79
  7. package/package.json +17 -4
  8. package/.dockerignore +0 -1
  9. package/.eslintrc +0 -22
  10. package/.travis.yml +0 -21
  11. package/.vscode/launch.json +0 -30
  12. package/Dockerfile +0 -6
  13. package/gulpfile.js +0 -36
  14. package/hl-tests/64/proxy.js +0 -52
  15. package/hl-tests/letsencrypt/a.js +0 -36
  16. package/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/meta.json +0 -1
  17. package/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/private_key.json +0 -1
  18. package/hl-tests/letsencrypt/certs/accounts/acme-staging.api.letsencrypt.org/directory/367e0270a5d31ab031561f9f284ca350/regr.json +0 -1
  19. package/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/meta.json +0 -1
  20. package/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/private_key.json +0 -1
  21. package/hl-tests/letsencrypt/certs/accounts/acme-v01.api.letsencrypt.org/directory/49881ab35d6ac7bb51f05ca3a220fbac/regr.json +0 -1
  22. package/hl-tests/letsencrypt/certs/api/privkey.pem +0 -27
  23. package/hl-tests/letsencrypt/certs/api.com/privkey.pem +0 -27
  24. package/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/cert0.pem +0 -29
  25. package/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/chain0.pem +0 -27
  26. package/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/fullchain0.pem +0 -56
  27. package/hl-tests/letsencrypt/certs/archive/caturra.exactbytes.com/privkey0.pem +0 -27
  28. package/hl-tests/letsencrypt/certs/caturra.exactbytes.com/cert.pem +0 -29
  29. package/hl-tests/letsencrypt/certs/caturra.exactbytes.com/chain.pem +0 -27
  30. package/hl-tests/letsencrypt/certs/caturra.exactbytes.com/fullchain.pem +0 -56
  31. package/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem +0 -27
  32. package/hl-tests/letsencrypt/certs/caturra.exactbytes.com/privkey.pem.bak +0 -27
  33. package/hl-tests/letsencrypt/certs/dash/.well-known/acme-challenge/abc +0 -1
  34. package/hl-tests/letsencrypt/certs/dash/privkey.pem +0 -27
  35. package/hl-tests/letsencrypt/certs/dash.com/privkey.pem +0 -27
  36. package/hl-tests/letsencrypt/certs/dash_/privkey.pem +0 -27
  37. package/hl-tests/letsencrypt/certs/dev-cert.pem +0 -17
  38. package/hl-tests/letsencrypt/certs/dev-csr.pem +0 -13
  39. package/hl-tests/letsencrypt/certs/dev-key.pem +0 -15
  40. package/hl-tests/letsencrypt/certs/example.com/privkey.pem +0 -27
  41. package/hl-tests/letsencrypt/certs/localhost/privkey.pem +0 -27
  42. package/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf +0 -67
  43. package/hl-tests/letsencrypt/certs/renewal/caturra.exactbytes.com.conf.bak +0 -68
  44. package/hl-tests/letsencrypt/proxy.js +0 -73
  45. package/hl-tests/paths.js +0 -19
  46. package/package-lock.json +0 -3269
  47. package/test/test_custom_resolver.js +0 -276
  48. package/test/test_hostheader.js +0 -141
  49. package/test/test_pathnames.js +0 -75
  50. package/test/test_register.js +0 -493
package/README.md CHANGED
@@ -83,6 +83,9 @@ proxy.register("balance.me", "http://172.17.41.6:8080");
83
83
  proxy.register("balance.me", "http://172.17.42.6:8080");
84
84
  proxy.register("balance.me", "http://172.17.43.6:8080");
85
85
 
86
+ // You can unregister routes as well
87
+ proxy.register("temporary.com", "http://172.17.45.1:8004");
88
+ proxy.unregister("temporary.com", "http://172.17.45.1:8004");
86
89
 
87
90
  // LetsEncrypt support
88
91
  // With Redbird you can get zero conf and automatic SSL certificates for your domains
@@ -105,7 +108,7 @@ redbird.register('example.com', 'http://172.60.80.2:8082', {
105
108
  // HTTP2 Support using LetsEncrypt for the certificates
106
109
  //
107
110
  var proxy = require('redbird')({
108
- port: 80, // http port is needed for LetsEncrypt challenge during request / renewal. Also enables automatic http->https redirection for registered https routes.
111
+ port: 80, // http port is needed for LetsEncrypt challenge during request / renewal. Also enables automatic http->https redirection for registered https routes.
109
112
  letsencrypt: {
110
113
  path: __dirname + '/certs',
111
114
  port: 9999 // LetsEncrypt minimal web server port for handling challenges. Routed 80->9999, no need to open 9999 in firewall. Default 3000 if not defined.
@@ -235,7 +238,7 @@ var redbird = new require('redbird')({
235
238
  ip: '123.45.67.11', // assigned to my-other-domain.com
236
239
  key: 'certs/my-other-domain.key',
237
240
  cert: 'certs/my-other-domain.crt',
238
- }
241
+ }
239
242
  ]
240
243
  });
241
244
 
@@ -402,33 +405,33 @@ setTimeout(function() {
402
405
 
403
406
  ## Replacing the default HTTP/HTTPS server modules
404
407
 
405
- By passing `serverModule: module` or `ssl: {serverModule : module}` you can override the default http/https
408
+ By passing `serverModule: module` or `ssl: {serverModule : module}` you can override the default http/https
406
409
  servers used to listen for connections with another module.
407
410
 
408
- One application for this is to enable support for PROXY protocol: This is useful if you want to use a module like
409
- [findhit-proxywrap](https://github.com/findhit/proxywrap) to enable support for the
411
+ One application for this is to enable support for PROXY protocol: This is useful if you want to use a module like
412
+ [findhit-proxywrap](https://github.com/findhit/proxywrap) to enable support for the
410
413
  [PROXY protocol](http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt).
411
-
412
-
413
- PROXY protocol is used in tools like HA-Proxy, and can be optionally enabled in Amazon ELB load balancers to pass the
414
- original client IP when proxying TCP connections (similar to an X-Forwarded-For header, but for raw TCP). This is useful
415
- if you want to run redbird on AWS behind an ELB load balancer, but have redbird terminate any HTTPS connections so you
416
- can have SNI/Let's Encrypt/HTTP2support. With this in place Redbird will see the client's IP address rather
414
+
415
+
416
+ PROXY protocol is used in tools like HA-Proxy, and can be optionally enabled in Amazon ELB load balancers to pass the
417
+ original client IP when proxying TCP connections (similar to an X-Forwarded-For header, but for raw TCP). This is useful
418
+ if you want to run redbird on AWS behind an ELB load balancer, but have redbird terminate any HTTPS connections so you
419
+ can have SNI/Let's Encrypt/HTTP2support. With this in place Redbird will see the client's IP address rather
417
420
  than the load-balancer's, and pass this through in an X-Forwarded-For header.
418
421
 
419
422
  ````javascript
420
423
  //Options for proxywrap. This means the proxy will also respond to regular HTTP requests without PROXY information as well.
421
- proxy_opts = {strict: false};
424
+ proxy_opts = {strict: false};
422
425
  proxyWrap = require('findhit-proxywrap');
423
426
  var opts = {
424
427
  port: process.env.HTTP_PORT,
425
428
  serverModule: proxyWrap.proxy( require('http'), proxy_opts),
426
429
  ssl: {
427
430
  //Do this if you want http2:
428
- http2: true,
431
+ http2: true,
429
432
  serverModule: proxyWrap.proxy(require('spdy').server, proxy_opts),
430
433
  //Do this if you only want regular https
431
- // serverModule: proxyWrap.proxy( require('http'), proxy_opts),
434
+ // serverModule: proxyWrap.proxy( require('http'), proxy_opts),
432
435
  port: process.env.HTTPS_PORT,
433
436
  }
434
437
  }
@@ -481,7 +484,7 @@ __Arguments__
481
484
  If you want to disable bunyan, just set this option to false. Keep in mind that
482
485
  having logs enabled incours in a performance penalty of about one order of magnitude per request.
483
486
  resolvers: {Function | Array} a list of custom resolvers. Can be a single function or an array of functions. See more details about resolvers above.
484
- serverModule : {Module} Optional - Override the http server module used to listen for http connections. Default is require('http')
487
+ serverModule : {Module} Optional - Override the http server module used to listen for http connections. Default is require('http')
485
488
  }
486
489
  ```
487
490
 
@@ -509,9 +512,13 @@ __Arguments__
509
512
  key: keyPath,
510
513
  cert: certPath,
511
514
  ca: caPath, // optional
512
- secureOptions: constants.SSL_OP_NO_TLSv1 //optional, see below
515
+ secureOptions: constants.SSL_OP_NO_TLSv1 //optional, see below
513
516
  }
514
517
  }
518
+ {onRequest: (req, res, target) => {
519
+ // called before forwarding is occurred, you can modify req.headers for example
520
+ // return undefined to forward to default target
521
+ }}
515
522
  ```
516
523
  > _Note: if you need to use **ssl.secureOptions**, to disable older, insecure TLS versions, import crypto/constants first:_
517
524
 
package/lib/docker.js CHANGED
@@ -20,27 +20,27 @@ function DockerModule(redbird, url) {
20
20
  this.redbird = redbird;
21
21
  var log = redbird.log;
22
22
 
23
- var targets = this.targets = {};
23
+ var targets = (this.targets = {});
24
24
  this.ports = {};
25
25
 
26
26
  // We keep an up-to-date table with all the images having
27
27
  // containers running on the system.
28
- var images = this.images = {};
29
- var dolphin = this.dolphin = new Dolphin(url);
28
+ var images = (this.images = {});
29
+ var dolphin = (this.dolphin = new Dolphin(url));
30
30
 
31
31
  var _this = this;
32
32
 
33
33
  function registerIfNeeded(imageName, containerId, containerName) {
34
- var image = images[imageName] = images[imageName] || {};
34
+ var image = (images[imageName] = images[imageName] || {});
35
35
 
36
36
  for (var targetName in targets) {
37
- var match = isMatchingImageName(targetName, imageName);
37
+ var match = isMatchingImageName(targetName, imageName);
38
38
 
39
- if (match && image[containerId] !== 'running') {
40
- var target = targets[targetName];
41
- log && log.info('Registering container %s for target %s', containerName, target.src);
42
- _this.registerContainer(target.src, containerId, target.opts);
43
- }
39
+ if (match && image[containerId] !== 'running') {
40
+ var target = targets[targetName];
41
+ log && log.info('Registering container %s for target %s', containerName, target.src);
42
+ _this.registerContainer(target.src, containerId, target.opts);
43
+ }
44
44
  }
45
45
 
46
46
  image[containerId] = 'running';
@@ -50,13 +50,12 @@ function DockerModule(redbird, url) {
50
50
  this.events = dolphin.events();
51
51
 
52
52
  this.events.on('connected', function () {
53
-
54
53
  // Fetch all running containers and register them if
55
54
  // necessary.
56
- dolphin.containers({ filters: {status:["running"]}}).then(function (containers) {
55
+ dolphin.containers({ filters: { status: ['running'] } }).then(function (containers) {
57
56
  for (var i = 0; i < containers.length; i++) {
58
57
  var container = containers[i];
59
- registerIfNeeded(container.Image, container.Id, container.Names[0].replace("/", ""));
58
+ registerIfNeeded(container.Image, container.Id, container.Names[0].replace('/', ''));
60
59
  }
61
60
  });
62
61
  });
@@ -81,7 +80,12 @@ function DockerModule(redbird, url) {
81
80
  var match = isMatchingImageName(targetName, evt.from);
82
81
  if (image[evt.id] === 'running' && match && _this.ports[evt.id]) {
83
82
  target = targets[targetName];
84
- log && log.info('Un-registering container %s for target %s', evt.Actor.Attributes.name, target.src);
83
+ log &&
84
+ log.info(
85
+ 'Un-registering container %s for target %s',
86
+ evt.Actor.Attributes.name,
87
+ target.src
88
+ );
85
89
  _this.redbird.unregister(target.src, _this.ports[evt.id]);
86
90
  }
87
91
  image[evt.id] = 'stopped';
@@ -112,12 +116,12 @@ DockerModule.prototype.register = function (src, target, opts) {
112
116
  var storedTarget = this.targets[target];
113
117
 
114
118
  if (storedTarget && storedTarget.src == src) {
115
- throw Error('Cannot register the same src and target twice');
119
+ throw Error('Cannot register the same src and target twice');
116
120
  }
117
121
 
118
122
  this.targets[target] = {
119
123
  src: src,
120
- opts: opts
124
+ opts: opts,
121
125
  };
122
126
 
123
127
  for (var imageName in this.images) {
@@ -138,8 +142,8 @@ DockerModule.prototype.registerContainer = function (src, containerId, opts) {
138
142
  };
139
143
 
140
144
  function isMatchingImageName(targetName, imageName) {
141
- var regex = new RegExp("^" + targetName + "$");
142
- return regex.test(imageName);
145
+ var regex = new RegExp('^' + targetName + '$');
146
+ return regex.test(imageName);
143
147
  }
144
148
 
145
149
  function containerPort(dolphin, containerId) {
@@ -7,8 +7,8 @@
7
7
  */
8
8
  var Etcd = require('node-etcd');
9
9
 
10
- function ETCDModule(redbird, options){
11
- if (!(this instanceof ETCDModule)){
10
+ function ETCDModule(redbird, options) {
11
+ if (!(this instanceof ETCDModule)) {
12
12
  return new ETCDModule(redbird, options);
13
13
  }
14
14
 
@@ -18,37 +18,34 @@ function ETCDModule(redbird, options){
18
18
  var _this = this;
19
19
 
20
20
  // Create node-etcd Instance
21
- this.etcd = new Etcd(options.hosts,options.ssloptions);
22
- this.etcd_dir = (typeof options.path !== 'undefined') ? options.path : "redbird";
21
+ this.etcd = new Etcd(options.hosts, options.ssloptions);
22
+ this.etcd_dir = typeof options.path !== 'undefined' ? options.path : 'redbird';
23
23
 
24
24
  // Create directory if not created
25
- this.etcd.get(this.etcd_dir,function(err, body, header){
26
- if (err && err.errorCode == 100){
27
- _this.etcd.mkdir(_this.etcd_dir, function(err){
28
- if (err){
25
+ this.etcd.get(this.etcd_dir, function (err, body, header) {
26
+ if (err && err.errorCode == 100) {
27
+ _this.etcd.mkdir(_this.etcd_dir, function (err) {
28
+ if (err) {
29
29
  log.error(err, 'etcd backend error');
30
- }
31
- else{
30
+ } else {
32
31
  createWatcher();
33
32
  }
34
33
  });
35
- }
36
- else if(!err && body.node.dir){
34
+ } else if (!err && body.node.dir) {
37
35
  createWatcher();
38
- }
39
- else{
36
+ } else {
40
37
  log.error(err, 'etcd backend error');
41
38
  }
42
39
  });
43
40
 
44
41
  // Helper function to check if values contain settings
45
42
  function IsJsonString(str) {
46
- try {
47
- JSON.parse(str);
48
- } catch (e) {
49
- return false;
50
- }
51
- return true;
43
+ try {
44
+ JSON.parse(str);
45
+ } catch (e) {
46
+ return false;
47
+ }
48
+ return true;
52
49
  }
53
50
 
54
51
  // Helper function to pretify etcd directory strings
@@ -56,35 +53,35 @@ function ETCDModule(redbird, options){
56
53
  return str.replace(_this.etcd_dir, '').replace(/^\/+|\/+$/g, '');
57
54
  }
58
55
 
59
- function createWatcher(){
56
+ function createWatcher() {
60
57
  // Watch etcd directory
61
- _this.watcher = _this.etcd.watcher(_this.etcd_dir, null, {recursive:true});
58
+ _this.watcher = _this.etcd.watcher(_this.etcd_dir, null, { recursive: true });
62
59
 
63
60
  // On Add/Update
64
- _this.watcher.on("change", function(body,headers){
65
- if(body.node.key && body.node.value && !IsJsonString(body.node.value)){
66
- _this.redbird.register(removeEtcDir(body.node.key),body.node.value);
67
- }
68
- else if(body.node.key && body.node.value && IsJsonString(body.node.value)){
61
+ _this.watcher.on('change', function (body, headers) {
62
+ if (body.node.key && body.node.value && !IsJsonString(body.node.value)) {
63
+ _this.redbird.register(removeEtcDir(body.node.key), body.node.value);
64
+ } else if (body.node.key && body.node.value && IsJsonString(body.node.value)) {
69
65
  var config = JSON.parse(body.node.value);
70
- if (typeof config.docker !== 'undefined'){
71
- require('../').docker(_this.redbird).register(body.node.key,body.node.value.docker,body.node.value);
72
- }
73
- else {
74
- _this.redbird.register(removeEtcDir(body.node.key),config.hosts,config);
66
+ if (typeof config.docker !== 'undefined') {
67
+ require('../')
68
+ .docker(_this.redbird)
69
+ .register(body.node.key, body.node.value.docker, body.node.value);
70
+ } else {
71
+ _this.redbird.register(removeEtcDir(body.node.key), config.hosts, config);
75
72
  }
76
73
  }
77
74
  });
78
75
 
79
76
  // On Delete
80
- _this.watcher.on("delete", function(body,headers){
81
- if(body.node.key){
77
+ _this.watcher.on('delete', function (body, headers) {
78
+ if (body.node.key) {
82
79
  _this.redbird.unregister(removeEtcDir(body.node.key));
83
80
  }
84
81
  });
85
82
 
86
83
  // Handle Errors
87
- _this.watcher.on("error", function(err){
84
+ _this.watcher.on('error', function (err) {
88
85
  log.error(err, 'etcd backend error');
89
86
  });
90
87
  }
@@ -17,7 +17,7 @@
17
17
  var leStoreConfig = {};
18
18
  var webrootPath = ':configDir/:hostname/.well-known/acme-challenge';
19
19
 
20
- function init(certPath, port, logger){
20
+ function init(certPath, port, logger) {
21
21
  var http = require('http');
22
22
  var path = require('path');
23
23
  var url = require('url');
@@ -36,37 +36,38 @@ function init(certPath, port, logger){
36
36
  logsDir: ':configDir/letsencrypt/var/log',
37
37
 
38
38
  webrootPath: webrootPath,
39
- debug: false
39
+ debug: false,
40
40
  };
41
41
 
42
42
  // we need to proxy for example: 'example.com/.well-known/acme-challenge' -> 'localhost:port/example.com/'
43
- http.createServer(function (req, res){
44
- var uri = url.parse(req.url).pathname;
45
- var filename = path.join(certPath, uri);
46
- var isForbiddenPath = uri.length < 3 || filename.indexOf(certPath) !== 0;
47
-
48
- if (isForbiddenPath) {
49
- logger && logger.info('Forbidden request on LetsEncrypt port %s: %s', port, filename);
50
- res.writeHead(403);
51
- res.end();
52
- return;
53
- }
54
-
55
- logger && logger.info('LetsEncrypt CA trying to validate challenge %s', filename);
56
-
57
- fs.stat(filename, function(err, stats) {
58
- if (err || !stats.isFile()) {
59
- res.writeHead(404, {"Content-Type": "text/plain"});
60
- res.write("404 Not Found\n");
43
+ http
44
+ .createServer(function (req, res) {
45
+ var uri = url.parse(req.url).pathname;
46
+ var filename = path.join(certPath, uri);
47
+ var isForbiddenPath = uri.length < 3 || filename.indexOf(certPath) !== 0;
48
+
49
+ if (isForbiddenPath) {
50
+ logger && logger.info('Forbidden request on LetsEncrypt port %s: %s', port, filename);
51
+ res.writeHead(403);
61
52
  res.end();
62
53
  return;
63
54
  }
64
55
 
65
- res.writeHead(200);
66
- fs.createReadStream(filename, "binary").pipe(res);
67
- });
56
+ logger && logger.info('LetsEncrypt CA trying to validate challenge %s', filename);
57
+
58
+ fs.stat(filename, function (err, stats) {
59
+ if (err || !stats.isFile()) {
60
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
61
+ res.write('404 Not Found\n');
62
+ res.end();
63
+ return;
64
+ }
68
65
 
69
- }).listen(port);
66
+ res.writeHead(200);
67
+ fs.createReadStream(filename, 'binary').pipe(res);
68
+ });
69
+ })
70
+ .listen(port);
70
71
  }
71
72
 
72
73
  /**
@@ -77,7 +78,7 @@ function init(certPath, port, logger){
77
78
  * TODO: We should use something like https://github.com/PaquitoSoft/memored/blob/master/index.js
78
79
  * to avoid
79
80
  */
80
- function getCertificates(domain, email, production, renew, logger){
81
+ function getCertificates(domain, email, production, renew, logger) {
81
82
  var LE = require('greenlock');
82
83
  var le;
83
84
 
@@ -87,35 +88,37 @@ function getCertificates(domain, email, production, renew, logger){
87
88
  // ACME Challenge Handlers
88
89
  var leChallenge = require('le-challenge-fs').create({
89
90
  webrootPath: webrootPath,
90
- debug: false
91
+ debug: false,
91
92
  });
92
93
 
93
94
  le = LE.create({
94
- server: production ? 'https://acme-v02.api.letsencrypt.org/directory' : 'https://acme-staging-v02.api.letsencrypt.org/directory',
95
- store: leStore, // handles saving of config, accounts, and certificates
96
- challenges: { 'http-01': leChallenge }, // handles /.well-known/acme-challege keys and tokens
97
- challengeType: 'http-01', // default to this challenge type
95
+ server: production
96
+ ? 'https://acme-v02.api.letsencrypt.org/directory'
97
+ : 'https://acme-staging-v02.api.letsencrypt.org/directory',
98
+ store: leStore, // handles saving of config, accounts, and certificates
99
+ challenges: { 'http-01': leChallenge }, // handles /.well-known/acme-challege keys and tokens
100
+ challengeType: 'http-01', // default to this challenge type
98
101
  debug: false,
99
102
  log: function (debug) {
100
103
  logger && logger.info(arguments, 'Lets encrypt debugger');
101
- }
104
+ },
102
105
  });
103
106
 
104
107
  // Check in-memory cache of certificates for the named domain
105
- return le.check({ domains: [domain] }).then(function (cert){
108
+ return le.check({ domains: [domain] }).then(function (cert) {
106
109
  var opts = {
107
110
  domains: [domain],
108
111
  email: email,
109
112
  agreeTos: true,
110
- rsaKeySize: 2048, // 2048 or higher
111
- challengeType: 'http-01'
112
- }
113
+ rsaKeySize: 2048, // 2048 or higher
114
+ challengeType: 'http-01',
115
+ };
113
116
 
114
- if (cert){
115
- if (renew){
117
+ if (cert) {
118
+ if (renew) {
116
119
  logger && logger.info('renewing cert for ' + domain);
117
120
  opts.duplicate = true;
118
- return le.renew(opts, cert).catch(function(err){
121
+ return le.renew(opts, cert).catch(function (err) {
119
122
  logger && logger.error(err, 'Error renewing certificates for ', domain);
120
123
  });
121
124
  } else {