TLS-Encrypted Connection with RabbitMQ Using pika
Asked Answered
B

3

9

I am finding it impossible to set up an encrypted connection with a RabbitMQ broker using python's pika library on the client side. My starting point was the pika tutorial example here but I cannot make it work. I have proceeded as follows.

(1) The RabbitMQ configuration file was:

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

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

rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl

Successful enabling was confirmed by checking the enable status through: rabbitmq-plugins list.

(3) The correctness of the TLS certificates was verified by using openssl tools as described here.

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

#!/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"))

(5) The client-side program failed with the following error message:

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

(6) The log message in the RabbitMQ broker was:

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)

(7) 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.

Questions: Does anyone have any idea as to why my test fails? Where can I find a complete working example of setting up an encrypted connection to RabbitMQ using pika?

NB: I am familiar with this post but none of the tips in the post helped me.

Byrom answered 15/10, 2019 at 21:8 Comment(1)
Please ask on the pika-python mailing list and we can continue discussion there. I maintain Pika and I have set up client-certificate authentication so I know it works. Most likely the issue is in the order of auth_mechanisms (try putting EXTERNAL first) or how you created the CN=www.node.com,O=Node GmbH,L=NodeTown,ST=NodeProvince,C=DE user in RabbitMQ. I'm assuming you're using Pika 1.1.0. See this message as well.Splenic
B
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

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

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

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...")

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...

Byrom answered 17/10, 2019 at 14:51 Comment(1)
I think you should try to add something like management prefixed config management.ssl.port = 15671 management.ssl.cacertfile = /ssl/CA.cer management.ssl.certfile = /ssl/cert.cer management.ssl.fail_if_no_peer_cert = false management.ssl.keyfile = /ssl/cert.key management.ssl.verify = verify_none management.ssl.versions.1 = tlsv1.2Algonquin
D
1

To anyone trying to do this with pika the answer is really

set host to the CN name of your client_certificate in the client_certificate.pem file

it will look something like

/CN=..local/O=client

you only need ..local if it is a self signed certificate

Dissolve answered 15/6, 2023 at 21:14 Comment(0)
J
0

It would be really nice if the official(?) pika Docs were updated with the answer from Alessandro. I can confirm that the implementation as it is described right now, does not work.

Proposed Changes for tls_example.py:

from pika.credentials import ExternalCredentials
# (...)
conn_params = pika.ConnectionParameters(host='localhost',
                                        port=5671,
                                        ssl_options=ssl_options,
                                        credentials = ExternalCredentials())
                                        
# instead of
conn_params = pika.ConnectionParametersport=5671,
                                        ssl_options=ssl_options)

                

Proposed changes for rabbitmq.conf:

# Enable AMQPS
listeners.ssl.default = 5671
ssl_options.cacertfile = PIKA_DIR/testdata/certs/ca_certificate.pem
ssl_options.certfile = PIKA_DIR/testdata/certs/server_certificate.pem
ssl_options.keyfile = PIKA_DIR/testdata/certs/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true
ssl_cert_login_from = common_name
auth_mechanisms.1 = EXTERNAL

# instead of
# Enable AMQPS
listeners.ssl.default = 5671
ssl_options.cacertfile = PIKA_DIR/testdata/certs/ca_certificate.pem
ssl_options.certfile = PIKA_DIR/testdata/certs/server_certificate.pem
ssl_options.keyfile = PIKA_DIR/testdata/certs/server_key.pem
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = true

Thank you in advance.

Jamestown answered 8/2 at 7:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.