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