Skip to content
mattmatt edited this page Sep 12, 2010 · 5 revisions

Security in the Nanite Cluster

There’s a couple of ways to ensure that communication in your cluster is secured. You can encrypt messages and you can affect routing of messages and agents registering in the cluster.

Authentication Requests and Registrations

Mappers come with a simple mechanism that lets you verify registrations and requests for validity. It’s a a stateless class that has two methods:

class SecurityCheck
  def authorize_registration(registration)
  end

  def authorize_request(request)
  end
end

The first, authorize_registration is called whenever it receives a registration request from an agent. It’s sort of an additional layer of access control. You can (and probably should, depending on how trustworthy agents in your cluster are) solve access control on two levels:

  • Create user and permissions for each agent in your message broker, e.g. for RabbitMQ using rabbitmqctl.
  • Check a registration. When an agent registers, the cluster calls authorize_registration on the security module specified. The default implementation will just accept any registration. The argument is a registration packet which contains the agent’s identity, services, tags, and status. It’s up to you to deal with that information. Again, depending on how trustworthy agents in your cluster are, this information should not be fully relied on as it could be modified in code by a third-party. It should not be the only way of ensuring that only valid agents register in your cluster. If the method returns anything that’s not considered false the registration will pass.

To authorize requests and pushes from agents to other agents (or every other client that uses MapperProxy to push requests into the cluster), authorize_request is called with the according request or push object, see packets.rb for all the info on what attributes you have access to. This way you can limit access from agents to other agents and their services. Again, this information can be forged on an agent, manipulating their own identity to get access to something you’re trying to block access to. If you need a fully reliable authorization system, it’s best to add an additional layer of protection, e.g. one-time access tokens that are part of the payload.

To specify a SecurityProvider for your mapper, you need to start a mapper manually using Nanite.start_mapper. Before starting it, you need to specify the provider implementation using Nanite::SecurityProvider.register(CustomProvider).

Other protection means

Agents have a —secure option. It’s a neat addition to doing fine-grained access control using RabbitMQ’s ACLs. When set this option will ensure that agents don’t need configuration rights on queues like heartbeat, request and registration. They do still need write access on these queues. Since reducing the rights required to be part of the cluster is a desirable situation, using this option is recommended. It reduces ACLs required by your agents to just their own queue and (if you’re using requests and not just pushes) the mappers queues as well.

Encrypting Messaging Using SSL

Authorizing requests is one thing, but that still doesn’t give you the safety of ensuring that a message is really coming from the agent it says it’s from. Nanite comes with built-in support for just that, using RSA keys for encryption and X.509 certificates for signing any kind of packets. When switching the transport format to ‘secure’, any kind of message will be signed and optionally encrypted, so both agents and mappers need to be switched to that format. Both have a command line option —format where you can just specify ‘secure’. Should you run mappers or agents manually, you can specify the option :format => 'secure' when starting or initializing them.

The process of setting up secure communication on both ends is still a bit tedious, but quite flexible.

Basic Ingredients

You need a pair of keys and a certificate for all identities in your cluster. They don’t need to be different for each member, the serializer just needs to find the right certificate for an identity.

You can generate a pair of keys using either OpenSSL, or just use the built-in classes if you like automating it in code:

rsa_key = Nanite::RsaKeyPair.new
dn = Nanite::DistinguishedName.new(
   'C' => 'US',
  'ST' => 'CA',
  'L'  => 'San Francisco',
  'O'  => 'Nanite',
  'OU' => 'The Ants',
  'CN' => 'example.com/emailAddress=cluster@example.com'
certificate = Nanite::Certificate.new(rsa_key, dn, dn)

You need to hold on to the certificate and the private key generated (rsa_key). To get the PEM data of both just call the method data on them. That’s all the data you need for later. The certificate and key objects can be restored from them at any time. The key is required on the mapper or agent, and is his personal key he uses to sign and decrypt messages.