pixl-server-web 2.0.3 → 2.0.5

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.
package/README.md CHANGED
@@ -35,6 +35,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
35
35
  * [http_brotli_opts](#http_brotli_opts)
36
36
  * [http_default_acl](#http_default_acl)
37
37
  * [http_blacklist](#http_blacklist)
38
+ * [http_allow_hosts](#http_allow_hosts)
38
39
  * [http_rewrites](#http_rewrites)
39
40
  * [http_redirects](#http_redirects)
40
41
  * [http_log_requests](#http_log_requests)
@@ -68,6 +69,7 @@ This module is a component for use in [pixl-server](https://www.github.com/jhuck
68
69
  * [https_force](#https_force)
69
70
  * [https_header_detect](#https_header_detect)
70
71
  * [https_timeout](#https_timeout)
72
+ * [https_bind_address](#https_bind_address)
71
73
  - [Custom URI Handlers](#custom-uri-handlers)
72
74
  * [Access Control Lists](#access-control-lists)
73
75
  * [Internal File Redirects](#internal-file-redirects)
@@ -441,6 +443,22 @@ This example would reject all incoming IP addresses from Apple and AT&T (who own
441
443
 
442
444
  When a new incoming connection is established, the socket IP is immediately checked against the blacklist, and if matched, the socket is "hard closed". This is an early detection and rejection, before the HTTP request even comes in. In this case a HTTP response isn't sent back (as the socket is simply slammed shut). However, if you are using a load balancer or proxy, the user's true IP address might not be known until later on in the request cycle, once the HTTP headers are read in. At that point all the user's IPs are checked against the blacklist again, and if any of them match, a `HTTP 403 Forbidden` response is sent back.
443
445
 
446
+ ## http_allow_hosts
447
+
448
+ The `http_allow_hosts` property allows you to specify a limited set of hosts to allow for incoming requests. Specifically, this matches the incoming HTTP `Host` request header, or SNI (TLS handshake) host for HTTPS, and the value must match at least one entry in the array (case-insensitive). For example, if you are hosting your application behind a domain name, you may want to restrict incoming requests so that they must explicitly point to your domain name. Here is how to set this up:
449
+
450
+ ```json
451
+ "http_allow_hosts": ["mydomain.com"]
452
+ ```
453
+
454
+ In the above example, only requests to `mydomain.com` would be allowed. All other domains or IP addresses in the URL would be rejected with a `HTTP 403 Forbidden` error (or in the case of SNI / TLS handshake the socket is simply closed). Include multiple entries in the array for things like subdomains:
455
+
456
+ ```json
457
+ "http_allow_hosts": ["mydomain.com", "www.mydomain.com"]
458
+ ```
459
+
460
+ If the `http_allow_hosts` array is empty or omitted entirely, all hosts are allowed. This is the default behavior.
461
+
444
462
  ## http_rewrites
445
463
 
446
464
  If you need to rewrite certain incoming URLs on-the-fly, you can define rules in the `http_rewrites` object. The basic format is as follows: keys are regular expressions matched on incoming URI paths, and the values are the substitution strings to use as replacements. Here is a simple example:
package/lib/https.js CHANGED
@@ -56,7 +56,7 @@ module.exports = class HTTP2 {
56
56
 
57
57
  if (max_conns && (self.numConns >= max_conns)) {
58
58
  // reached maximum concurrent connections, abort new ones
59
- self.logError('maxconns', "Maximum concurrent connections reached, denying request from: " + ip, { ip: ip, port: port, max: max_conns });
59
+ self.logError('maxconns', "Maximum concurrent connections reached, denying request from: " + ip, { host: socket.servername || '', ip: ip, port: port, max: max_conns });
60
60
  socket.end();
61
61
  socket.unref();
62
62
  socket.destroy(); // hard close
@@ -65,14 +65,23 @@ module.exports = class HTTP2 {
65
65
  }
66
66
  if (self.server.shut) {
67
67
  // server is shutting down, abort new connections
68
- self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { ip: ip, port: port});
68
+ self.logError('shutdown', "Server is shutting down, denying connection from: " + ip, { host: socket.servername || '', ip: ip, port: port});
69
69
  socket.end();
70
70
  socket.unref();
71
71
  socket.destroy(); // hard close
72
72
  return;
73
73
  }
74
74
  if (ip && self.aclBlacklist.checkAny(ip)) {
75
- self.logError('blacklist', "IP is blacklisted, denying connection from: " + ip, { ip: ip, port: port });
75
+ self.logError('blacklist', "IP is blacklisted, denying connection from: " + ip, { host: socket.servername || '', ip: ip, port: port });
76
+ socket.end();
77
+ socket.unref();
78
+ socket.destroy(); // hard close
79
+ return;
80
+ }
81
+
82
+ // SNI: check allow list at socket connect time
83
+ if (self.httpsAllowHosts.length && !self.httpsAllowHosts.includes( ('' + socket.servername).toLowerCase() )) {
84
+ self.logError('allowhosts', "SNI host not allowed: " + (socket.servername || 'n/a'), { host: socket.servername || '', ip: ip, port: port });
76
85
  socket.end();
77
86
  socket.unref();
78
87
  socket.destroy(); // hard close
@@ -82,7 +91,7 @@ module.exports = class HTTP2 {
82
91
  var id = self.getNextId('cs');
83
92
  self.conns[ id ] = socket;
84
93
  self.numConns++;
85
- self.logDebug(8, "New incoming HTTPS (SSL) connection: " + id, { ip: ip, port: port, num_conns: self.numConns });
94
+ self.logDebug(8, "New incoming HTTPS (SSL) connection: " + id, { host: socket.servername || '', ip: ip, port: port, num_conns: self.numConns });
86
95
 
87
96
  // Disable the Nagle algorithm.
88
97
  socket.setNoDelay( true );
package/lib/request.js CHANGED
@@ -71,6 +71,24 @@ module.exports = class Request {
71
71
  return;
72
72
  }
73
73
 
74
+ // custom host allow list
75
+ if (this.allowHosts.length && !this.allowHosts.includes( ('' + request.headers['host']).toLowerCase().replace(/\:\d+$/, '') )) {
76
+ this.logError(403, "Forbidden: Host not allowed: " + (request.headers['host'] || 'n/a'), {
77
+ id: args.id,
78
+ host: request.headers['host'] || '',
79
+ useragent: request.headers['user-agent'] || '',
80
+ referrer: request.headers['referer'] || '',
81
+ cookie: request.headers['cookie'] || '',
82
+ url: this.getSelfURL(request, request.url) || request.url
83
+ });
84
+ this.sendHTTPResponse( args,
85
+ "403 Forbidden",
86
+ { 'Content-Type': "text/html" },
87
+ "403 Forbidden\n"
88
+ );
89
+ return;
90
+ }
91
+
74
92
  // allow special URIs to skip the line
75
93
  if (this.queueSkipMatch && request.url.match(this.queueSkipMatch)) {
76
94
  this.logDebug(8, "Bumping request to front of queue: " + request.url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-web",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "A web server component for the pixl-server framework.",
5
5
  "author": "Joseph Huckaby <jhuckaby@gmail.com>",
6
6
  "homepage": "https://github.com/jhuckaby/pixl-server-web",
package/web_server.js CHANGED
@@ -47,6 +47,7 @@ module.exports = Class({
47
47
  "http_enable_brotli": false,
48
48
  "http_default_acl": ['127.0.0.1', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '::1/128', 'fd00::/8', '169.254.0.0/16', 'fe80::/10'],
49
49
  "http_blacklist": [],
50
+ "http_allow_hosts": [],
50
51
  "http_log_requests": false,
51
52
  "http_log_request_details": false,
52
53
  "http_log_body_max": 32768,
@@ -243,6 +244,10 @@ class WebServer extends Component {
243
244
  this.redirects.push(redirect);
244
245
  }
245
246
  }
247
+
248
+ // custom host list
249
+ this.allowHosts = (this.config.get('http_allow_hosts') || []).map( function(host) { return host.toLowerCase(); } );
250
+ this.httpsAllowHosts = (this.config.get('https_allow_hosts') || this.config.get('http_allow_hosts') || []).map( function(host) { return host.toLowerCase(); } );
246
251
  }
247
252
 
248
253
  startAll(callback) {