Monday, July 13, 2015

Authenticating and authorising users in RabbitMQ

RabbitMQ is a messaging broker, basically an infrastructure providing message queues to which applications can push and pull data messages. Such time asynchrony provides a decoupling between data producers and consumers, as well as interoperability between clients running on different machines and technologies. In particular RabbitMQ implements the Advanced Message Queuing Protocol (AMQP), a lightweight application-level binary protocol based on TCP [1]. AMQP together with the MQTT, XMPP and CoAP, is emerging as the leading machine-to-machine application protocol for internet-of-things applications [2]. An important aspect of RabbitMQ is also the reliability of its message queues (e.g. persistence of messages, retransmission, etc.) as well as its scalability over clusters of computers [3]. Its plugin mechanism also allows for connecting the broker to others written in different protocols, such as MQTT. A relevant issue in M2M application protocols is, however, authentication and authorisation management. This is mainly due to the fact that those protocols originated from the WSN community, where the network was normally managed by the same corporate or individual. 

A basic way is to add users and define their privileges using the rabbitmqctl command line interface, or the rabbitmq http API [4]. Accordingly, access control is provided by RabbitMQ using an internal database, which is initialized to a guest user, entitled to login only from localhost. 

The main alternative we discuss here is the use of an external authentication server, based on a RESTful interface. In particular, we use the rabbitmq_auth_backend_http plugin available at [5]. 
As also shown in [6], the plugin can be simply downloaded with a WGET from the community plugins [7], placed directly in the folder rabbitmq/plugins, and enabled with ./sbin/rabbitmq-plugins enable rabbitmq_auth_backend_http. The next step is to configure the plugin to connect to our authentication server. To this end we need to modify the ./etc/rabbitmq.config file which should look like this:

[
  {rabbit, [{auth_backends, [rabbit_auth_backend_http]}]},
  {rabbitmq_auth_backend_http,
   [{user_path,     "http://www.domainname/rmq_auth.php"},
    {vhost_path,    "http://www.domainname/rmq_auth.php"},
    {resource_path, "http://www.domainname/rmq_auth.php"}]}
].

with the first line enabling the auth_http backend and the second one properly configuring the goal server. As shown in [5], the tool will mainly perform 3 calls, user_path to authenticate a user, vhost_path to grant access to a certain vhost and resource_path to grant access to a specific resource, such as a queue or an exchange. We provide below a very simple script, granting access to the default '/' vhost to all users registered in our MySQL db. In particular, we avoid sending username and password in favour of a hashed token (authentication_key). For simplicity, we omit here the connection detail and the "SELECT * from user WHERE authentication_key = %s" query implemented in the getUser function.

<?php

// Authentication script for RabbitMQ
include("connect.php");

 if(isset($_GET['username']) ){ 

$authkey = $con->escape($_GET["username"]);

// first of all check if the user exists
$user = $con->getUser($authkey);

  if($user == -1) {
echo "deny";
  }else{
  // go ahead with checking
  if( isset($_GET['password']) ){
  // AUTHENTICATION mechanism
  // having the authkey is enough, allow the user
  // USER
  echo "allow management";
 
  }else if( isset($_GET['name']) ){ 
  // AUTHORIZATION mechanism
  // both vhost and resource are set (RESOURCE)
  // a certain user can only access it's specific resource
$resource_name = $con->escape($_GET["name"]);

$valid_names = array($user, $user."_device_control", $user."_device_status");

// a user can only access 2 specific queues
if( in_array($resource_name, $valid_names) ){
echo "allow";
} else{
echo "deny";
}

//file_put_contents('filename.php', $resource_name);
 
  }else if( isset($_GET['vhost']) ){ 
  // only vhost is set (VHOST)
  echo "allow";
// for simplicity we only have '/' as unique host
 
  }else{
  echo "deny";
  }
 
  }

 }else{
  echo "deny"; // deny access to the user
 }

?>

The example Python code shown on the RabbitMQ site would now look for the sender like:

#!/usr/bin/env python
import pika

credentials = pika.PlainCredentials('authenticationkey-of-administrator', 'test')
parameters = pika.ConnectionParameters(credentials=credentials, host="143.205.116.250", virtual_host='/')
connection = pika.BlockingConnection(parameters)

channel = connection.channel()

channel.exchange_declare(exchange='Administrator', type='direct')
#channel.queue_declare(queue='Administrator_device_status')

channel.basic_publish(exchange='Administrator',
                      routing_key='device_status',
                      body='Hello World!')
print " [x] Sent 'Hello World!'"


connection.close()

and for the receiver:

#!/usr/bin/env python
import pika
from pika.exceptions import ProbableAccessDeniedError, ProbableAuthenticationError


def callback(ch, method, properties, body):
    print " [x] Received %r" % (body,)

credentials = pika.PlainCredentials('authenticationkey-of-administrator', '')
parameters = pika.ConnectionParameters(credentials=credentials, host="143.205.116.250", virtual_host='/')
connection = pika.BlockingConnection(parameters)

channel = connection.channel()
channel.exchange_declare(exchange='Administrator', type='direct')

queue_name = 'Administrator_device_status'
result = channel.queue_declare(queue=queue_name)
channel.queue_bind(exchange='Administrator', queue=queue_name, routing_key='device_status')


channel.basic_consume(callback,
                      queue=queue_name,
                      no_ack=True)
try:
print ' [*] Waiting for messages. To exit press CTRL+C'
channel.start_consuming()
except:

print 'Terminate'


Which basically creates an exchange called Administrator, with a queue named Administrator_device_status accessible solely by the administrator user. Such resource is therefore replicated for each user. For instance, this could allow users to remotely control their IoT devices.


Links:

  1. https://en.wikipedia.org/wiki/Advanced_Message_Queuing_Protocol
  2. http://postscapes.com/internet-of-things-protocols
  3. http://www.rabbitmq.com/features.html
  4. http://hg.rabbitmq.com/rabbitmq-management/raw-file/3646dee55e02/priv/www-api/help.html
  5. https://github.com/rabbitmq/rabbitmq-auth-backend-http
  6. http://correlia.blogspot.co.at/2012/07/rabbitmq-with-http-backend-plugin.html
  7. https://www.rabbitmq.com/community-plugins.html

No comments:

Post a Comment