Consolidated my Ruby IPAddr extensions.

[ Posted by James Harton Tue, 22 Dec 2009 20:13:02 GMT ]

The project I'm working on requires some pretty fancy twiddling of IP addresses and prefixes, so I've slowly been adding functionality to Ruby's IPAddr class. Here's a couple of new features;

You can download the whole file from gist.github.com

Tags , , , , , , , , , , ,  | no comments

Using Auditable in nested controllers.

[ Posted by James Harton Tue, 08 Dec 2009 22:18:42 GMT ]

After yesterday releasing the initial version of Auditable I have added a few extra features, the most important of which is a Model.auditable? method so you can tell from other parts of the model whether you're dealing with a model that potentially has audit logs.

In my application I will be using a controller called audit_controller.rb which contains only the index method and will be nested below controllers for almost every model that is using Auditable. The first thing you need to do is set up your routes, my application has two controllers called remote_servers_controller and tasks_controller, both of which are auditable. We'll set up nested routes by mapping their resources, however we only want to route the index method because we don't want anyone to be able to change the audit logs. My config/routes.rb file looks like this:

ActionController::Routing::Routes.draw do |map|
  map.resources :tasks do |controller|
    controller.resources :audits, :only => :index
  end
  map.resources :remote_servers do |controller|
    controller.resources :audits, :only => :index
  end
  map.connect ':controller/:action/:id'
  map.connect ':controller/:action/:id.:format'
end

Next we need to create an audits_controller.rb file in app/controllers to handle these requests:

class AuditsController < ApplicationController

  before_filter :find_auditable_parent

  def index
    @audits = @parent.audits.find(:all, :order => 'audits.created_at DESC')
    respond_to do |format|
      format.html # index.html.erb
      format.xml { render :xml => @audits }
    end
  end

  protected

  def find_auditable_parent
    # Use find parent to dynamically figure out
    # what the parent model for this request is
    if self.find_parent
      # Check if that model is auditable.
      if @parent.class.auditable?
        true
      else
        false
      end
    end
  end

end

As you can see we access the parent model's audits method to limit the scope of our find so that we only show audits about that particular model. You'll notice that there is a before_filter applied to this controller calling a method called find_parent_auditable, which is what is used to verify that the @parent variable is auditable. find_parent_auditable also calls self.find_parent which is such a common pattern with nested resources that I wound up writing something generic and including it in my ApplicationController:

def find_parent
  # Dynamically figure out and what the parent
  # model is for a nested resources request.
  params.each do |key,val|
    if key[-3..-1] == '_id'
      # parameter is potentially an FK field.
      potential_model = key[0..-4]
      if !potential_model.empty?
        # only keep processing if the name is not
        # empty.
        begin
          model = Object.const_get potential_model.camelize
          if model.ancestors.member? ActiveRecord::Base
            if val.to_i > 0
              @parent = model.find(val.to_i)
              true
            else
              # no point trying to clone the model if the
              # id doesn't make sense.
              false
            end
          else
            # If the Class 'PotentialModel' is not a subclass of
            # ActiveRecord::Base then return false.
            false
          end
        rescue NameError
          # If there is no Class by the name of 'PotentialModel'
          # then return false.
          false
        end
      end
    end
  end
end

As you can see this function iterates through params looking for variables like remote_server_id, it uses that to attempt to imply the name of the model. It checks the model to see if it has ActiveRecord::Base as an ancestor, in which case it populates @parent with the correct instance of the model and returns true, telling the before_filter that it can allow the request to proceed to the controller.

Now all you need to do is create app/views/audits/index.erb to display your audit logs.

Posted in ,  | Tags , , , ,  | no comments

Auditable: keep audit logs of model changes in Rails

[ Posted by James Harton Tue, 08 Dec 2009 01:45:06 GMT ]

This afternoon I threw together a Rails plugin to keep audit logs of any changes made to your models. It makes use of ActiveRecord's polymorphic relationships to attach an Audit model to every model you specify.

Installing Auditable

From within your Rails application I would suggest using the plugin script to install the latest version of Auditable directly from GitHub and checking for updates regularly.

# ./script/plugin install git://github.com/jamesotron/Auditable.git
Initialized empty Git repository in /Users/jnh/tmp/test/vendor/plugins/Auditable/.git/
remote: Counting objects: 22, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 22 (delta 4), reused 0 (delta 0)
Unpacking objects: 100% (22/22), done.
From git://github.com/jamesotron/Auditable
 * branch            HEAD       -> FETCH_HEAD
* Generating model and migration...
    exists  app/models/
    exists  test/unit/
    exists  test/fixtures/
    create  app/models/audit.rb
    create  test/unit/audit_test.rb
    create  test/fixtures/audits.yml
    create  db/migrate
    create  db/migrate/20091208005219_create_audits.rb
* Making model polymorphic...
* You probably want to run rake db:migrate now.
* Done.

Auditable will generate a model and migration called Audit, which you can safely remove if you don't want to use the default model. If you want to use the default Audit model then run

# rake db:migrate
(in /Users/jnh/tmp/test)
==  CreateAudits: migrating ===================================================
-- create_table(:audits)
   -> 0.0032s
==  CreateAudits: migrated (0.0035s) ==========================================

to create the table in your database. You can always add any additional fields you may want using a migration.

Enabling Auditable on your models

Enabling an audit log on your model is now as simple as adding a single line to the model definition:

class WorrisomeData < ActiveRecord::Base
  acts_as_auditable
end

acts_as_auditable defaults to the most paranoid setting by basically turning on all auditing by default, you can override them with options that make sense to your installation. Options available are:

  • :using => symbol pointing to the polymorphic model to use for storing audit logs. Defaults to :auditable. Only useful if you are defining your own Audit model.
  • :relation => symbol pointing to the relationship mapping for audit longs. Defaults to :audits. Useful if you want to access the audit log by a method other than Model.audits().
  • :when => [] an array containing symbols describing on which actions to write audit logs. Defaults to [ :accessed, :modified, :saved, :created, :deleted ].
  • :identity => lambda block containing any code needed to retrieve the user identifier. Defaults to lambda { "Unknown user" }. You might want to try something like lamba { ApplicationController.session[:current_user].name } if you are using restful_authentication.
  • :for => [] an array of symbols naming the model fields you wish to audit. Defaults to [ :all ] where :all is a special name symbolising all fields. Note you can never audit :id because it will cause an infinite loop, however Auditable will log if :id is changed.
  • :log_field => A symbol naming the text field in the auditor model to store the log information in. Only useful if you need something more than the default Audit model.
  • :identity_field => A symbol naming the string field in the auditor model to store the user's identity information in. Only useful if you need something more than the default Audit model.

Posted in ,  | Tags , , ,  | 1 comment

Design fun with Ponoko

[ Posted by James Harton Sun, 22 Nov 2009 22:17:54 GMT ]

Here's a bit of a belated update. My friends and I have started a prog rock band called "Venus is too close", it's the same guys I did the Passenger cover with and although in it's infancy I am expecting great things (or at least great fun).

I have also been spending time working on a few designs for cutting with Ponoko. The two designs I have had cut so far is a laptop stand designed for my ageing MacBook Pro:
Laptop Stand

and some toy gingerbread men for a friend's two year old son:
gingerbread men
All cut out of 5mm Rimu veneered MDF. I have great plans for Ponoko, but as you, the gentle reader, may have noticed already, I almost never have time to complete my ideas. More soon. Promise.

Tags , , , , , , , , ,  | 4 comments

Passenger - The Wild Ones

[ Posted by James Harton Tue, 20 Oct 2009 23:25:23 GMT ]

Hi all.

I've been very busy lately working on a number of projects, but the one I am most proud of is our entry for the Orcon Iggy Live Competition embedded below:

Passenger, by The Wild Ones. from James Harton on Vimeo.

The song was recorded and mixed in about 5 hours using Cubase. The drum track is synthetic everything else is real instruments. There is two rhythm guitar parts, played by myself and Matthew, a bass part played by me, lead vocals are Justin and backing vocals by Matthew and I. Justin also added a lead guitar part which made the recording feel much more dynamic.

We were able to shoot the video on the same evening, finishing up around midnight. Due to some unforeseen issues with the DV camera we were using I had a great deal of trouble getting the video off the camera, and wound up having to capture it using an old bt848 video capture card, meaning that quality is a lot lower than what I would have liked, but there's not much to be done about it now.

We shot five takes of the video, three from static angles and two hand-held takes. I used the excellent Kdenlive video editor to sync each take with the audio track and edit the cuts between angles. I also make a lot of use of the Pan and Zoom and Rotate and Skew effects to alter each shot so that it looks like there are a lot more camera angles than there really are.

I would call it a success and now Matthew and I are considering working together to record more music. You can become a fan of The Wild Ones on Facebook and we'll keep you updated on what we're working on.

Posted in  | Tags , , , , , , , , , ,  | no comments

acts_as_lockbox for your Rails Models

[ Posted by James Harton Sun, 23 Aug 2009 22:34:32 GMT ]

After yesterday's post about using Lockbox to secure your models, I have implemented an acts_as_lockbox extension to ActiveRecord::Base. Here's a quick tutorial on how to make it work:

Installing Lockbox

From within your Rails application I would suggest using the plugin script to install the latest version of Lockbox directly from GitHub and checking for updates regularly, as this is a piece of security infrastructure you need to be able to rely on.

# ./script/plugin install git://github.com/jamesotron/Lockbox.git

Configuring your public and private keys

In order to securely encrypt secrets we need both a public and private key, and a pass phrase. We will just use the openssl command-line tool to generate these keys, first the private key. Make sure you select a pass phrase which would be non-trivial to guess.

# openssl genrsa -des3 -out private.pem 2048
..................................................................................................................+++
..........+++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:

Next we will extract the public key from the private key so that we can use it:

# openssl rsa -in private.pem -out public.pem -outform PEM -pubout
Enter pass phrase for private.pem:
writing RSA key

Move these files into your Rails application path, (I'd suggest putting them under config/) and make sure that they are only readable from by your rails application.

The next step is to configure your config/lockbox.yml to tell Lockbox where to access the public and private keys, and optionally the pass phrase (I would not recommend this, except for your test environment or if you are absolutely certain that the pass phrase cannot be compromised).

# cat <<EOF > config/lockbox.yml
development:
  public_key_path: config/public.pem
  private_key_path: config/private.pem

testing:
  public_key_path: vendor/plugins/lockbox/test/public.pem
  private_key_path: vendor/plugins/lockbox/test/private.pem
  pass_phrase: test

production:
  public_key_path: config/public.pem
  private_key_path: config/private.pem
EOF

Using the Lockbox API

Lockbox is a generic PK en/decryption system, meaning you can use it anywhere in your Rails app, not just in your models. The Lockbox API is very simple:

  • Lockbox.locked? tests if the Lockbox is still locked (ie: no correct pass phrase has been supplied yet)
  • Lockbox.unlocked? the inverse of the above.
  • Lockbox.try? (pass_phrase) attempt to unlock the Lockbox using the supplied pass phrase. Returns true or false depending on success.
  • Lockbox.lock! abandons access to the public and private keys, stopping your application from being able to retrieve encrypted secrets.
  • Lockbox.encrypt (val) encrypts a string using the public key, if the lockbox is unlocked.
  • Lockbox.decrypt (val) decrypts an encrypted string using the private key, provided the lockbox is unlocked.

Using Lockbox in your Models

Using Lockbox in your models is as simple as using acts_as_lockbox into your model's class. acts_as_lockbox adds the locked?, unlocked?, try? and lock! methods to your model's class and creates accessors for fields supplied in the an array to the :for argument:

class User < ActiveRecord::Base
  acts_as_lockbox :for => [ :password, :credit_card ]
end

In the above example if you try and access the password or credit_card fields without providing Lockbox with the pass phrase (via the Lockbox.try? or User.try? methods) then Lockbox will raise a StillLockedException.

Stopping your application from running until the Lockbox is unlocked

Because you haven't used an easily guessable pass phrase (right?) I don't see any problem with hijacking any web requests using a before_filter on your ApplicationController class:

class ApplicationController < ActionController::Base
  before_filter :check_unlocked

  def check_unlocked
    if Lockbox.locked?
      if !Lockbox.try? params[:pass_phrase]
        render :partial => 'unlock'
     end
  end

end

Alternatively you could redirect requests to a "down for maintenance" page.

Tags , , , , , , , , ,  | no comments

Lockbox: simple attribute encryption for your Rails models

[ Posted by James Harton Sun, 23 Aug 2009 05:23:34 GMT ]

After a short discussion I had at work about storing secrets on a web application I threw together this Rails plugin which handles public key cryptography using RSA key pairs. In my particular use case I wanted to stop the web application from running at all if the passphrase hasn't been provided.

Lockbox is very simple to use, you can test if the Lockbox is locked using Lockbox.locked? or Lockbox.unlocked?, try unlocking the private key using Lockbox.try? 'my passphrase', lock it again using Lockbox.lock! and of course encryption is done using Lockbox.encrypt(str) and Lockbox.decrypt(str).

I plan to add acts_as_lockbox functionality for ActiveRecord models in the future.

Lockbox is available at Github, you can install it with the following:

./script/plugin install git://github.com/jamesotron/Lockbox.git

The README file contains documentation on configuring your keys, etc.

Tags , , , , , , , , ,  | no comments

Calculating EUI-64 addresses.

[ Posted by James Harton Thu, 13 Aug 2009 22:54:42 GMT ]

Another instalment in my series of IPAddr extensions for Ruby, today I show you how to calculate the EUI-64 address for a given network and MAC address:

class IPAddr
  def self.eui_64(network, mac)
    if !network.is_a? IPAddr
      raise ArgumentError, "Network must be an IPAddr type."
    elsif network.ipv4?
      raise ArgumentError, "Network must be an IPv6 network."
    elsif network.length != 64
      raise ArgumentError, "Network must have a 64 bit mask."
    end
    if mac.is_a? Integer
      mac = "%:012x" % mac
    end
    if mac.is_a? String
      mac = mac.split(":").join.downcase
      if mac.match(/^[0-9a-f]{12}/).nil?
        raise ArgumentError, "Second argument must be a valid MAC address."
      end
      e64 = (mac[0..5] + "fffe" + mac[6..11]).to_i(16) ^ 0x0200000000000000
      IPAddr.from_i(network.first.to_i + e64, Socket::AF_INET6)
    end
  end
end

Tags , , , , , , ,  | no comments

More missing pieces for Ruby's IPAddr

[ Posted by James Harton Wed, 12 Aug 2009 23:50:17 GMT ]

WRT yesterday's blog entry, Ruby's IPAddr class is missing a lot of useful methods. Today I'll show you how to add methods to return the network address and the last address (the broadcast address in IPv4 parlance).

class IPAddr

 def length
   # nasty hack, but works well enough.
   @mask_addr.to_s(2).count("1")
 end

 def first
   IPAddr.from_i(@addr & @mask_addr, @family)
 end

 def last
   if @family == Socket::AF_INET
     IPAddr.from_i(first.to_i | (@mask_addr ^ IN4MASK), @family)
   elsif @family == Socket::AF_INET6
     IPAddr.from_i(first.to_i | (@mask_addr ^ IN6MASK), @family)
   else
     raise "unsupported address family."
   end
 end

end

I hope you find these useful.

Tags , , , , , , ,  | no comments

Adding range and from_i support to Ruby's IPAddr class

[ Posted by James Harton Wed, 12 Aug 2009 01:00:38 GMT ]

There are a number of really dumb things wrong with Ruby's IPAddr class, but the two most annoying is no way to convert an integer to an IPv4 or IPv6 address and you can't use IPAddr's in ranges.

I've added a from_i() Class method that clones a new IPAddr instance by converting the integer into the string representation of an IP address. You can optionally also pass in an address family (Socket::AF_INET or Socket::AF_INET6) otherwise it will try and work it out from the size of the integer. I was lucky because I was able to take the bulk of the code from Culvert and convert it to rubyisms.

I've also added the succ() and <=>() methods to IPAddr so that you can use it in ranges, eg:

(IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.1") => false (IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.17") => true (IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.43") => false

I need this because I'm writing some code for work which abstracts DHCP ranges (which don't always match subnet boundaries).

Here's the code:

require 'ipaddr'

class IPAddr include Comparable

 def self.from_i(i = 0, family = Socket::AF_UNSPEC)
   if i < 0
     raise ArgumentError, "Cannot convert negative integer to an IP address."
   elsif family == Socket::AF_INET && i > IN4MASK
     raise ArgumentError, "Cannot convert integer > 0xffffffff to an IPv4 address."
   elsif family == Socket::AF_INET6 && i > IN6MASK
     raise ArgumentError, "Cannot convert integer > 0xffffffffffffffffffffffffffffffff to an IPv6 address."
   elsif i > IN6MASK
     raise ArgumentError, "Cannot convert integer > 0xffffffffffffffffffffffffffffffff to IP address."
   end

   if family == Socket::AF_UNSPEC
     if (0..4) === i.size
       family = Socket::AF_INET
     elsif (5..16) === i.size
       family = Socket::AF_INET6
     end
   end

   if family == Socket::AF_INET
     r = []
     (0..3).each do |byte|
       x = (3 - byte) * 8
       y = (4 - byte) * 8
       r << ((i >> x) - ((i >> y) * 256 )).to_s
     end
     IPAddr.new(r * ".")
   elsif family == Socket::AF_INET6
     r = []
     (0..7).each do |byte|
       x = (7 - byte) * 16
       y = (8 - byte) * 16
       r << ((i >> x) - ((i >> y) * 65536)).to_s(16)
     end
     IPAddr.new(r * ":")
   end
 end

 def succ
   i = to_i + 1
   if ipv4? and (i <= IN4MASK)
     IPAddr.from_i(i)
   elsif ipv6? and (i <= IN6MASK)
     IPAddr.from_i(i)
   end
 end

 def <=>(other)
   to_i <=> other.to_i
 end

end

I hope you find this code useful.

Tags , , , , , , ,  | no comments