Skip to content

monorkin/rabbitmq_http_auth_backend

Repository files navigation

RabbitMQHttpAuthBackend

Mountable Rack application that implements a configurable API for RabbitMQ's rabbitmq-auth-backend-http.

Gem Version

Purpose

RabbitMQ comes bundled with the rabbitmq-auth-backend-http plug-in. The purpose of this plug-in is to authorize each action of an user connected to RabbitMQ by asking a server over HTTP if the user is allowed to do that action.

The plugin expects the server to implement four endpoints - one for login (/user), one for vhost access, one for general resources (exchanges, queues, topics) and one for topics.

Each endpoint has to respond with a custom format to either allow or deny the action.

This library implements all of this as a mountable Rack application. Meaning, after minimal configuration your application can implement the four required endpoints and respond with correctly formated responses.

Index

  1. Usage
    1. Mounting the endpoint
    2. Configuration
    3. Resolvers
    4. Versioning
    5. Default configuration
  2. Installation
  3. FAQ
  4. Change log
  5. Development
  6. Contributing
  7. License

Usage

  1. Mounting the endpoint
  2. Configuration
  3. Resolvers
  4. Versioning
  5. Default configuration

Mounting the endpoint

To use RabbitMQHttpAuthBackend, you will have to mount it within your Rack application. This will expose it's endpoints from your application, name spaced with by any prefix of your choosing.

The following are examples for some popular Rack based frameworks. Note that /rabbitmq/auth is only a prefix and can be changed to whatever you desire.

For Rails applications, add the following line to your routes.rb file:

# /config/routes.rb
Rails.application.routes.draw do
  mount RabbitMQHttpAuthBackend.app => '/rabbitmq/auth', as: 'rmq_auth_api'
end

For Sinatra applications, add the following to config.ru:

# config.ru
map '/rabbitmq/auth' do
  run RabbitMQHttpAuthBackend.app
end

For Hanami applications, add the following to config/environment.rb:

Hanami.configure do
  mount RabbitMQHttpAuthBackend.app, at: '/rabbitmq/auth'
end

For Roda applications, you have to call run from within your routing tree:

class MyApp < Roda
  route do |r|
    r.on '/rabbitmq/auth' do
      r.run RabbitMQHttpAuthBackend.app
    end
  end
end

Configuration

RabbitMQHttpAuthBackend can be configured to suite your needs. Both the HTTP method as well as the names of all the endpoints are configurable in the following manner.

# /config/initializers/rabbitmq_http_auth_backend.rb
RabbitMQHttpAuthBackend.configure! do
  http_method :post

  user do
    path '/anvandare'
  end

  vhost do
    path '/vhost'
  end

  resource do
    path '/resurs'
  end

  topic do
    path '/amne'
  end
end

Resolvers

Resolvers are used to determine whether or not a user is allowed access to a particular resource. Resolvers are passed as part of the configuration. They can be any callable object - any object that responds to a call method that takes one argument (the params, a hash containing RabbitMQ query information).

The return value of the resolver can be either :allow or :deny. If additional tags need to be returned alongside :allow return an Array containing :allow and an Array of tags - e.g. [:allow, ['admin']].

# /config/initializers/rabbitmq_http_auth_backend.rb
RabbitMQHttpAuthBackend.configure! do
  http_method :post

  user do
    path '/anvandare'
    resolver(lambda do |params|
      if params['username'] == 'admin'
        return :allow, [:admin, :moderator]
      end

      :deny
    end)
  end

  topic do
    resolver TopicsResolver
  end
end

class TopicsResolver
  def self.call(params)
    if params['username'] == 'admin'
      return :allow
    end

    if params['permission'] == 'read' && params['name'] == 'messages'
      return :allow
    end

    :deny
  end
end

Most commonly used methods related to resolvers are extracted to the BasicResolver class. Any class inheriting from it becomes a callable object on the class and instance level. The user is expected to implement the #call method.

The following methods are available within a class inheriting from BasicResolver:

Method Description
username Returns the user's username
password Returns the user's password
name Returns the name of the resource
queue? Returns true if the queried resource is a queue
exchange? Returns true if the queried resource is an exchange
topic? Returns true if the queried resource is a topic
resource Returns the resource type (as a String, e.g. 'exchange')
read? Returns true if the queried permission is read
write? Returns true if the queried permission is write
configure? Returns true if the queried permission is write
permission Returns the requested permission (as a String, e.g. 'read')
routing_key Returns the queried routing key
vhost Returns the queried vhost
ip Returns the IP address of the client querying

The following is the same as the TopicsResolver from the previous example, but rewritten using BasicResolver:

class TopicsResolver < RabbitMQHttpAuthBackend::BasicResolver
  def call
    return :allow if username == 'admin'
    return :allow if name == 'messages' && read?
    :deny
  end
end

# This makes `TopicsResolver` a callable object on the class level
# > TopicsResolver.call(params)
# And it's callable on the instance level
# > TopicsResolver.new(params).call

A "native" configuration DSL is also provided. The DSL provides the same utility methods as BasicResolver as well as allow!, deny!, tags, allowed? and denised? which can be used to set the result or to query it - note that they don't stop execution!

# /config/initializers/rabbitmq_http_auth_backend.rb
RabbitMQHttpAuthBackend.configure! do
  http_method :post

  topic do
    resolver do
      if username == 'admin'
        allow! ['admin', 'manager']
      else
        deny!
      end
    end
  end
end

Not all methods return usable values for all resources. Here's a list:

  • user
    • username
    • password
  • vhost
    • username
    • vhost
    • ip
  • resource
    • username
    • vhost
    • resource (can return :exchange, :queue or :topic)
    • name
    • permission (can return :configure, :read or :write)
  • topic
    • username
    • vhost
    • resource (can return :topic)
    • name
    • permission (can return :configure, :read or :write)
    • routing_key (of the published message if the permission is :write, else of the queue binding)

Versioning

Everybody makes mistakes and changes their minds. Therefore this library enables you to create multiple versions of itself and mount them.

There is little difference to the regular usage.

Mounting:

# /config/routes.rb
Rails.application.routes.draw do
  mount RabbitMQHttpAuthBackend.app(:v1) => '/rabbitmq/auth'
  #                                ^^^^^
end

Configuration:

# /config/initializers/rabbitmq_http_auth_backend.rb
RabbitMQHttpAuthBackend.configure!(:v1) do
  #                               ^^^^^
  # ...
end

If no version is given the :default or global configuration is edited.

Default configuration

The global default configuration can be changed by altering the configuration for the :default version.

Here is the full default configuration

# /config/initializers/rabbitmq_http_auth_backend.rb
RabbitMQHttpAuthBackend.configure!(:default) do
  http_method :get

  user do
    path '/user'
    resolver do
      deny!
    end
  end

  vhost do
    path '/vhost'
    resolver do
      deny!
    end
  end

  resource do
    path '/resource'
    resolver do
      deny!
    end
  end

  topic do
    path '/topic'
    resolver do
      deny!
    end
  end
end

Installation

Add this line to your application's Gemfile:

# Gemfile
gem 'rabbitmq_http_auth_backend'

Configure the library:

# /config/initializers/rabbitmq_http_auth_backend.rb
RabbitMQHttpAuthBackend.configure!(:v1) do
  http_method :get

  user do
    path '/user'
    resolver do
      deny!
    end
  end

  vhost do
    path '/vhost'
    resolver do
      deny!
    end
  end

  resource do
    path '/resource'
    resolver do
      deny!
    end
  end

  topic do
    path '/topic'
    resolver do
      deny!
    end
  end
end

Mount the application:

# /config/routes.rb
Rails.application.routes.draw do
  mount RabbitMQHttpAuthBackend.app => '/rabbitmq/auth', as: 'rmq_auth_api'
end

You are done!

bash-4.4$ curl localhost:3000/rabbitmq/auth/user && echo
deny

FAQ

You use a version in your installation example. Do I have to use a version?

You don't have to, but I would advise you do. Editing the default configuration might cause you problems in the future when you would like to have a clean slate.

Why does the "native" DSL exist?

To provide a simple configuration language to accomplish basic tasks. I would advise you to use `BasicResolver` or a custom resolver callable object for anything other than the most basic use case e.g. check a username or IP.

Can my path be nested? E.g. `/foo/bar/baz/cux`?

Yes they can.

Can my path contain arguments? E.g. `/foo/:name/bar`?

At the moment, no.

Does this library handle caching for me?

No. This feature was removed from the original implementation of this library.

Caching is a tricky topic. It's hard to get right. I couldn't find an expressive enough interface for handling cache invalidation that would satisfy my needs or be flexible enough to accommodate the use cases I think are common. Therefore I decided against implementing caching within this library.

I recommend that you implement your custom caching and invalidation logic in a custom resolver.

Also, use the rabbitmq-auth-backend-cache plugin. It provides time based client-side caching and comes standard with RabbitMQ 3.7+

Here is an example of a fully configured HTTP auth backend plugin in conjunction with the caching plugin:

    auth_backends.1 = internal
    auth_backends.2 = cache
    auth_cache.cached_backend = http
    auth_http.http_method = get
    auth_http.user_path = http://localhost:3000/rabbitmq/auth/user
    auth_http.vhost_path = http://localhost:3000/rabbitmq/auth/vhost
    auth_http.resource_path = http://localhost:3000/rabbitmq/auth/resource
    auth_http.topic_path = http://localhost:3000/rabbitmq/auth/topic
  
How do you use Rabbit's HTTP backend?

Follow the installation instructions in this guide to setup your Rack (Rails/Roda/Sinatra/...) application as an HTTP auth backend. Then add the following to your `rabbitmq.conf`, located within `/etc/rabbitmq` (if it's not there, create it).

    auth_backends.1 = internal
    auth_backends.2 = http
    auth_http.http_method = get
    auth_http.user_path = http://localhost:3000/rabbitmq/auth/user
    auth_http.vhost_path = http://localhost:3000/rabbitmq/auth/vhost
    auth_http.resource_path = http://localhost:3000/rabbitmq/auth/resource
    auth_http.topic_path = http://localhost:3000/rabbitmq/auth/topic
  

Assuming that your application and RabbitMQ instance are on the same machine, and that your application is exposed on port 3000 everything should just work™️.

If it doesn't work try restarting RabbitMQ and your application.

If your application and RabbitMQ instance aren't on the same machine, make sure that the RabbitMQ instance can access your application, the easiest way to do this is to connect to the RabbitMQ server and using `ping ` or `curl :`. Remember to change the `localhost:3000` in `rabbitmq.conf` to your application's URL or IP.

Change log

All changes between versions are logged to the change log available in the CHANGELOG.md file.

This project follows the semantic versioning schema.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/monorkin/rabbitmq_http_auth_backend/.

License

This software is licensed under the MIT license. A copy of the license can be found in the LICENSE.txt file included with this software.

TL;DR this software comes with absolutely no warranty of any kind. You are free to redistribute and modify the software as long as the original copyright notice is present in your derivative work.

About

Mountable Rack application that implements a configurable API for RabbitMQ's rabbitmq-auth-backend-http.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages