使用 pika 与 RabbitMQ 进行 TLS 加密连接

Ale*_*dro 9 ssl rabbitmq pika

我发现无法在客户端使用 python 的 pika 库与 RabbitMQ 代理建立加密连接。我的出发点是此处的鼠兔教程示例,但我无法使其工作。我已进行如下操作。

(1) RabbitMQ配置文件为:

listeners.tcp.default = 5672
listeners.ssl.default = 5671

ssl_options.verify               = verify_peer
ssl_options.fail_if_no_peer_cert = false
ssl_options.cacertfile           = /etc/cert/tms.crt
ssl_options.certfile             = /etc/cert/tms.crt
ssl_options.keyfile              = /etc/cert/tmsPrivKey.pem

auth_mechanisms.1 = PLAIN
auth_mechanisms.2 = AMQPLAIN
auth_mechanisms.3 = EXTERNAL
Run Code Online (Sandbox Code Playgroud)

(2) 使用以下命令启用rabbitmq-auth-mechanism-ssl插件:

rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl
Run Code Online (Sandbox Code Playgroud)

通过以下方式检查启用状态来确认启用成功:rabbitmq-plugins list

(3)使用 openssl 工具验证TLS 证书的正确性,如下所述。

(4)建立连接的客户端程序为:

#!/usr/bin/env python
import logging
import pika
import ssl
from pika.credentials import ExternalCredentials

logging.basicConfig(level=logging.INFO)
context = ssl.create_default_context(
                        cafile="/Xyz/sampleNodeCert/tms.crt")
context.load_cert_chain("/Xyz/sampleNodeCert/node.crt",
                        "/Xyz/sampleNodeCert/nodePrivKey.pem")

ssl_options = pika.SSLOptions(context, '127.0.0.1')
conn_params = pika.ConnectionParameters(host='127.0.0.1',
                                        port=5671,
                                        ssl_options=ssl_options,
                                        credentials=ExternalCredentials())

with pika.BlockingConnection(conn_params) as conn:
     ch = conn.channel()
     ch.queue_declare("foobar")
     ch.basic_publish("", "foobar", "Hello, world!")
     print(ch.basic_get("foobar"))
Run Code Online (Sandbox Code Playgroud)

(5) 客户端程序失败并出现以下错误消息

pika.exceptions.ProbableAuthenticationError: ConnectionClosedByBroker: (403) 'ACCESS_REFUSED - Login was refused using authentication mechanism EXTERNAL. For details see the broker logfile.'
Run Code Online (Sandbox Code Playgroud)

(6) RabbitMQ 代理中的日志消息为:

2019-10-15 20:17:46.028 [info] <0.642.0> accepting AMQP connection <0.642.0> (127.0.0.1:48252 -> 127.0.0.1:5671)
2019-10-15 20:17:46.032 [error] <0.642.0> Error on AMQP connection <0.642.0> (127.0.0.1:48252 -> 127.0.0.1:5671, state: starting):
EXTERNAL login refused: user 'CN=www.node.com,O=Node GmbH,L=NodeTown,ST=NodeProvince,C=DE' - invalid credentials
2019-10-15 20:17:46.043 [info] <0.642.0> closing AMQP connection <0.642.0> (127.0.0.1:48252 -> 127.0.0.1:5671)
Run Code Online (Sandbox Code Playgroud)

(7)本次测试的环境是Ubuntu 18.04,在Erlang 22.0.7上使用RabbitMQ 3.7.17。在客户端,使用了python3版本3.6.8。

问题:有人知道我的测试失败的原因吗?在哪里可以找到使用 pika 设置与 RabbitMQ 的加密连接的完整工作示例?

注意:我很熟悉这篇文章,但文章中的任何提示都没有帮助我。

Ale*_*dro 9

After studying the link provided above by Luke Bakken, I am now in a position to answer my own question. The main change with respect to my original example is that I configure the RabbitMQ broker with a passwordless user which has the same name as the CN field of the TLS certificate on both the server and the client side. To illustrate, below, I go through my example again in detail:

(1) The RabbitMQ configuration file is:

listeners.tcp.default = 5672
listeners.ssl.default = 5671

ssl_cert_login_from = common_name

ssl_options.verify               = verify_peer
ssl_options.fail_if_no_peer_cert = true
ssl_options.cacertfile           = /etc/cert/tms.crt
ssl_options.certfile             = /etc/cert/tms.crt
ssl_options.keyfile              = /etc/cert/tmsPrivKey.pem

auth_mechanisms.1 = EXTERNAL
auth_mechanisms.2 = PLAIN
auth_mechanisms.3 = AMQPLAIN
Run Code Online (Sandbox Code Playgroud)

Note that, with the ssl_cert_login_from configuration option, I am asking for the username of the RabbitMQ account to be taken from the "common name" (CN) field of the TLS certificate.

(2) The rabbitmq-auth-mechanism-ssl plugin is enabled with the following command:

rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl
Run Code Online (Sandbox Code Playgroud)

Successful enabling can be confirmed by checking the enable status through command: rabbitmq-plugins list.

(3) The signed TLS certificate must have the issuer and subject CN fields equal to each other and equal to the hostname of the RabbitMQ broker node. In my case, inspection of the RabbitMQ log file (in /var/log/rabbitmq) shows that the broker is running on a node called: rabbit@pnp-vm2. The host name is therefore pnp-vm2. In order to check the CN fields of the client-side certificate, I use the following command:

ap@pnp-vm2:openssl x509 -noout -text -in /etc/cert/node.crt | fgrep CN
        Issuer: C = CH, ST = CH, L = Location, O = Organization GmbH, CN = pnp-vm2
        Subject: C = DE, ST = NodeProvince, L = NodeTown, O = Node GmbH, CN = pnp-vm2
Run Code Online (Sandbox Code Playgroud)

As you can see, both the Issuer CN field and the Subject CN Field are equal to: "pnp-vm2" (which is the hostname of the RabbitMQ broker, see above). I tried using this name for only one of the two CN fields but then the connection to the broker could not be established. In my test environment, it was easy to create a client certificate with identical CN names but, in an operational environment, this may be a lot harder to do. Also, I do not quite understand the reason for this constraint: is it a bug or it is a feature? And does it originate in the particular RabbitMQ library I am using (python's pika) or in the AMQP protocol? These question probably deserve a dedicated post.

(4) The client-side program to set up the connection is:

#!/usr/bin/env python
import logging
import pika
import ssl
from pika.credentials import ExternalCredentials

logging.basicConfig(level=logging.INFO)
context = ssl.create_default_context(cafile="/home/ap/RocheTe/cert/sampleNodeCert/tms.crt")
context.load_cert_chain("/home/ap/RocheTe/cert/sampleNodeCert/node.crt",
                        "/home/ap/RocheTe/cert/sampleNodeCert/nodePrivKey.pem")

ssl_options = pika.SSLOptions(context, 'pnp-vm2')
conn_params = pika.ConnectionParameters(host='a.b.c.d',
                                        port=5671,
                                        ssl_options=ssl_options,
                                        credentials=ExternalCredentials(),
                                        heartbeat=0)

with pika.BlockingConnection(conn_params) as conn:
    ch = conn.channel()
    ch.queue_declare("foobar")
    ch.basic_publish("", "foobar", "Hello, world!")
    print(ch.basic_get("foobar"))
    input("Press Enter to continue...")
Run Code Online (Sandbox Code Playgroud)

Here, "a.b.c.d" is the IP address of the machine on which the RabbitMQ broker is running.

(5) The environment in which this test was done is Ubuntu 18.04 using RabbitMQ 3.7.17 on Erlang 22.0.7. On the client side, python3 version 3.6.8 was used.

One final word of warning: with this configuration, I was able to establish a secure connection to the RabbitMQ Broker but, for reasons which I still do not understand, it became impossible to start the RabbitMQ Web Management Tool...