Безопасная связь между сервером flask-socketio и python-socketio - PullRequest
0 голосов
/ 08 октября 2019

Я работаю над проектом с использованием flask-socketio и python-socketio. Для обеспечения безопасности связи необходимо обновить систему с помощью SSL. Я работаю с Mac и Python 3.7.

Я создал сертификат с помощью openssl (rootCA.pem) и добавил его в связку ключей Mac. После этого я выдал server.cert и server.key с rootCA.pem. Я построил веб-сервер с помощью flask-socketio, добавил server.cert и server.key на его стороне. В конце концов, веб-сайт хорошо работал с ssl.

Проблема возникла, когда я захотел защитить связь между сервером flask-socketio и клиентом python-socketio. Когда клиент пытался соединиться с сервером, соединение было отклонено и установлено unknown ca error:

ssl.SSLError: [SSL: TLSV1_ALERT_UNKNOWN_CA] tlsv1 alert unknown ca (_ssl.c:2488)

Я провел некоторые исследования и понял, что эта проблема может быть связана с тем, что Python 3.7 использует свою собственную частную копиюOpenSSL. Из-за этого клиент SocketIO не смог проверить мой самозаверяющий сертификат. И это информация из установки Python 3.7:

Этот вариант Python 3.7 включает в себя собственную частную копию OpenSSL 1.1.1. Устаревшие поставляемые Apple библиотеки OpenSSL больше не используются. Это означает, что сертификаты доверия в системных и пользовательских цепочках ключей, управляемых приложением Keychain Access и утилитой командной строки безопасности, больше не используются в качестве значений по умолчанию модулем Python ssl. Пример сценария команды включен в / Applications/ Python 3.7 для установки курируемого пакета корневых сертификатов по умолчанию из стороннего пакета certifi (https://pypi.org/project/certifi/). Если вы решите использовать certifi, вам следует рассмотреть возможность подписки на службу обновления электронной почты проекта, чтобы получать уведомления при получении пакета сертификатовобновлен.

Итак, я пошел в папку /Applications/Python 3.7 и выполнил Install Certificates.command.

Однако, это не ключ к проблеме. тщательно отобранная коллекция корневых сертификатов . Конечно, она не содержит мой самозаверяющий сертификат. Поэтому, чтобы решить эту проблему, я должен позволить Python найти rootCA.pem.

В папке сертификата python находится документ с именем cacert.pem, в котором содержится Root. Сертификаты. Поэтому я добавил содержимое rootCA.pem в конце cacert.pem.

После этого я попробовал этот код в командной строке:

openssl verify -CAfile cacert.pem server.crt

И вывод:

server.crt: OK

Думаю, в этом случае cacert.pem может проверить сертификат сервера. Я знаю, что это не элегантное решение. Пожалуйста, дайте мне знать, если у вас есть лучший способ заставить python найти мой самозаверяющий сертификат.

После этого я попытался подключиться к серверу flask-socketio с помощью python-socketio. На этот раз я получил еще одну ошибку. На стороне сервера информация журнала:

(57183) accepted ('127.0.0.1', 56322)
0fd838477c534dfda802bfb0d130d358: Sending packet OPEN data {'sid': '0fd838477c534dfda802bfb0d130d358', 'upgrades': ['websocket'], 'pingTimeout': 60000, 'pingInterval': 25000}
0fd838477c534dfda802bfb0d130d358: Sending packet MESSAGE data 0
127.0.0.1 - - [10/Oct/2019 08:50:36] "GET /socket.io/?transport=polling&EIO=3&t=1570706436.665149 HTTP/1.1" 200 349 0.000436
(57183) accepted ('127.0.0.1', 56324)
http://localhost:3000 is not an accepted origin.
127.0.0.1 - - [10/Oct/2019 08:50:36] "GET /socket.io/?transport=websocket&EIO=3&sid=0fd838477c534dfda802bfb0d130d358&t=1570706436.685631 HTTP/1.1" 400 122 0.000182

А на стороне клиента ошибка выдается вот так:

Traceback (most recent call last):
  File "simple_client.py", line 18, in <module>
    sio.connect('https://localhost:3000', namespaces="/channel_A")
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/socketio/client.py", line 262, in connect
    engineio_path=socketio_path)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 170, in connect
    url, headers, engineio_path)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 308, in _connect_polling
    if self._connect_websocket(url, headers, engineio_path):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/engineio/client.py", line 346, in _connect_websocket
    cookie=cookies)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_core.py", line 514, in create_connection
    websock.connect(url, **options)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_core.py", line 226, in connect
    self.handshake_response = handshake(self.sock, *addrs, **options)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_handshake.py", line 79, in handshake
    status, resp = _get_resp_headers(sock)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/websocket/_handshake.py", line 160, in _get_resp_headers
    raise WebSocketBadStatusException("Handshake status %d %s", status, status_message, resp_headers)
websocket._exceptions.WebSocketBadStatusException: Handshake status 400 BAD REQUEST

Я понятия не имею, почему это происходит. Связь между сервером flask-socketio и клиентом python-socketio работает без SSL. Так что я думаю, что с кодом может быть все в порядке, но я все же приведу код здесь. И это для сервера:

from flask import Flask, render_template
from flask_socketio import SocketIO
from flask_socketio import Namespace
import eventlet

app = Flask(__name__, template_folder="templates")

app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, engineio_logger=True, logger=True)

# Create a URL route in our application for "/"
@app.route('/')
def home():
    """
    This function loads the homepage
    """
    return render_template('index.html')


class MyCustomNamespace(Namespace):
    def on_connect(self):
        print("Client just connected")

    def on_disconnect(self):
        print("Client just left")

    def on_messages(self, data):                     
        print(f"\nReceived data from client: \n {data}\n")
        return data

socketio.on_namespace(MyCustomNamespace('/channel_A'))

if __name__ == "__main__":
    eventlet.wsgi.server(
        eventlet.wrap_ssl(eventlet.listen(("localhost", 3000)),
                          certfile='server.crt',
                          keyfile='server.key',
                          server_side=True), app)   
    # socketio.run(app, host="localhost", port=3000, debug=True) 

Это для клиента:

import socketio

sio = socketio.Client()

def message_received(data):
    print(f"Message {data} received")

@sio.on('connect', namespace="/channel_A")
def on_connect():
    print("Connect...")

@sio.on('disconnect', namespace="/channel_A")
def on_disconnect():
    print(f"Disconnected from server")

sio.connect('https://localhost:3000', namespaces="/channel_A")

Пожалуйста, помогите! Я знаю, что мой вопрос многословен. Но я просто хочу показать, как я пытался решить проблему. Если что-то не так, пожалуйста, дайте мне знать. Спасибо!

1 Ответ

0 голосов
/ 10 октября 2019

Последние версии Flask-SocketIO поставляются с самыми безопасными настройками в отношении установок с перекрестным происхождением, которые допускают только одно и то же происхождение. Если ваше веб-приложение и приложение Flask работают на разных серверах или портах, вам необходимо настроить кросс-источник, чтобы они могли работать вместе.

Например, если ваш веб-интерфейс размещен на http://localhost:3000,Вы можете разрешить это как источник с:

socketio = SocketIO(app, cors_allowed_origins='http://localhost:3000')
...