firewool 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|