firewool 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +95 -0
- data/Rakefile +13 -0
- data/firewool.gemspec +24 -0
- data/lib/firewool.rb +23 -0
- data/lib/firewool/hook.rb +12 -0
- data/lib/firewool/instance_methods.rb +68 -0
- data/lib/firewool/railtie.rb +19 -0
- data/lib/firewool/version.rb +3 -0
- data/test/dummy/.gitignore +4 -0
- data/test/dummy/Gemfile +31 -0
- data/test/dummy/README +3 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/dummy_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/dummy_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +47 -0
- data/test/dummy/config/boot.rb +6 -0
- data/test/dummy/config/database.yml +22 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +26 -0
- data/test/dummy/config/environments/production.rb +49 -0
- data/test/dummy/config/environments/test.rb +35 -0
- data/test/dummy/config/firewool.yml +17 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/db/seeds.rb +7 -0
- data/test/dummy/doc/README_FOR_APP +2 -0
- data/test/dummy/lib/tasks/.gitkeep +0 -0
- data/test/dummy/public/403.html +26 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/images/rails.png +0 -0
- data/test/dummy/public/index.html +239 -0
- data/test/dummy/public/javascripts/application.js +2 -0
- data/test/dummy/public/javascripts/controls.js +965 -0
- data/test/dummy/public/javascripts/dragdrop.js +974 -0
- data/test/dummy/public/javascripts/effects.js +1123 -0
- data/test/dummy/public/javascripts/prototype.js +6001 -0
- data/test/dummy/public/javascripts/rails.js +191 -0
- data/test/dummy/public/robots.txt +5 -0
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/test/functional/dummy_controller_test.rb +8 -0
- data/test/dummy/test/performance/browsing_test.rb +9 -0
- data/test/dummy/test/test_helper.rb +13 -0
- data/test/dummy/test/unit/helpers/dummy_helper_test.rb +4 -0
- data/test/dummy/vendor/plugins/.gitkeep +0 -0
- data/test/firewool_test.rb +59 -0
- data/test/test_helper.rb +7 -0
- data/watchr.rb +73 -0
- metadata +189 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
== Firewool
|
2
|
+
Firewool is an IP firewall for rails. You set what IPs to block and what IPs to allow. Specifics below.
|
3
|
+
|
4
|
+
== Why would I need this?
|
5
|
+
- A layer 7 firewall is too expensive.
|
6
|
+
- Anonymous authentication doesn't equate to all access authorization.
|
7
|
+
- Belt and suspenders style double security check.
|
8
|
+
|
9
|
+
== Install
|
10
|
+
gem install firewool
|
11
|
+
|
12
|
+
- Tested on rails 3.0.4.
|
13
|
+
- Untested on rails 2.x. Probably won't work because no engines.
|
14
|
+
|
15
|
+
== Configuration
|
16
|
+
Add firewool and dependency to Gemfile:
|
17
|
+
gem 'firewool'
|
18
|
+
gem 'ipaddress'
|
19
|
+
|
20
|
+
Create a configuration file in config/firewool.yml
|
21
|
+
|
22
|
+
# config/firewool.yml
|
23
|
+
# changing any valves requires app server restart (apache/webrick/etc)
|
24
|
+
|
25
|
+
development:
|
26
|
+
ip_restriction: true
|
27
|
+
allow: [ 192.168.0.0/16 ]
|
28
|
+
|
29
|
+
test:
|
30
|
+
ip_restriction: false
|
31
|
+
|
32
|
+
production:
|
33
|
+
ip_restriction: true
|
34
|
+
allow: [ 1.1.0.0/16, 1.2.0.0/16, 1.3.0.0/16 ]
|
35
|
+
deny: [ 10.50.0.0/16 ]
|
36
|
+
|
37
|
+
|
38
|
+
Add these lines to your controller you want to protect:
|
39
|
+
|
40
|
+
class DummyController < ApplicationController
|
41
|
+
include Firewool
|
42
|
+
acts_as_firewalled
|
43
|
+
before_filter :ip_filter
|
44
|
+
|
45
|
+
|
46
|
+
Optionally, you can just filter certain actions like any filter:
|
47
|
+
before_filter :ip_filter, :only => [:admin, :secret]
|
48
|
+
|
49
|
+
== About
|
50
|
+
Firewool has an implicit deny by default. This means that Firewool does the following evalation:
|
51
|
+
Deny first
|
52
|
+
Allow all in allow list
|
53
|
+
Deny all in deny list
|
54
|
+
|
55
|
+
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:
|
56
|
+
allow: [ 0.0.0.0 ]
|
57
|
+
deny: [ whatever ]
|
58
|
+
|
59
|
+
So then firewool will do allow -> deny.
|
60
|
+
|
61
|
+
IPs can be spoofed so in the case of strong security, you'll want to use this with one or more factor authentication.
|
62
|
+
|
63
|
+
== Quick Network Primer
|
64
|
+
So how do I write the rules when I'm not a network guy? No problem, let's go through some examples.
|
65
|
+
|
66
|
+
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:
|
67
|
+
10.0.0.0/24 (matches 10.0.0.*)
|
68
|
+
10.0.0.1 (match)
|
69
|
+
10.0.0.204 (match)
|
70
|
+
10.0.1.1 (no match)
|
71
|
+
7.8.9.10 (no match)
|
72
|
+
|
73
|
+
If we just want to match one IP we can use the /32 or just specify the IP by itself.
|
74
|
+
192.168.0.1/32 (matches only 192.168.0.1)
|
75
|
+
192.168.0.1 (matches only 192.168.0.1, same meaning as /32)
|
76
|
+
5.0.0.0/8 (matches 5.*.*.*)
|
77
|
+
5.6.0.0/16 (matches 5.6.*.*)
|
78
|
+
5.6.0.0/24 (matches 5.6.0.*)
|
79
|
+
5.6.7.0/24 (matches 5.6.7.*)
|
80
|
+
|
81
|
+
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 58.14.0.0. This would be our firewool.yml config:
|
82
|
+
production:
|
83
|
+
ip_restriction: true
|
84
|
+
allow: [ 5.6.7.0/24 ]
|
85
|
+
deny: [ 58.14.0.0/16 ]
|
86
|
+
|
87
|
+
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 5.6.7.200 then we'd want to tighten up our rule a little bit and not allow all of the 5.6.7.0 network in. Maybe we research what our IP block really is, or add only the IPs we know about as /32 IPs.
|
88
|
+
|
89
|
+
As a special case, 0.0.0.0 means *.*.*.*, or all IPs.
|
90
|
+
|
91
|
+
== Pretty up
|
92
|
+
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}[https://github.com/squarism/firewool/blob/master/test/dummy/public/403.html].
|
93
|
+
|
94
|
+
== Thanks to
|
95
|
+
Bluemonk for his awesome ipaddress gem.
|
data/Rakefile
ADDED
data/firewool.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "firewool/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "firewool"
|
7
|
+
s.version = Firewool::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Chris Dillon"]
|
10
|
+
s.email = ["squarism@gmail.com"]
|
11
|
+
s.homepage = "http://github.com/squarism/firewool"
|
12
|
+
s.summary = %q{Firewalling gem for rails. Baa.}
|
13
|
+
s.description = %q{Provides ip filtering based on a black/white list.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "firewool"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency('ipaddress', '>= 0.7.0')
|
23
|
+
s.add_development_dependency('shoulda')
|
24
|
+
end
|
data/lib/firewool.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
require 'ipaddress'
|
3
|
+
|
4
|
+
# rails engine setup
|
5
|
+
require File.join(File.dirname(__FILE__), "firewool/railtie.rb")
|
6
|
+
|
7
|
+
module Firewool
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(Firewool::Hook)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Config
|
14
|
+
attr_reader :yaml_config
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@yaml_config = YAML.load_file("#{Rails.root.to_s}/config/firewool.yml")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
autoload :Hook, File.join(File.dirname(__FILE__), "firewool/hook")
|
22
|
+
autoload :InstanceMethods, File.join(File.dirname(__FILE__), "firewool/instance_methods")
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Firewool
|
2
|
+
module Hook
|
3
|
+
def acts_as_firewalled
|
4
|
+
@firewool_config = Firewool::Config.new
|
5
|
+
include Firewool::InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
def firewool_config
|
9
|
+
@firewool_config.yaml_config || self.superclass.instance_variable_get('@firewool_config').yaml_config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Firewool
|
2
|
+
module InstanceMethods
|
3
|
+
# TODO: opinionated. provide instructions on how to forget about this filter
|
4
|
+
# and redirect to their own thing. but this should redirect to the 403.html in public
|
5
|
+
def ip_filter
|
6
|
+
# if no allowed ranges match, then deny
|
7
|
+
if !ip_allow?(request.remote_ip)
|
8
|
+
if File.exists? "#{::Rails.root.to_s}/public/403.html"
|
9
|
+
render :file => "#{::Rails.root.to_s}/public/403.html", :layout => false, :status => 403
|
10
|
+
else
|
11
|
+
render :text => "Public Access Denied.", :status => 403
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def ip_allow?(ip)
|
17
|
+
firewool_config = self.class.firewool_config[Rails.env]
|
18
|
+
|
19
|
+
if firewool_config['ip_restriction']
|
20
|
+
# get our policy from the conf file
|
21
|
+
allowed_ranges = firewool_config['allow']
|
22
|
+
denied_ranges = firewool_config['deny']
|
23
|
+
|
24
|
+
# default allow check
|
25
|
+
if allowed_ranges.include?("0.0.0.0")
|
26
|
+
# default_allow done with access_decision true first
|
27
|
+
# allow -> deny
|
28
|
+
access_decision = true
|
29
|
+
else
|
30
|
+
# without default_allow is access_decision is false by default
|
31
|
+
# deny -> allow -> deny
|
32
|
+
access_decision = false
|
33
|
+
end
|
34
|
+
|
35
|
+
client_ip = IPAddress::parse ip
|
36
|
+
|
37
|
+
# apply allow rules
|
38
|
+
if !allowed_ranges.nil?
|
39
|
+
if in_range?(allowed_ranges, client_ip)
|
40
|
+
access_decision = true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# apply deny rules
|
45
|
+
if !denied_ranges.nil?
|
46
|
+
if in_range?(denied_ranges, client_ip)
|
47
|
+
access_decision = false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# return our shizz
|
52
|
+
access_decision
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#-----------------------------------------------------------------------------------------------
|
57
|
+
private
|
58
|
+
def in_range?(range, ip)
|
59
|
+
range.each do |r|
|
60
|
+
range_ip = IPAddress::parse r
|
61
|
+
if range_ip.include? ip
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails'
|
2
|
+
require 'firewool'
|
3
|
+
|
4
|
+
begin
|
5
|
+
module Firewool
|
6
|
+
|
7
|
+
class Railtie < Rails::Railtie
|
8
|
+
# nothing here right now
|
9
|
+
config.to_prepare do
|
10
|
+
# p "hook added"
|
11
|
+
#ApplicationController.send(:extend, Firewool::Hook)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
rescue
|
17
|
+
p $!, $!.message
|
18
|
+
raise $!
|
19
|
+
end
|
data/test/dummy/Gemfile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
source 'http://rubygems.org'
|
2
|
+
|
3
|
+
gem 'rails', '~>3.0.4'
|
4
|
+
|
5
|
+
# Bundle edge Rails instead:
|
6
|
+
# gem 'rails', :git => 'git://github.com/rails/rails.git'
|
7
|
+
|
8
|
+
# gem 'sqlite3'
|
9
|
+
|
10
|
+
# Use unicorn as the web server
|
11
|
+
# gem 'unicorn'
|
12
|
+
|
13
|
+
# Deploy with Capistrano
|
14
|
+
# gem 'capistrano'
|
15
|
+
|
16
|
+
# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+)
|
17
|
+
# gem 'ruby-debug'
|
18
|
+
# gem 'ruby-debug19'
|
19
|
+
|
20
|
+
# Bundle the extra gems:
|
21
|
+
# gem 'bj'
|
22
|
+
# gem 'nokogiri'
|
23
|
+
# gem 'sqlite3-ruby', :require => 'sqlite3'
|
24
|
+
# gem 'aws-s3', :require => 'aws/s3'
|
25
|
+
|
26
|
+
# Bundle gems for the local environment. Make sure to
|
27
|
+
# put test-only gems in this group so their generators
|
28
|
+
# and rake tasks are available in development mode:
|
29
|
+
# group :development, :test do
|
30
|
+
# gem 'webrat'
|
31
|
+
# end
|
data/test/dummy/README
ADDED
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
|
+
|
4
|
+
require File.expand_path('../config/application', __FILE__)
|
5
|
+
require 'rake'
|
6
|
+
|
7
|
+
Dummy::Application.load_tasks
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
# get rid of activerecord, we don't need it to test
|
4
|
+
#require 'rails/all'
|
5
|
+
require "action_controller/railtie"
|
6
|
+
require "action_mailer/railtie"
|
7
|
+
require "active_resource/railtie"
|
8
|
+
require "rails/test_unit/railtie"
|
9
|
+
|
10
|
+
# If you have a Gemfile, require the gems listed there, including any gems
|
11
|
+
# you've limited to :test, :development, or :production.
|
12
|
+
Bundler.require(:default, Rails.env) if defined?(Bundler)
|
13
|
+
|
14
|
+
module Dummy
|
15
|
+
class Application < Rails::Application
|
16
|
+
# Settings in config/environments/* take precedence over those specified here.
|
17
|
+
# Application configuration should go into files in config/initializers
|
18
|
+
# -- all .rb files in that directory are automatically loaded.
|
19
|
+
|
20
|
+
# Custom directories with classes and modules you want to be autoloadable.
|
21
|
+
# config.autoload_paths += %W(#{config.root}/extras)
|
22
|
+
|
23
|
+
# Only load the plugins named here, in the order given (default is alphabetical).
|
24
|
+
# :all can be used as a placeholder for all plugins not explicitly named.
|
25
|
+
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
|
26
|
+
|
27
|
+
# Activate observers that should always be running.
|
28
|
+
# config.active_record.observers = :cacher, :garbage_collector, :forum_observer
|
29
|
+
|
30
|
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
31
|
+
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
32
|
+
# config.time_zone = 'Central Time (US & Canada)'
|
33
|
+
|
34
|
+
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
35
|
+
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
36
|
+
# config.i18n.default_locale = :de
|
37
|
+
|
38
|
+
# JavaScript files you want as :defaults (application.js is always included).
|
39
|
+
# config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
|
40
|
+
|
41
|
+
# Configure the default encoding used in templates for Ruby 1.9.
|
42
|
+
config.encoding = "utf-8"
|
43
|
+
|
44
|
+
# Configure sensitive parameters which will be filtered from the log file.
|
45
|
+
config.filter_parameters += [:password]
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# SQLite version 3.x
|
2
|
+
# gem install sqlite3
|
3
|
+
development:
|
4
|
+
adapter: sqlite3
|
5
|
+
database: db/development.sqlite3
|
6
|
+
pool: 5
|
7
|
+
timeout: 5000
|
8
|
+
|
9
|
+
# Warning: The database defined as "test" will be erased and
|
10
|
+
# re-generated from your development database when you run "rake".
|
11
|
+
# Do not set this db to the same as development or production.
|
12
|
+
test:
|
13
|
+
adapter: sqlite3
|
14
|
+
database: db/test.sqlite3
|
15
|
+
pool: 5
|
16
|
+
timeout: 5000
|
17
|
+
|
18
|
+
production:
|
19
|
+
adapter: sqlite3
|
20
|
+
database: db/production.sqlite3
|
21
|
+
pool: 5
|
22
|
+
timeout: 5000
|