RabbitMQ TLS Authentication
Overview
RabbitMQ is a messaging broker - an intermediary for messaging. It gives your applications a common platform to send and receive messages, and your messages a safe place to live until received.
Prerequisite
For simulation, I have installed the RabbitMQ on local.
OpenSSL utility
RabbitMQ broker.
The Short Route to Generating a CA, Certificates, and Keys
This guide assumes the user has access to a CA certificate bundle file and two certificate/key pairs. The certificate/key pairs are used by RabbitMQ and clients that connect to the server on a TLS-enabled port. The process of generating a Certificate Authority and two key pairs is fairly laborious and can be error-prone. An easier way of generating all that stuff on macOS or Linux is with tls-gen: it requires Python 3.5+, make, and OpenSSL in PATH.
Note that tls-gen and the certificate/key pairs it generates are self-signed and only suitable for development and test environments. The vast majority of production environments should use certificates and keys issued by a widely trusted commercial CA.
Client-Side Configuration
Using tls-gen's Basic Profile'
Let’s clone the tls-gen utility and generate the certificates.
git clone https://github.com/rabbitmq/tls-gen tls-gen cd tls-gen/basic # private key password make PASSWORD=1234 make verify make info
global@del1-lmc-n72765 basic % pwd /Users/global/Documents/tls-gen1/basic
-rw-r--r-- 1 global staff 662 Jun 5 11:55 Makefile
-rw-r--r-- 1 global staff 2228 Jun 5 11:55 README.md
drwxr-xr-x 6 global staff 192 Jun 5 11:56 client_del1-lmc-n72765.local
-rw-r--r-- 1 global staff 2375 Jun 5 11:55 openssl.cnf
-rw-r--r-- 1 global staff 4499 Jun 5 11:55 profile.py
drwxr-xr-x 10 global staff 320 Jun 5 11:56 result
drwxr-xr-x 6 global staff 192 Jun 5 11:56 server_del1-lmc-n72765.local
drwxr-xr-x 12 global staff 384 Jun 5 11:56 testcals -l ./result
-rw-r--r-- 1 global staff 1.2K May 31 16:02 ca_certificate.pem
-rw-r--r-- 1 global staff 1.7K May 31 16:02 ca_key.pem
-rw-r--r-- 1 global staff 1.3K May 31 16:02 client_del1-lmc-n72765.local_certificate.pem
-rw-r--r-- 1 global staff 2.5K May 31 16:02 client_del1-lmc-n72765.local_key.p12
-rw-r--r-- 1 global staff 1.7K May 31 16:02 client_del1-lmc-n72765.local_key.pem
-rw-r--r-- 1 global staff 1.4K May 31 16:02 server_del1-lmc-n72765.local_certificate.pem
-rw-r--r-- 1 global staff 2.5K May 31 16:02 server_del1-lmc-n72765.local_key.p12
-rw-r--r-- 1 global staff 1.7K May 31 16:02 server_del1-lmc-n72765.local_key.pem
In this steps , we gonna rename a few of directories , because host name is appended by default-
mv client_del1-lmc-n72765.local client
mv server_del1-lmc-n72765.local server
global@del1-lmc-n72765 basic % ls -ll
-rw-r--r-- 1 global staff 662 Jun 5 11:55 Makefile
-rw-r--r-- 1 global staff 2228 Jun 5 11:55 README.md
drwxr-xr-x@ 6 global staff 192 Jun 5 16:24 client
-rw-r--r-- 1 global staff 2375 Jun 5 11:55 openssl.cnf
-rw-r--r-- 1 global staff 4499 Jun 5 11:55 profile.py
drwxr-xr-x@ 10 global staff 320 Jun 5 13:08 result
drwxr-xr-x@ 6 global staff 192 Jun 5 16:24 server
drwxr-xr-x@ 12 global staff 384 Jun 5 13:08 testca
Copy the created certificates, and change the owner.
below copying a few directories like testca, server, client to RabbitMQ installed directories( in my mac it’s shown below but it can be different in your system ) /opt/homebrew/etc/rabbitmq
mv testca/ /opt/homebrew/etc/rabbitmq/
mv server/ /opt/homebrew/etc/rabbitmq/
mv client/ /opt/homebrew/etc/rabbitmq/Let’s change the permission-( i have not done this but solution still working)
chown -R rabbitmq: /opt/homebrew/etc/rabbitmq/testca
chown -R rabbitmq: /opt/homebrew/etc/rabbitmq/server
chown -R rabbitmq: /opt/homebrew/etc/rabbitmq/clientConnecting with Peer Verification Enabled
For a Java client to trust a server, the server certificate must be added to a trust store which will be used to instantiate a Trust Manager. The JDK ships with a tool called keytool that manages certificate stores. To import a certificate to a store use keytool -import:
global@del1-lmc-n72765 result % pwd
/Users/global/Documents/tls-gen1/basic/result
cd /Users/global/Documents/tls-gen1/basic/result
mv server_del1-lmc-n72765.local_certificate.pem server_certificate.pem
keytool -import -alias server1 -file server_certificate.pem -keystore rabbitstore This will ask you you enter a password, Please note that the password is somewhere, it will be required in the java client for the connectivity.
I used 👇 (you will find it used in the java client at the end of blog)
e.g password→ welcome
The above command will import server/certificate.pem into the rabbitstore file using the JKS format.
Import certificate to JDK cacert store
Import the certificate from the server.
Use the given command to add the certificate to the JDK store.
keytool -import -trustcacerts -alias Server_cert1 -file /opt/homebrew/etc/rabbitmq/testca/cacert.cer -keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/jre/lib/security/cacertsThis will ask you to enter the password by default: changeit
Notice the code : SSLContext.getInstance("TLSv1.2"). This code picks up the certificates added to JDK cacert store. So make a note of it.
Now we are done with client configuration, let's move to server configuration.
Server-side Configuration
The New and Old Config File Formats
A compare this exemplary rabbitmq.conf file
We bring the configuration file to the form (/opt/homebrew/etc/rabbitmq/advanced .config) ,
The new config format is much simpler, easier for humans to read and machines to generate. It is also relatively limited compared to the classic config format used prior to RabbitMQ 3.7.0.
To accommodate this need, modern RabbitMQ versions allow for both formats to be used at the same time in separate files: rabbitmq.conf uses the new style format and is recommended for most settings, and advanced.config covers more advanced settings that the ini-style configuration cannot express.
By default, these files may not be there, in the rabbitMQ home dir in my Mac, give path /opt/homebrew/etc/rabbitmq/,
So I had to create advanced.config and update the following configuration
[
{ssl, [{versions, ['tlsv1.3', 'tlsv1.2','tlsv1.1']}]},
{rabbit, [
{ssl_listeners, [5671]},
{auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'EXTERNAL']},
{ssl_options, [{cacertfile, "/opt/homebrew/etc/rabbitmq/testca/cacert.pem"},
{certfile, "/opt/homebrew/etc/rabbitmq/server/cert.pem"},
{keyfile, "/opt/homebrew/etc/rabbitmq/server/key.pem"},
{verify, verify_peer},
{password, "1234"},
{fail_if_no_peer_cert, true}]}
]}
].What are all these parameters in the config file? 🙀
Note to Windows users: backslashes ("\") in the configuration file are interpreted as escape sequences - so for example to specify the path c:\ca_certificate.pem for the CA certificate you would need to use "c:\ca_certificate.pem" or "c:/ca_certificate.pem".
ssl_listeners→ 5671, this is a secure port.
ssl→Set according to listed on the clients.
NOTE: check this using the following command on the client.
rabbitmq-diagnostics --silent tls_versionsrabbitmq-diagnostics.bat --silent tls_versionscacertfile→ Certificate Authority (CA) bundle file path
certfile→ Server certificate file path
keyfile→ Server private key file path
verify→verify option is set to verify_peer, the client does send us a certificate, the node must perform peer verification. When set to verify_none, peer verification will be disabled and certificate exchange won't be performed.
fail_if_no_peer_cert→ fail_if_no_peer_cert option to false tells the node to accept clients which don't present a certificate (for example, were not configured to use one).
Now we are done with client and server configuration.
Reboot the broker and enable the authentication.
sudo rabbitmq-plugins enable --offline rabbitmq_auth_mechanism_ssl
brew services restart rabbitmq
In order to verify whether SSL is configured correctly or not, check the server logs.
tail -f /opt/homebrew/var/log/rabbitmq/rabbit@localhost.log
🤞🏻Figure crossed -🧑🏽💻
2022-06-05 09:32:23.411366+05:30 [info] <0.470.0> Ready to start client connection listeners
2022-06-05 09:32:23.413873+05:30 [info] <0.662.0> started TCP listener on 127.0.0.1:5672
2022-06-05 09:32:23.414784+05:30 [info] <0.682.0> started TLS (SSL) listener on [::]:5671If you see the above logs , check whether client is able to connet with server using client certificate.
openssl s_client -tls1 -connect localhost:5671 -CAfile /opt/homebrew/etc/rabbitmq/testca/cacert.pem -cert /opt/homebrew/etc/rabbitmq/client/cert.pem -key /opt/homebrew/etc/rabbitmq/client/key.pem
This will ask you to enter the password which you’veve used to generate the certificates. e.g 1234
Client logs ,
SSL handshake has read 2499 bytes and written 2363 bytes
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-SHA
Server public key is 2048 bit
Server log.
tail -f /opt/homebrew/var/log/rabbitmq/rabbit@localhost.log
2022-06-05 09:32:29.670544+05:30 [info] <0.689.0> connection <0.689.0> (127.0.0.1:49571 -> 127.0.0.1:5671): user 'guest' authenticated and granted access to vhost '/'
2022-06-05 09:32:29.723409+05:30 [info] <0.689.0> closing AMQP connection <0.689.0> (127.0.0.1:49571 -> 127.0.0.1:5671, vhost: '/', user: 'guest')
👉 That’s great news, the client is authenticated successfully. 🎉🎉
Huuh ! we just check the authentication using the cli , but how to implement the same in the code 🤔🤔
Let’s look at the java client.
package com.rm.rabbitmq.tls;
import java.io.*;
import java.security.*;
import javax.net.ssl.*;
import com.rabbitmq.client.*;
public class Example2 {
public static void main(String[] args) throws Exception {
char[] keyPassphrase = "1234".toCharArray();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(new FileInputStream("/Users/global/Documents/tls-gen/basic/result/client_del1-lmc-n72765.local_key.p12"), keyPassphrase);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, keyPassphrase);
char[] trustPassphrase = "welcome".toCharArray();
KeyStore tks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream("/Users/global/Documents/tls-gen/basic/result/rabbitstore"), trustPassphrase);
// tks.load(new FileInputStream("/Library/Java/JavaVirtualMachines/jdk1.8.0_311.jdk/Contents/Home/jre/lib/security/cacerts"), trustPassphrase);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(tks);
SSLContext c = SSLContext.getInstance("TLSv1.2");
c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5671);
factory.useSslProtocol(c);
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.queueDeclare("rabbitmq-java-test", false, true, true, null);
channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());
GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);
if (chResponse == null) {
System.out.println("No message retrieved");
} else {
byte[] body = chResponse.getBody();
System.out.println("Received: " + new String(body));
}
//channel.close();
conn.close();
}
}Let’s check the logs, In the above example I sent the “hello word” to check the client-server authentication.
javax.net.ssl|FINE|0C|pool-1-thread-1|2022-06-05 09:32:29.723 IST|SSLSocketOutputRecord.java:87|Raw write (
0000: 15 03 03 00 1A 00 00 00 00 00 00 00 0A DA DC CE ................
0010: 98 9B 38 C4 33 A0 44 61 6C C7 A8 0C 95 95 B9 ..8.3.Dal......
)
Received: Hello, World
Spring Cloud Stream with RabbitMQ
If you are using the spring cloud stream for rabbitMQ then configure the client using the properties file. so no code change is required for existing services.
application.properties
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5671
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.ssl.enabled=true
spring.rabbitmq.ssl.algorithm=TLSv1.2
spring.rabbitmq.ssl.trust-store=file:/Users/global/tls-gen/basic/result/rabbitstore
spring.rabbitmq.ssl.trust-store-password=welcome
//changeit
spring.rabbitmq.ssl.trust-store-type=JKS
spring.rabbitmq.ssl.key-store=file:/Users/global/tls-gen/basic/result/client_del1-lmc-72770.p12
spring.rabbitmq.ssl.key-store-password=1234If you followed the blog!
Congratulations🎉🎉, you have successfully implemented the TLS. 👏👏
Stay tune for more technical solutions.
if you like the above post, please don’t forget to subscribe. 🙏🙏
What is next?
I am planning to create a solution on docker, what could possibly replace it?
