SSL and Rabbit MQ

SSL, three small letters, a lot of work.

My original, and rather flawed, idea to sort out the access control to Rabbit was to encrypt the password with the account’s public key.  Our security person rightly pointed out that this is rubbish and that we should use SSL.  So that’s what I did and here’s what happened…

 

Step 1: Get a client .net application running on client/dev machine authenticating against a local RabbitMQ instance

I created a batch script that created a set of test keys and certificates for the Client, Rabbit Server and the Certificate Authority so that I did not have to depend on the security network people, at first.  The bat file creates the certificates that I used.  The batch file is an amalgamation of the commands in the rabbit mq ssl guide.  It allowed me to recreate the certificates at will without any manual fiddling with files.  What I was trying to do was make it reproduce able as possible for when we apply it to the real server.

Certificate Create Batch File…. (SetUpCerts.bat)

SET LocationOfCerts=C:\dev\RabbitCerts
SET LocationOfStaticContent=C:\dev\RabbitMqSecurity\CreateCerts
SET Machine=PeterFinch

cd %LocationOfCerts%

rem Setup file structure

rm * -r -f 
mkdir testca
cd testca
mkdir certs private
chmod 700 private
echo 01 > serial
touch index.txt

rem Copy SSL Config File
ROBOCOPY %LocationOfStaticContent%\ %LocationOfCerts%\testca openssl.conf

rem TEST CA
openssl req -x509 -config %LocationOfCerts%\testca\openssl.conf -newkey rsa:2048 -days 365 -out cacert.pem -outform PEM -subj /CN=MyTestCA/ -nodes
openssl x509 -in cacert.pem -out cacert.cer -outform DER

rem Server Keys
cd ..
mkdir server
cd server
openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out req.pem -outform PEM -subj /CN=%Machine%/O=server/ -nodes -config %LocationOfCerts%\testca\openssl.conf
cd ../testca
openssl ca -config %LocationOfCerts%\testca\openssl.conf -in ../server/req.pem -out ../server/cert.pem -notext -batch -extensions server_ca_extensions
cd ../server
openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword

rem Client Keys
cd ..
ls
# => server testca
mkdir client
cd client
openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out req.pem -outform PEM -subj /CN=%Machine%/O=client/ -nodes -config %LocationOfCerts%\testca\openssl.conf
cd ../testca
openssl ca -config %LocationOfCerts%\testca\openssl.conf -in ../client/req.pem -out ../client/cert.pem -notext -batch -extensions client_ca_extensions
cd ../client
openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword

cd %LocationOfStaticContent%

 

Open SSL Configuration File….  (openssl.conf)

 

[ ca ]
default_ca = testca

[ testca ]
dir = .
certificate = $dir/cacert.pem
database = $dir/index.txt
new_certs_dir = $dir/certs
private_key = $dir/private/cakey.pem
serial = $dir/serial

default_crl_days = 7
default_days = 365
default_md = sha256

policy = testca_policy
x509_extensions = certificate_extensions

[ testca_policy ]
commonName = supplied
stateOrProvinceName = optional
countryName = optional
emailAddress = optional
organizationName = optional
organizationalUnitName = optional
domainComponent = optional

[ certificate_extensions ]
basicConstraints = CA:false

[ req ]
default_bits = 2048
default_keyfile = ./private/cakey.pem
default_md = sha256
prompt = yes
distinguished_name = root_ca_distinguished_name
x509_extensions = root_ca_extensions

[ root_ca_distinguished_name ]
commonName = hostname

[ root_ca_extensions ]
basicConstraints = CA:true
keyUsage = keyCertSign, cRLSign

[ client_ca_extensions ]
basicConstraints = CA:false
keyUsage = digitalSignature
extendedKeyUsage = 1.3.6.1.5.5.7.3.2

[ server_ca_extensions ]
basicConstraints = CA:false
keyUsage = keyEncipherment
extendedKeyUsage = 1.3.6.1.5.5.7.3.1

 

I used the standard .Net RabbitMq Client to try to connect and had no luck.

Code 1: Connect to Rabbit with SSL

var hostName = "my-local-machine";
 
 
var cf = new ConnectionFactory
{
        HostName = hostName,
        VirtualHost = "/",
        AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() },
        Ssl = new SslOption
        {
               Enabled = true,
               ServerName = hostName,
               AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch | SslPolicyErrors.RemoteCertificateChainErrors, <-- had to set because the keys are not trusted
               CertPath = @"C:\dev\RabbitCerts\client\keycert.p12",
               CertPassphrase = "MySecretPassword"  <--- Yes I know!  But in the real world the service user key will be used and this will not be required
        }
};

This code used the Client public key and private key (with password).  This was just for testing and I know that there is a password in there.

I then used this command which allows you to test connections.

C:\dev\RabbitCerts>openssl s_client -connect localhost:5671 -tls1 -cert client/cert.pem -key client/key.pem -CAfile testca/cacert.pem
This worked and this appeared in the RabbitMQ log…

=INFO REPORT==== 27-Jun-2017::14:29:41 === accepting AMQP connection <0.695.0> (127.0.0.1:60657 -> 127.0.0.1:5671)

I now knew that something was at least working.  The problem was was that I could not connect using the .Net client.

A bit of a breakthrough came when I found that this needs to be run.   This was NOT mentioned in the RabbitMQ SSL Setup Guide page.

rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl

This was the break though and then the code in “Code 1” worked.  This was the result.  You can see that “PeterFinch” is connected using SSL.  Importantly “Finch Peter” cannot login using a password.

 

This is the Config file that worked..

[
        {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},
        {rabbit, [
               {ssl_listeners, [5671]},
               {ssl_options, [{cacertfile,"C:/dev/RabbitCerts/testca/cacert.pem"},
               {certfile,"C:/dev/RabbitCerts/server/cert.pem"},
               {keyfile,"C:/dev/RabbitCerts/server/key.pem"},
               {verify,verify_peer},
               {fail_if_no_peer_cert,true}]},
               {auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'EXTERNAL']},
               {ssl_cert_login_from, common_name} 
        ]}
].

Step 2: Getting a connection using a real certificate (not a fake one created by me) and connecting with my “cn= finch peter” account

The next thing was to use real certificates.  I had to contact the server team who created a certificate for my local dev machine that was running the rabbitmq server.  These certificates then used here…..

[
        {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},
        {rabbit, [
               {ssl_listeners, [5671]},
               {ssl_options, [{cacertfile,"C:/dev/RabbitCerts/testca/cacert.pem"},
               {certfile,"C:/dev/RabbitCerts/server/cert.pem"},  <------- PUBLIC KEY OF SERVER 
               {keyfile,"C:/dev/RabbitCerts/server/key.pem"},    <--------PRIVATE KEY OF SERVER (has to be unencrypted)
               {verify,verify_peer},
               {fail_if_no_peer_cert,true}]},
               {auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'EXTERNAL']},
               {ssl_cert_login_from, common_name} 
        ]}
].
The CN values of the keys are all very important.  For example the CN of the server key has to match that of the server on which it is running.  e.g. my test machine is called icpdev-finch so the CN of the key had to be devmachine-finch (cn= devmachine -finch.someplace)

For this to work the client code has to be changed so that it was getting my certificates from my key store…

You can see that I am opening up the x509Store finding the key with my thumbprint and then passing that into the ssl Certs option later on.

            var hostName = " devmachine-FINCH";
 
            X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            store.Open(OpenFlags.ReadOnly);
 
            X509Certificate cert = store.Certificates
                .Find(
                    X509FindType.FindByThumbprint,
                    "E569C6AEF012F835ABC48E674A42F257950C6593",
                    true
                )
                .OfType<X509Certificate>()
                .First();
 
             var cf = new ConnectionFactory
            {
                HostName = hostName,
 
                VirtualHost = "/",
                AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() },  <---- this is important will not work without relates to "{auth_mechanisms, ['PLAIN', 'AMQPLAIN', 'EXTERNAL']}" in 
                                                                                                    -- config file
 
                Ssl = new SslOption
                {
                    Enabled = true,
                    ServerName = hostName,
 
                    AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateNameMismatch | SslPolicyErrors.RemoteCertificateChainErrors,
 
                    Certs = new X509CertificateCollection(new X509Certificate[] { cert })
 
}
 
};

This worked and we can see this in the RabbitMQ console.  You can see that “Finch Peter” is connected and that the “SSL / TLS” dot is filled in saying that it’s a SSL connection.  Also it’s not possible for “Peter Finch” to login with a user name and password via the standard authentication route.

Step 3: Get the client talking to a RabbitMQ server with SSL set up that is on a separate machine.

This step will allow us to test a .Net Client talking to a remote RabbitMQ server.

It’s important that any changes made are reproducible and scripted.  I don’t want to be manually fiddling with files on the prod server.

1: Certificate created for RabbitMQ server

2: Script created for extracting keys and copying over the config file

3: RabbitMQ  was not reading the config file.

config file(s) : c:/RabbitMQ/rabbitmq.config (not found)
I had to re-install the RabbitMQ service with….

rabbitmq-service.bat uninstall
rabbitmq-service.bat install
to pick up the %RABBITMQ_BASE% setting.

config file now reads

config file(s) : c:/RabbitMQ/rabbitmq.config
I checked the prod server and the config file is being read so I don’t think this will be a problem on prod server

4: Tried to connect with the client on development machine but got

{“No connection could be made because the target machine actively refused it 10.10.243.102:5671”}
added firewall rule to open port on RabbitMQ2 but problem remains

5: Asked Network team to add rule to firewall to open 5671 to RabbitMQ1 and RabbitMQ2

6: Tested and now working.  This is the script file that extracts the keys and sets up the config file for rabbit.

 

Step 4: Setup SSL on the production server and not break anything

Had to enable RABBITMQ 5671 SSL firewall rule on rabbit production server to allow SSL traffic into the server.

Also the server key is rabbitmq1.somewhere.org not rabbitmq1

Needed to remember that when we migrate accounts we need to use rabbitmq1.somewhere.org as server to connect to.

 

 

Step 5: Migrate accounts to use SSL

Service accounts that run the service do not have certificates.  We had to ask the network team to create certificates for the accounts.

 

 

Useful pages….

Webpage Comments
RabbitMQ SSL Setup Guide Basic howto from Rabbit. Major ommision of not stating that you need to run

rabbitmq-plugins enable rabbitmq_auth_mechanism_sslalso this is missing from the instructions which needs to be in the config file…

{auth_mechanisms, [‘EXTERNAL’, ‘PLAIN’]},

RabbitMQ SSL Setup Guide Troubleshooting  
Establishing-ssl-connection-to-rabbitmq Shows how to use x509 from cert store
SSL and Pasword support How to configure for both SSL and Plain (username/password) authentication
Configuring Client authentication via certificates             Interesting points of enabling SSL Plugin….

rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl

and setting the correct auth mechanisms

[

{rabbit,  [

{ssl_listeners, [5671]},

{auth_mechanisms, [‘EXTERNAL’, ‘PLAIN’]},

{ssl_options, [{cacertfile,”D:/RabbitMQ/certs/MyCA.pem”},

{certfile,”D:/RabbitMQ/certs/MyRabbitServer.pem”},

{keyfile,”D:/RabbitMQ/certs/MyRabbitServer.key”},

{verify,verify_peer}, {ssl_cert_login_from, common_name}, {fail_if_no_peer_cert,true}]}

]}].

 

 

Leave a comment