Repository: bokeh/bokeh

Issue:
--host whitelist doesn't trust 127.0.0.1 by default
It looks like by default bokeh server doesn't trust requests sent to `127.0.0.1`, nor the address of localhost on local network interfaces.  Perhaps it should?  This had me confused for a while.

```
mrocklin@workstation:~/workspace/distributed$ bokeh serve distributed/diagnostics/bokeh/status.py
DEBUG:bokeh.server.tornado:Allowed Host headers: ['localhost:5006']
DEBUG:bokeh.server.tornado:These host origins can connect to the websocket: ['localhost:5006']
INFO:bokeh.server.tornado:Rejected connection from host '192.168.1.141:5006' because it is not in the --host whitelist
INFO:bokeh.server.tornado:Rejected connection from host '127.0.0.1:5006' because it is not in the --host whitelist
```



Discussion:
Alternatively is there a way to ignore the whitelist?

Perhaps we could accept something like `--host *` or `--host *:80`

This is similar to what you see in other services like the Jupyter notebook.

Here is where the whitelist is checked, if you'd like to try and make a small PR to focus discussion: 

https://github.com/bokeh/bokeh/blob/master/bokeh/server/tornado.py#L29-L42


Resolving patch:
diff --git a/bokeh/command/subcommands/serve.py b/bokeh/command/subcommands/serve.py
--- a/bokeh/command/subcommands/serve.py
+++ b/bokeh/command/subcommands/serve.py
@@ -102,6 +102,11 @@
 accept requests from ``localhost:<port>`` where ``<port>`` is the port
 that the server is configured to listen on (by default: {DEFAULT_PORT}).
 
+If an asterix ``*`` is used in the host value (for example ``--host *``) then
+it will be treated as a wildcard.  As a warning, using permissive host values
+like ``*`` may be insecure and open your application to HTTP host header
+attacks.
+
 Also note that the host whitelist applies to all request handlers,
 including any extra ones added to extend the Bokeh server.
 
diff --git a/bokeh/server/tornado.py b/bokeh/server/tornado.py
--- a/bokeh/server/tornado.py
+++ b/bokeh/server/tornado.py
@@ -25,21 +25,51 @@
 from .application_context import ApplicationContext
 from .views.static_handler import StaticHandler
 
+
+def match_host(host, pattern):
+    """ Match host against pattern
+
+    >>> match_host('192.168.0.1', '192.168.0.1')
+    True
+    >>> match_host('192.168.0.1', '192.168.0.2')
+    False
+    >>> match_host('192.168.0.1', '192.168.*.*')
+    True
+    >>> match_host('alice', 'alice')
+    True
+    >>> match_host('alice', 'bob')
+    False
+    >>> match_host('foo.example.com', 'foo.example.com.net')
+    False
+    >>> match_host('alice', '*')
+    True
+    """
+    host = host.split('.')
+    pattern = pattern.split('.')
+
+    if len(pattern) > len(host):
+        return False
+
+    for h, p in zip(host, pattern):
+        if h == p or p == '*':
+            continue
+        else:
+            return False
+    return True
+
+
 # factored out to be easier to test
 def check_whitelist(request_host, whitelist):
     ''' Check a given request host against a whitelist.
 
     '''
-    if request_host not in whitelist:
+    if ':' not in request_host:
+        request_host = request_host + ':80'
 
-        # see if the request came with no port, assume port 80 in that case
-        if len(request_host.split(':')) == 1:
-            host = request_host + ":80"
-            return host in whitelist
-        else:
-            return False
+    if request_host in whitelist:
+        return True
 
-    return True
+    return any(match_host(request_host, host) for host in whitelist)
 
 
 def _whitelist(handler_class):


Test patch:
diff --git a/bokeh/server/tests/test_tornado.py b/bokeh/server/tests/test_tornado.py
--- a/bokeh/server/tests/test_tornado.py
+++ b/bokeh/server/tests/test_tornado.py
@@ -40,6 +40,12 @@ def test_check_whitelist_accepts_name_port_match():
 def test_check_whitelist_accepts_implicit_port_80():
     assert True == tornado.check_whitelist("foo", ["foo:80"])
 
+def test_check_whitelist_accepts_all_on_star():
+    assert True == tornado.check_whitelist("192.168.0.1", ['*'])
+    assert True == tornado.check_whitelist("192.168.0.1", ['192.168.0.*'])
+    assert False == tornado.check_whitelist("192.168.1.1", ['192.168.0.*'])
+    assert True == tornado.check_whitelist("foobarbaz", ['*'])
+
 # tried to use capsys to test what's actually logged and it wasn't
 # working, in the meantime at least this tests that log_stats
 # doesn't crash in various scenarios