Сервер GoLang Mochi MQTT с клиентом Python Paho MQTT. Проверка аутентификации TLS не удалась

Я пытаюсь использовать сервер GoLang Mochi MQTT в Windows 11 (https://github.com/mochi-mqtt/server ) с Python Paho ( https://pypi.org/project/paho- mqtt/ ) клиент для тестирования на той же машине. Тип аутентификации, который я хотел бы использовать, — TLS ( https://github.com/mochi-mqtt/server/blob/main/examples/tls/main.go).

Я настраиваю локальную инфраструктуру PKI на компьютере с Windows 11 с помощью библиотеки Python PKI (https://github.com/BrixIT/Python-PKI).

Я создаю корневой центр сертификации с библиотекой PKI и двумя клиентскими сертификатами.

developer@docker-desktop:Python-PKI$ python3 pypki.py list
+---------------+----------------+---------+---------------------+----------+
| Common name   | Organisation   | State   | Expiration date     |   Serial |
|---------------+----------------+---------+---------------------+----------|
| Dev Cert 1    | Me Co.         | Valid   | 2034-05-01 18:52:04 |     1000 |
| Dev Cert 2    | Me Co.         | Valid   | 2034-05-01 18:54:12 |     1001 |
+---------------+----------------+---------+---------------------+----------+

Я скопировал сертификаты клиента как в каталог тестового клиента Python Paho, так и в каталог сервера GoLang MQTT.

Я немного изменил пример сервера Mochi MQTT для TLS, чтобы он выглядел следующим образом.

package main

import (
    "crypto/tls"
    "crypto/x509"
    "github.com/mochi-mqtt/server/v2/packets"
    "io/ioutil"
    "log"
    "os"
    "os/signal"
    "syscall"

    mqtt "github.com/mochi-mqtt/server/v2"
    "github.com/mochi-mqtt/server/v2/hooks/auth"
    "github.com/mochi-mqtt/server/v2/listeners"
)

func main() {
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)
    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sigs
        done <- true
    }()

    cer, loadCertErr := tls.LoadX509KeyPair("Dev Cert 2.cert.pem", "Dev Cert 2.key.pem")
    if loadCertErr != nil {
        log.Println(loadCertErr)
        os.Exit(1)
    }

    // Optionally, if you want clients to authenticate only with certs issued by your CA,
    // you might want to use something like this:
    certPool := x509.NewCertPool()
    caCertPem, certErr := ioutil.ReadFile("ca.cert.pem")
    if certErr != nil {
        log.Println("Error: Failed to subscribe to direct topic", "error", certErr)
        os.Exit(1)
    }
    _ = certPool.AppendCertsFromPEM(caCertPem)
    tlsConfig := &tls.Config{
        ClientCAs:    certPool,
        ClientAuth:   tls.RequireAndVerifyClientCert,
        Certificates: []tls.Certificate{cer},
    }

    server := mqtt.New(&mqtt.Options{
        InlineClient: true,
    })
    _ = server.AddHook(new(auth.AllowHook), nil)

    log.Println("Listening on :1883")
    tcp := listeners.NewTCP(listeners.Config{
        ID:        "t1",
        Address:   ":1883",
        TLSConfig: tlsConfig,
    })
    err := server.AddListener(tcp)
    if err != nil {
        log.Fatal(err)
    }

    log.Println("Listening on :1882")
    ws := listeners.NewWebsocket(listeners.Config{
        ID:        "ws1",
        Address:   ":1882",
        TLSConfig: tlsConfig,
    })
    err = server.AddListener(ws)
    if err != nil {
        log.Fatal(err)
    }

    log.Println("Listening on :8080")
    stats := listeners.NewHTTPStats(
        listeners.Config{
            ID:        "stats",
            Address:   ":8080",
            TLSConfig: tlsConfig,
        }, server.Info,
    )

    err = server.AddListener(stats)
    if err != nil {
        log.Fatal(err)
    }

    log.Println("Running server")
    go func() {
        err := server.Serve()
        if err != nil {
            log.Fatal(err)
        }
    }()

    // Inline subscribe
    callbackFn := func(cl *mqtt.Client, sub packets.Subscription, pk packets.Packet) {
        server.Log.Info("inline client received message from subscription", "client", cl.ID, "subscriptionId", sub.Identifier, "topic", pk.TopicName, "payload", string(pk.Payload))
    }
    subscribeErr := server.Subscribe("direct/#", 1, callbackFn)
    if subscribeErr != nil {
        server.Log.Error("Error: Failed to subscribe to direct topic", "error", subscribeErr)
        os.Exit(1)
    }

    // Inline publish
    publishErr := server.Publish("direct/publish", []byte("packet scheduled message"), false, 0)
    if publishErr != nil {
        server.Log.Error("Error: Failed to publish to direct topic", "error", publishErr)
        os.Exit(1)
    }

    <-done
    server.Log.Warn("caught signal, stopping...")
    _ = server.Close()
    server.Log.Info("main.go finished")
}

Я также изменил пример клиента Paho MQTT для подключения к серверу MQTT. Все это делается на одном компьютере с Windows 11.

import time
import paho.mqtt.client as paho
import ssl
import pathlib
#define callbacks
def on_message(client, userdata, message):
  print("received message  = ",str(message.payload.decode("utf-8")))

def on_log(client, userdata, level, buf):
  print("log: ",buf)

def on_connect(client, userdata, flags, rc):
  print("publishing ")
  client.publish("muthu","muthupavithran",)


client=paho.Client()
client.on_message=on_message
client.on_log=on_log
client.on_connect=on_connect
print("connecting to broker")
client.tls_set(str(pathlib.Path().cwd() / pathlib.Path("Python-PKI/certs/Dev Cert 1.cert.pem")), tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)
client.connect("localhost", 1883, 60)

##start loop to process received messages
client.loop_start()
#wait to allow publish and logging and exit
time.sleep(1)

Когда сервер GoLang запускается и подключается тестовый клиент Paho, сервер выводит следующие ошибки:

time=2024-05-04T13:11:03.354-05:00 level=INFO msg = "added hook" hook=allow-all-auth
time=2024-05-04T13:11:03.357-05:00 level=INFO msg = "attached listener" id=t1 protocol=tcp address=[::]:1883
time=2024-05-04T13:11:03.357-05:00 level=INFO msg = "attached listener" id=ws1 protocol=wss address=:1882
time=2024-05-04T13:11:03.357-05:00 level=INFO msg = "attached listener" id=stats protocol=https address=:8080
time=2024-05-04T13:11:03.357-05:00 level=INFO msg = "inline client received message from subscription" client=inline subscriptionId=1 topic=direct/publish payload = "packet scheduled message"
time=2024-05-04T13:11:03.357-05:00 level=INFO msg = "mochi mqtt starting" version=2.6.3
time=2024-05-04T13:11:03.359-05:00 level=INFO msg = "mochi mqtt server started"
2024/05/04 13:11:03 Listening on :1883
2024/05/04 13:11:03 Listening on :1882
2024/05/04 13:11:03 Listening on :8080
2024/05/04 13:11:03 Running server
time=2024-05-04T13:11:20.865-05:00 level=WARN msg = "" listener=t1 error = "read connection: remote error: tls: unknown certificate authority"

Кроме того, клиент Paho печатает следующее:

C:\Users\me\go-mqtt-server\test\client.py:17: DeprecationWarning: Callback API version 1 is deprecated, update to latest version
  client=paho.Client()
connecting to broker
Traceback (most recent call last):
  File "C:\Users\me\go-mqtt-server\test\client.py", line 24, in <module>
    client.connect("localhost", 1883, 60)
  File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 1435, in connect
    return self.reconnect()
           ^^^^^^^^^^^^^^^^
  File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 1598, in reconnect
    self._sock = self._create_socket()
                 ^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 4612, in _create_socket
    sock = self._ssl_wrap_socket(sock)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\me\go-mqtt-server\test\Lib\site-packages\paho\mqtt\client.py", line 4671, in _ssl_wrap_socket
    ssl_sock.do_handshake()
  File "C:\Users\me\AppData\Local\Programs\Python\Python311\Lib\ssl.py", line 1346, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1002)

Я хотел бы, чтобы клиент мог подключаться только с сертификатом, подписанным локальным центром сертификации PKI. Что я делаю не так и как это исправить? Спасибо.

Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
1
0
250
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основываясь на утверждении «Тип аутентификации, который я хотел бы использовать, — TLS», я считаю, что вы намерены использовать самозаверяющие сертификаты для двух целей:

  • Проверка сервера (позволяет клиенту подтвердить, что сервер именно тот, который вы ожидаете) — для этого сервер использует Dev Cert 2.cert.
  • Аутентификация клиента. Я полагаю, что это ваше намерение, но клиент не передает сертификат, поэтому я не уверен (предполагаю, что вы собираетесь использовать Dev Cert 1 для аутентификации клиента).

Оба сертификата подписаны одним и тем же центром сертификации, что может немного запутать (в производственной среде, вероятно, лучше использовать отдельные центры сертификации). Таким образом, в этом случае сервер (для проверки сертификата клиента) и клиент (для проверки сертификата сервера) должны доверять ЦС (который вы создали с помощью Python-PKI).

Теперь вы говорите: «Я скопировал сертификаты клиента в каталог тестового клиента Python Paho». Я предполагаю, что это означает, что вы также скопировали ca.cert.pem, на который есть ссылка в вашем коде Go (если нет, вам нужно будет это сделать), и что это сертификат CA, сгенерированный Python-PKI (это означает, что на первый взгляд , сервер выглядит нормально).

Кстати, вероятно, полезно делать это шаг за шагом. то есть

  • Настройте mochi-server с помощью сертификата сервера, протестируйте что-то вроде MQTTX, а затем Python.
  • Добавьте требование сертификата клиента в mochi-server, проверьте, как указано выше.

В настоящее время вполне возможно, что проблемы есть как с клиентом, так и с сервером, поэтому полезно тестировать все по частям.

В любом случае в вашем коде Python вы вызываете:

client.tls_set(str(pathlib.Path().cwd() / pathlib.Path("Python-PKI/certs/Dev Cert 1.cert.pem")), tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)

Здесь есть несколько проблем:

  • tls_set только передается ca_certs (первый аргумент). Это означает, что сертификат клиента не будет отправлен на сервер (поэтому эта часть не будет выполнена, если она будет достигнута).
  • Сертификат передается, поскольку ca_certs не подписывал сертификат сервера (оба сертификата были подписаны одним и тем же центром сертификации)

Итак, я думаю, вам нужно что-то вроде (сокращенно):

client.tls_set("Python-PKI/certs/ca.cert.pem", "Python-PKI/certs/Dev Cert 1.cert.pem", "Python-PKI/certs/Dev Cert 1.cert.key", tls_version=ssl.PROTOCOL_TLSv1_2)
client.tls_insecure_set(True)

Обратите внимание, что tls_insecure_set(True) на самом деле не нужен, если вы выдаете эти сертификаты самостоятельно (вы можете установить CN на localhost). Этот параметр контролирует (немного упрощая) только то, подтверждает ли клиент, что CN в сертификате сервера соответствует имени хоста (сертификат по-прежнему будет проверяться на предмет его действительности и выдачи доверенным центром сертификации). При необходимости вы можете изменить это с помощью параметра cert_reqs (по умолчанию — ssl.CERT_REQUIRED).

Надеюсь, вышеизложенное поможет, если нет, то попробуйте упростить свой вопрос (например, сосредоточьтесь на сервере и протестируйте с помощью MQTTX), поскольку здесь много переменных.

Обновление: несколько комментариев о том, почему для сервера и клиента будет использоваться отдельный центр сертификации? Цель здесь состоит в том, чтобы создать закрытую систему, в которой только клиенты, использующие сертификат, который подписывает мой собственный центр сертификации, могут аутентифицироваться на сервере MQTT.

Сертификаты клиента и сервера выполняют разные роли, и, на мой взгляд, единственное преимущество использования одного и того же центра сертификации заключается в том, что это упрощает задачу (немного!). Вот несколько причин, почему их разделение может быть хорошей идеей:

  • Безопасность
    • Наименьшие привилегии: скорее всего, клиентский ЦС будет использоваться довольно часто (при добавлении клиентов), тогда как серверный ЦС будет использоваться редко (как часто вы добавляете новый сервер?). Это означает, что вам может потребоваться предоставить доступ к одному (для поддержки персонала и т. д.), но не к другому (гораздо сложнее, если это один и тот же центр сертификации).
    • Изоляция: если один ЦС скомпрометирован, другой остается незатронутым (на самом деле, если в вашем сценарии скомпрометирован любой из них, у вас возникнут проблемы!).
    • При использовании одного центра сертификации, если кто-то может генерировать клиентские сертификаты, он также потенциально может выдать себя за ваш сервер (поскольку сертификат клиента может использоваться на сервере). Обратное также применимо.
  • Возможно, вы захотите использовать для сервера доверенный сторонний центр сертификации (например, позволяет шифровать), чтобы избежать необходимости распространять собственный сертификат центра сертификации.
  • Ясность — использование двух центров сертификации упрощает понимание модели доверия (т. е. в вашем Python у вас будет server-ca.pem, который дает понять, что он используется для проверки сертификата сервера).

Это несколько мыслей, которые пришли мне в голову.

Спасибо за этот очень полезный ответ. Мне удалось заставить все работать, предоставив сертификат и ключ ca.cert.pem, Dev Cert 1, как вы предложили.

PhilBot 05.05.2024 21:13

Не могли бы вы дать несколько комментариев о том, почему отдельный центр сертификации будет использоваться как для сервера, так и для клиента? Причина, по которой я спрашиваю, заключается в том, что цель здесь состоит в том, чтобы создать закрытую систему, в которой только клиенты, использующие сертификат, который подписывает мой собственный центр сертификации, могут аутентифицироваться на сервере MQTT. Спасибо.

PhilBot 06.05.2024 00:04

Другие вопросы по теме