Reverse-proxying: Flask app with Bokeh server on Nginx

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

Reverse-proxying: Flask app with Bokeh server on Nginx

nginx mailing list

I have created a website with Flask that is serving a Bokeh app on a Digital Ocean VPN. Everything worked fine until I secured the server with Let's Encrypt following this tutorial.

In step 3 of the tutorial the Nginx configuration file is changed, which might be the crux of the problem I'm getting:

When I go on the website, the Flask content is rendered perfectly. However, the Bokeh app is not running. In the Inspection Console I get the following Error (note that I hashed out the IP address of my website):

Mixed Content: The page at 'https://example.com/company_abc/' was loaded over HTTPS, 
but requested an insecure script 
'http://###.###.###.##:5006/company_abc/autoload.js?bokeh-autoload-element=f…aab19c633c95&bokeh-session-id=AvWhaYqOzsX0GZPOjTS5LX2M7Z6arzsBFBxCjb0Up2xP'. 
This request has been blocked; the content must be served over HTTPS.

I understand that I might have to use a method called reverse proxying, which is described here. However, I wasn't able to get it to work.

Does anybody have an idea how to solve this? A similar problem was described here.

Here are my modified server files:

'/etc/nginx/sites-available/default':

upstream flask_siti {
        server 127.0.0.1:8118 fail_timeout=0;
}
server {
        listen 443 ssl;

        server_name example.com www.example.com;

        ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_dhparam /etc/ssl/certs/dhparam.pem;
        ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
        ssl_session_timeout 1d;
        ssl_session_cache shared:SSL:50m;
        ssl_stapling on;
        ssl_stapling_verify on;
        add_header Strict-Transport-Security max-age=15768000;

        charset utf-8;
        client_max_body_size 75M;

        access_log /var/log/nginx/flask/access.log;
        error_log /var/log/nginx/flask/error.log;

        keepalive_timeout 5;

        location / {
                # checks for static file, if not found proxy to the app
                try_files $uri @proxy_to_app;
        }

        location @proxy_to_app {
                proxy_redirect off;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://flask_siti;
        }
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
'/etc/supervisor/conf.d/bokeh_serve.conf':
[program:bokeh_serve]
command=/opt/envs/virtual/bin/bokeh serve company_abc.py company_xyz.py --allow-websocket-origin=www.example.com --allow-websocket-origin=example.com --host=###.###.###.##:5006 --use-xheaders
directory=/opt/webapps/flask_telemetry
autostart=false
autorestart=true
startretries=3
user=nobody
'/etc/supervisor/conf.d/flask.conf':
[program:flask]
command=/opt/envs/virtual/bin/gunicorn -b :8118 website_app:app
directory=/opt/webapps/flask_telemetry
user=nobody
autostart=true
autorestart=true
redirect_stderr=true
And here is my Flask app (Note that I hashed out security related info):
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask import render_template, request, redirect, url_for
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, login_required, roles_accepted, current_user
from flask_security.decorators import anonymous_user_required
from flask_security.forms import LoginForm
from bokeh.embed import autoload_server
from bokeh.client import pull_session
from wtforms import StringField
from wtforms.validators import InputRequired
from werkzeug.contrib.fixers import ProxyFix

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://###:###@localhost/telemetry'
app.config['SECRET_KEY'] = '###'
app.config['SECURITY_REGISTERABLE'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = 'username'
app.config['SECURITY_POST_LOGIN_VIEW'] = '/re_direct'
app.debug = True
db = SQLAlchemy(app)

# Define models
roles_users = db.Table('roles_users',
        db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
        db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))

class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))

class ExtendedLoginForm(LoginForm):
    email = StringField('Username', [InputRequired()])

# Setup Flask-Security
user_datastore = SQLAlchemyUserDatastore(db, User, Role)
security = Security(app, user_datastore, login_form=ExtendedLoginForm)

# Views
@app.route('/')
@anonymous_user_required
def index():
    return render_template('index.html')

@app.route('/re_direct/')
@login_required
def re_direct():
    identifier = current_user.username
    print(identifier)
    return redirect(url_for(identifier))

@app.route('/index/')
@login_required
@roles_accepted('admin')
def admin():
    return render_template('admin.html')

@app.route("/company_abc/")
@login_required
@roles_accepted('company_abc', 'admin')
def company_abc():
    url='http://###.###.###.##:5006'
    session=pull_session(url=url,app_path="/company_abc")
    bokeh_script=autoload_server(None,app_path="/company_abc",session_id=session.id,url=url)
    return render_template("company_abc.html", bokeh_script=bokeh_script)

@app.route("/company_xyz/")
@login_required
@roles_accepted('company_xyz', 'admin')
def company_xyz():
    url='http://###.###.###.##:5006'
    session=pull_session(url=url,app_path="/company_xyz")
    bokeh_script=autoload_server(None,app_path="/company_xyz",session_id=session.id,url=url)
    return render_template("company_xyz.html", bokeh_script=bokeh_script)

app.wsgi_app = ProxyFix(app.wsgi_app)

if __name__ == '__main__':
    app.run()

_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx
Reply | Threaded
Open this post in threaded view
|

Re: Reverse-proxying: Flask app with Bokeh server on Nginx

Reinis Rozitis
> I understand that I might have to use a method called reverse proxying,
> which is described here. However, I wasn't able to get it to work.

Well you already do this "method called reverse proxying" with the Flask app
so you have to do the same with the Bokeh app as all modern/current browsers
require all resources on a HTTPS website to be also loaded through secure
channels.


> Does anybody have an idea how to solve this? A similar problem was
> described here.

You can basically copy the configuration from the SO thread you linked
(obviously you can change the location names as you wish / they just need to
match):

In nginx add:

location /bokeh/ {
    proxy_pass <a href="http://127.0.1.1:5006;">http://127.0.1.1:5006;

    # .. with the rest of directives
}

relaunch the Bokeh app with

--prefix=/bokeh/

and (if takes part in the url construction rather than application
background requests) change the url variable in the Flask app

url='<a href="http://###.###.###.##:5006'">http://###.###.###.##:5006'
to
url='https://yourserver/bokeh/'

or even just relative url='/bokeh/' .. (I'm not familiar with this software
stack so you have to test yourself)


rr

_______________________________________________
nginx mailing list
[hidden email]
http://mailman.nginx.org/mailman/listinfo/nginx