Firewool is an IP firewall for rails. You set what IPs to block and what IPs to allow. Specifics below.
Why would I need this?
A layer 7 firewall is too expensive.
Anonymous authentication doesn't equate to all access authorization.
Belt and suspenders style double security check.
gem install firewool
Tested on rails 3.0.4.
Untested on rails 2.x. Probably won't work because no engines.
Add firewool and dependency to Gemfile:
gem 'firewool' gem 'ipaddress'
Create a configuration file in config/firewool.yml
# config/firewool.yml # changing any valves requires app server restart (apache/webrick/etc)
development: ip_restriction: true
allow: [ 192.168.0.0/16 ]
test: ip_restriction: false production: ip_restriction: true allow: [ 22.214.171.124/16, 126.96.36.199/16, 188.8.131.52/16 ] deny: [ 10.50.0.0/16 ]
Add these lines to your controller you want to protect:
class DummyController < ApplicationController include Firewool acts_as_firewalled before_filter :ip_filter
Optionally, you can just filter certain actions like any filter:
before_filter :ip_filter, :only => [:admin, :secret]
Firewool has an implicit deny by default. This means that Firewool does the following evalation:
Deny first Allow all in allow list Deny all in deny list
This allows you to have security by default, a whitelist and then exceptions to that whitelist. However, sometimes you want a default allow and only exceptions to that rule. In that case, use an allow with 0.0.0.0 like this:
allow: [ 0.0.0.0 ] deny: [ whatever ]
So then firewool will do allow -> deny.
IPs can be spoofed so in the case of strong security, you'll want to use this with one or more factor authentication.
Quick Network Primer
So how do I write the rules when I'm not a network guy? No problem, let's go through some examples.
First, the IP is four numbers separated by periods. Each number is called an ocet. The slash number (like /16 up above) is how many octets match. So to match every usable IP from 10.0.0.1 to 10.0.0.254, we can just say:
10.0.0.0/24 (matches 10.0.0.*) 10.0.0.1 (match) 10.0.0.204 (match) 10.0.1.1 (no match) 184.108.40.206 (no match)
If we just want to match one IP we can use the /32 or just specify the IP by itself.
192.168.0.1/32 (matches only 192.168.0.1) 192.168.0.1 (matches only 192.168.0.1, same meaning as /32) 220.127.116.11/8 (matches 5.*.*.*) 18.104.22.168/16 (matches 5.6.*.*) 22.214.171.124/24 (matches 5.6.0.*) 126.96.36.199/24 (matches 5.6.7.*)
These are the simplest examples of this notation (called CIDR if you want to read more) but it's enough to build a few use cases. Let's say we want to allow anyone from our company network but block anyone coming from Evil Hackers' Inc. Our company's external network is 5.6.7.* (ie: what users see when they go to whatismyip.com from inside their network) and let's say that Evil Hackers' proxy is 188.8.131.52. This would be our firewool.yml config:
production: ip_restriction: true allow: [ 184.108.40.206/24 ] deny: [ 220.127.116.11/16 ]
Now we'd want to be careful that 5.6.7.* was really where our users are coming from. If people that we want to keep out are coming from 18.104.22.168 then we'd want to tighten up our rule a little bit and not allow all of the 22.214.171.124 network in. Maybe we research what our IP block really is, or add only the IPs we know about as /32 IPs.
As a special case, 0.0.0.0 means ..., or all IPs.
If 403.html doesn't exist in your public directory, then a blocked user will simply see “Public Access Denied.” which isn't that great. Create a 403.html file in public, you can use this 403.html template as an example.
Bluemonk for his awesome ipaddress gem.