Flask + Bokeh в Docker аутентификации - PullRequest
1 голос
/ 09 июля 2019

Мы используем Flask для маршрутизации пользователей на серверы Bokeh. Система работает внутри образа Docker. Все работает хорошо Но теперь мы хотим добавить аутентификацию, которая оказывается трудной, потому что мы не хотим сопоставлять порты сервера bokeh с клиентом.

Позвольте мне показать вам, как это работает в настоящее время (без аутентификации):

Flask app.py (маршрутизация):

...
@app.route('/folder/report_x')
def page_folder_report_x():
    ''' embedded bokeh server for report_x '''
    script = server_document('http://localhost:5001/report_x')
    resp = {
        'title': 'Report X',
        'script': script,
        'template': 'Flask', }
    return render_template('embed.html', **resp)
...
app.run(host='0.0.0.0', port=5000, use_reloader=False)

Колба embed.py (шаблон):

...
{% extends "base.html" %}
{% block content %}
  {{ script|safe }}
{% endblock %}
...

Сервер Bokeh запускается с помощью панели python из командной строки (localhost: 5000 представляет сервер Flask):

panel serve report_x --port 5001 --allow-websocket-origin localhost:5000

Сервер Bokeh обслуживается с помощью файла main.ipynb:

import panel as pn
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import Button, DataTable, PreText
from bokeh.models.widgets import TableColumn, NumberFormatter, DateFormatter
...
gspec = pn.GridSpec(sizing_mode='stretch_both')
gspec[0:12, 0:12] = pn.WidgetBox(widgets)
...
gspec.servable()

На нашем образе Docker представлены порты флеш-сервера и сервера (-ов) боке:

...
RUN pip install -r /app/requirements.txt
EXPOSE 5000:5000
EXPOSE 5001:5001
...

Наконец, когда мы запускаем докер-контейнер, мы отображаем порты:

# success!
docker run -p 5000:5000 -p 5001:5001 report_server:0.1

Если мы запустим образ докера таким образом, все будет отлично.

Но если мы запустим его без сопоставления сервера bokeh, мы не сможем связаться с сервером bokeh (даже если он открыт внутри, как вы можете видеть в DockerFile):

# fail
docker run -p 5000:5000 report_server:0.1

В целях безопасности мы хотим сопоставить только один порт с внешним миром. Есть ли что-то, чего нам не хватает в том, как встроить серверы Bokeh в Flask, чтобы позволить только Flask общаться с сервером Bokeh?

1 Ответ

1 голос
/ 09 июля 2019

Есть ли что-то, чего нам не хватает в том, как встроить серверы Bokeh в Flask, чтобы позволить только Flask общаться с сервером Bokeh?

Клиент (браузер)должен иметь возможность общаться с сервером Bokeh, полная остановка .Все функции сервера Bokeh выполняются через прямое веб-сокет-соединение между сервером Bokeh и браузером.Таким образом, краткий ответ на ваш вопрос «вы не можете».

Тем не менее, вы можете настроить сервер Bokeh на:

  • , чтобы автоматически не создавать новые сеансы при каждом соединении
  • только для сеансов, имеющих криптографическую подписьID сеанса
  • принимать соединения только из источников, занесенных в белый список

Для этого вам необходимо сначала создать секрет для подписания идентификаторов сеанса с помощью команды bokeh secret, например,

export BOKEH_SECRET_KEY=`bokeh secret` 

Затем также установите BOKEH_SIGN_SESSIONS и установите допустимое происхождение веб-сокета:

BOKEH_SIGN_SESSIONS=yes bokeh serve --allow-websocket-origin=<app origin> app.py

Затем в своем приложении фляги вы явно предоставляете (подписанные) идентификаторы сеанса:

from bokeh.util.session_id import generate_session_id

script = server_session(url='http://localhost:5006/bkapp', 
                        session_id=generate_session_id())
return render_template("embed.html", script=script, template="Flask")

Обратите внимание, что переменная окружения BOKEH_SECRET_KEY должна быть установлена ​​(и идентична) как для сервера Bokeh, так и для процессов Flask.

Теперь, если кто-либо подключится к серверу Bokeh напрямую, он получит ошибку 403, если URL-адрес соединения не содержит подписанный идентификатор сеанса, подписанный с тем же секретом, с которого был запущен сервер Bokeh.Предположительно, только ваше приложение Flask знает этот секрет, поэтому только оно может успешно инициировать новые сеансы.

Этого достаточно, чтобы полностью обезопасить вещи?Технически любой, кто может получить доступ к строке подключения, отправленной в браузер (например, пользователь, просматривающий приложение, или искушенный злоумышленник MitM, особенно если вы не отключаете HTTPS перед приложением), может извлечь подписанный идентификатор сеанса.Но пока вы устанавливаете разрешенное происхождение веб-сокета, эта информация не может быть использована для инициирования нового соединения из-за пределов вашего приложения.Если кто-то попытается, сервер вернет сообщение 403:

ОШИБКА: bokeh.server.views.ws: Отказ в соединении веб-сокетов от Origin 'http://localhost:5006'; use --allow-websocket-origin = localhost: 5006 или установить BOKEH_ALLOW_WS_ORIGIN = localhost: 5006, чтобы разрешить это;в настоящее время мы разрешаем origins {'localhost: 8000'}

Я не думаю, что вы можете подделать заголовок Origin из реального браузера, хотя, возможно, кто-то может создать модифицированный Chrome из исходного кода (это не легко, но не невозможно) подделать.Если вам нужно остерегаться этого, то Bokeh Project Discourse , вероятно, является лучшим местом для продолжения обсуждения, так как оно несколько открытое и может указывать на разработку новых функций (например, возможность указать соединениеограничение для сессий, или чтобы идентификаторы сессий никогда не использовались повторно.

Для справки, здесь приведен полный пример, который также встраивает сервер Bokeh непосредственно в процесс Flask (если вам нужно уменьшить или ожидатьнесколько одновременных пользователей, это было бы слишком наивным развертыванием):

https://gist.github.com/bryevdv/481fc64c59620acb74c64bff0f4d47d0

В качестве последнего комментария вы, возможно, также (дополнительно) можете поместить URL-адрес сервера Bokeh позади прокси-сервера аутентификации некоторыхсортировать, чтобы предотвратить обновление WS в первую очередь без аутентификации.Хотя я не уверен, что именно это будет выглядеть не по назначению.Это также лучше обсудить на Дискурсе

...