Help needed with SSL for a Nginx, Gunicorn, Flask, Bokeh setup

I am not able to show the Bokeh server app when I add SSL to my Nginx proxy server; the Index page shows up with SSL, but the page with embed Bokeh server document does not show the plot. Without SSL everything works fine. I am running the webserver on an internal private network, hence the certificate created is just self signed.

I am using the --use-xheaders option when running the Bokeh server. I use server_document to embed Bokeh doc. If I in the app route for the bokeh url specify regular http:// then I get an browser error related to mixed content.

Blocked loading mixed active content “http://10.73.162.4:5100/bokeh/sliders/autoload.js?bokeh-auto…ers&bokeh-absolute-url=http://10.73.162.4:5100/bokeh/sliders”

If I instead use https:// then nothing happens (seems to be time out on the bokeh script). Browser reports in the XHR among others “No headers for this request”; (since the Bokeh server is running on http:// then this is expected I guess).

Starting Bokeh server:

bokeh serve sliders/ --prefix /bokeh --allow-websocket 10.73.162.4 --address 10.73.162.4 --port 5100 --use-xheaders

Gunicorn:

gunicorn --bind 0.0.0.0:8000 wsgi:app

Using Bokeh version 2.0.2 (not the latest due to bug related to some transformation), CentOS 8.

So I do not understand how to get the Nginx proxy to show the Bokeh doc with setup with SSL for Nginx part. Any help appreciated.

nginx conf

server {
    listen 443 ssl;
    server_name  10.73.162.4;

    ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
    ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;
#
#    # enables all versions of TLS, but not SSLv2 or v3 which are deprecated.
    ssl_protocols TLSv1.1 TLSv1.2;
#
#    # disables all weak ciphers
    ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-A-$
#
    ssl_prefer_server_ciphers on;

    # bokeh apps are run with prefix option where prefix bokeh
    location /bokeh {
        proxy_pass          http://10.73.162.4:5100;
        proxy_set_header    Upgrade              $http_upgrade;
        proxy_set_header    Connection           "upgrade";
        proxy_http_version  1.1;
        proxy_set_header    X-Forwarded-Proto    $scheme;
        proxy_set_header    X-Forwarded-For	 $proxy_add_x_forwarded_for;
        proxy_set_header    Host                 $host:$server_port;
    }

    location / {
       proxy_pass http://0.0.0.0:8000;
        proxy_redirect     off;
        proxy_set_header   Host                 $host;
        proxy_set_header   X-Real-IP            $remote_addr;
        proxy_set_header   X-Forwarded-For	$proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto    $scheme;
    }
}

application/route.py

from flask import render_template, request
from flask import current_app as app
from bokeh.embed import server_document, server_session
from bokeh.client import pull_session

# setup all the routes
@app.route('/')
@app.route('/index')
def home():
    """Landing page."""
    return render_template(
        'index.html',
        title = 'Index',
    )

BOKEH_PREFIX = 'bokeh/'
BOKEH_IP = '10.73.162.4'
BOKEH_PORT = '5100'

@app.route('/sliders')
def sliders():
    title = 'Sliders'
    app_name = 'sliders'
    bk_app = '{}{}'.format(BOKEH_PREFIX, app_name)

    app_url = 'https://{}:{}/{}'.format(
        BOKEH_IP,
        BOKEH_PORT,
        bk_app
    )
    print(app_url)
    bokeh_script = server_document(url = app_url, relative_urls = False)
    print(bokeh_script)

    return render_template("bk_app.html", bokeh_script = bokeh_script, title = title, url = app_url)

application/templates/bk_app.html

<!-- bk_app.html -->
<!doctype html>
<html>
<head>
    <title>{{title}}</title>
</head>
<body>
<div>
   <ul>
       <li>
           <a href="{{ url_for('home') }}">Index</a>
        </li>
   </ul>
</div>
<div>
   {{ bokeh_script|safe }}
</div>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.0.2.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.0.2.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.0.2.min.js"
        crossorigin="anonymous"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-api-2.0.2.min.js"
        crossorigin="anonymous"></script>
</body>
</html>

application/templates/index.html

<!-- index.html -->
<!doctype html>
<html>
<head>
    <title>{{title}}</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link rel="stylesheet" href="{{ url_for('static', filename='css/template.css') }}">
    {% block head %}{% endblock %}
</head>
<body>
   <ul>
       <li>
           <a href="{{ url_for('sliders') }}">Sliders</a>
        </li>
   </ul>
</body>
</html>

**application/init.py

from flask import Flask


def create_app():
    """Initialize the core application."""
    app = Flask(
        __name__, 
        instance_relative_config = False,
        template_folder = 'templates',
        static_folder = 'static'
    )

    with app.app_context():
        # Include our Routes
        from . import routes

        return app

wsgi.py

from application import create_app

app = create_app()

if __name__ == "__main__":
    app.run(host='0.0.0.0', port = 8000, debug = True)

Unfortunately my nginx expertise is extremely minimal. Is this just in order to be able to have SSL connections to the Bokeh server? If so, it can be configured to directly terminate SSL without needing a proxy:

https://docs.bokeh.org/en/2.0.2/docs/user_guide/server.html#ssl-termination

I ran into similar problems a few weeks back and put the issue aside without resolution. This topic has made me want to revisit how to configure things to get my embedded bokeh apps working securely.

My system is a subset of that identified by @Jonas_Grave_Kristens; Gunicorn+Flask+bokeh but not currently using the nginx layer.

Like @Jonas_Grave_Kristens, my routes that are not related to bokeh are accessible via HTTPS, but the bokeh document doesn’t embed properly. Depending what options I give when configuring the servers I end up with errors related to mixed-content (HTTPS with bokeh via HTTP) or problems with SSL error codes when trying to configure HTTPServer with protocol, ssl_options, etc.

Well I would like the web server itself to have SSL so yes I need Nginx to have SSL. I tried setting up bokeh using cert and key I created but I got permission denied, Bokeh server does not seem to be able to read the created certificate…

@Jonas_Grave_Kristens

I am running bokeh 2.0.2 and generated certificates using openssl, and can get bokeh to have SSL connections when it is the only server.

And the client browser confirms that the traffic is encrypted (tested with Firefox and Safari, b/c these are easier to make security exceptions for self-signed certificates compared to Chrome).


Apologies if I misunderstand the reported behavior in the quoted text, but I think bokeh can deal with the certificates properly at least in certain system setups. I think the problem is how to make the leap from that bokeh-only system to one that embeds it.

Client:

https://localhost:5006/sliders

Server:

Desktop % bokeh serve --ssl-certfile cert.pem --ssl-keyfile key.pem sliders.py

2020-07-30 05:44:52,318 Starting Bokeh server version 2.0.2 (running on Tornado 6.0.4)

2020-07-30 05:44:52,319 Configuring for SSL termination

2020-07-30 05:44:52,320 User authentication hooks NOT provided (default user enabled)

2020-07-30 05:44:52,324 Bokeh app running at: http://localhost:5006/sliders

2020-07-30 05:44:52,324 Starting Bokeh server with process id: 97617

2020-07-30 05:45:33,695 WebSocket connection opened

2020-07-30 05:45:33,695 ServerConnection created

In my setup, sans the nginx part, I can run the web server securely by passing the credentials to gunicorn, which gives me secure connections to all of the other pages on the site. I just have not figured out how to configure the bokeh server part of things in my Flask app. (I am running the bokeh server programmatically in a worker thread and have tried passing things through during the server instantiation related to SSL options, etc.)

I made a specific certificate for the bokeh server and running that as you do (no Nginx) works, however due to self signed certificate I get error messages but the bokeh document is displayed (Firefox).

SSL Error on 8 ('10.224.36.7', 45674): [SSL: SSLV3_ALERT_BAD_CERTIFICATE] sslv3 alert bad certificate (_ssl.c:897)

And now I suddenly got it working with Nginx… not sure what happend… clearing of browser cache? However Chromium/Chrome do not accept the self signed certificate.

I need to use some more time on the setup testing endpoints/routes. Also need to figure out if it is possible to create self signed certificate that works in Chrome. So far I have used openssl

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/bokeh-selfsigned.key -out /etc/ssl/certs/bokeh-selfsigned.crt

@Jonas_Grave_Kristens

I was able to get Chrome (Version 84) to work with self-signed certificates. The important step seems to be generating a self-signed certificate with SAN (Subject Alternative Names). If you do that, the client’s browser will still generate a warning, but there will be a link that allows you to proceed. See screenshot Proceed to 127.0.0.1 (unsafe) link at bottom.

I used the Bash script with config baked in procedure on this serverfault URL to generate the self-signed SAN statistic, b/c I preferred it to modifying existing openssl configuration files.

https://serverfault.com/questions/845766/generating-a-self-signed-cert-with-openssl-that-works-in-chrome-58

Thanks, I’ll look into this.