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

Я создал сертификат с помощью OpenSSL (rootCA.pem) и добавил его в брелок Mac. После этого я выпустил server.cert и server.key с rootCA.pem. Я построил веб-сервер, используя Flask-Sockeio, добавил 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. Из-за этого клиент Sockeio не смог проверить мой самозагодный сертификат. И это информация из установки Python 3.7:

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

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

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

В папке Python Certifi есть документ с именем cacert.pem. Это содержит корневые сертификаты. Поэтому я добавил содержимое 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")

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

2
Tyrion 8 Окт 2019 в 20:45

1 ответ

Лучший ответ

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

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

socketio = SocketIO(app, cors_allowed_origins='http://localhost:3000')
3
Miguel 10 Окт 2019 в 20:40