Tuesday, December 27, 2011

Establishing a SSL connection to RabbitMQ using the .NET client

First, I'm making the assumption that you've read, re-read, and followed the SSL tutorial on rabbitmq's website.  If you haven't done everything that it's instructed you to do (including adding your certificates to the Windows Certificate Store using certmgr), none of this code is going to work for you.  If you have, this code should "Just Work™".

Here's the complete code file that works for me.  You will (obviously) need to change the names of the servers, and the thumbprint of your certificate.

Note that this code only uses your client and server's certificates to establish a secure connection.  You are still logging in as guest.  I will show you how to use your client certificate to authenticate yourself below.
using System;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;

using RabbitMQ.Client;
using RabbitMQ.Util;


namespace RabbitSslTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            p.Start();

            Console.WriteLine();
            Console.WriteLine("Press <ENTER> to exit.");
            Console.ReadLine();
        }


        public void Start()
        {
            try
            {
                // we use the rabbit connection factory, just like normal
                ConnectionFactory cf = new ConnectionFactory();

                // set the hostname and the port
                cf.HostName = "rabbitmq-server";
                cf.Port = AmqpTcpEndpoint.DefaultAmqpSslPort;

                // I've imported my certificate into my certificate store
                // (the Personal/Certificates folder in the certmgr mmc snap-in)
                // Let's open that store right now.
                X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                store.Open(OpenFlags.ReadOnly);

                // and find my certificate by its thumbprint.
                X509Certificate cert = store.Certificates
                    .Find(
                        X509FindType.FindByThumbprint,
                        "6d 36 c0 f9 c2 f8 72 05 d1 01 6c 54 e1 91 bb 2d 66 7b ac 94",
                        true
                    )
                    .OfType<X509Certificate>()
                    .First();

                // now, let's set the connection factory's ssl-specific settings
                // NOTE: it's absolutely required that what you set as Ssl.ServerName be
                //       what's on your rabbitmq server's certificate (its CN - common name)
                cf.Ssl.Certs = new X509CertificateCollection(new X509Certificate[] { cert });
                cf.Ssl.ServerName = "rabbitmq-server";
                cf.Ssl.Enabled = true;

                // we're ready to create an SSL connection to the rabbitmq server
                using (IConnection conn = cf.CreateConnection())
                {
                    using (IModel channel = conn.CreateModel())
                    {
                        channel.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null);
                       
                        // create some basic properties for the message we're going to send
                        IBasicProperties props = channel.CreateBasicProperties();
                        props.ContentEncoding = "utf-8";
                        props.MessageId = Guid.NewGuid().ToString();

                        // publish a hello world message
                        channel.BasicPublish("", "rabbitmq-dotnet-test", props, Encoding.UTF8.GetBytes("Hello, World"));

                        // and go get it
                        BasicGetResult result = channel.BasicGet("rabbitmq-dotnet-test", true);

                        if (null == result)
                        {
                            Console.WriteLine("No message received.");
                        }
                        else
                        {
                            Console.WriteLine("-----==<([ Received ])>==-----");
                            DebugUtil.DumpProperties(result, Console.Out, 0);
                        }

                        channel.QueueDelete("rabbitmq-dotnet-test");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}
 And the output:

Using your certificate to authenticate
If you want to authenticate yourself with your certificate as well, you need to change the code and your rabbitmq server's configuration.

First, update your rabbitmq server's configuration.  If you don't know where to look for that, read this page.  Here's what mine looks like:
  [
  {rabbit, [
     {auth_mechanisms,['EXTERNAL']},
     {ssl_listeners, [5671]},
     {ssl_options, [{cacertfile,"C:/Path/To/Your/cacert.pem"},
                    {certfile,"C:/Path/To/Your/cert.pem"},
                    {keyfile,"C:/Path/To/Your/key.pem"},
                    {verify,verify_peer},
                    {fail_if_no_peer_cert,true}]}
   ]}
].
The thing to note there is the addition of {auth_mechanisms,['EXTERNAL']}.  Don't forget to stop your service, re-install it, and start it after making these changes.
rabbitmq-service.bat stop
rabbitmq-service.bat install
rabbitmq-service.bat start
Next, let's update our client code just a tad by setting the connection factory's auth mechanisms.

// we use the rabbit connection factory, just like normal ConnectionFactory cf = new ConnectionFactory();

// set the hostname and the port
cf.HostName = "rabbitmq-server";
cf.Port = AmqpTcpEndpoint.DefaultAmqpSslPort;
cf.AuthMechanisms = new AuthMechanismFactory[] { new ExternalMechanismFactory() };

// I've imported my certificate into my certificate store
// (the Personal/Certificates folder in the certmgr mmc snap-in)
// Let's open that store right now.
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
The other change I made was to insert a Console.ReadLine() just before all of my using () statements cause everything to close down.  By doing this, I can view the RabbitMQ management web app to ensure that I'm authenticating using my certificate.


1 comment:

  1. Great article John. The only other thing I had to do was execute rabbitmq-plugins enable rabbitmq_auth_mechanism_ssl and restart the server. Otherwise, client's would fail to authenticate because rabbitmq wasn't providing the ssl option as an authentication mechanism.

    ReplyDelete