alpaca 0.9.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/README.md +59 -0
- data/Rakefile +9 -0
- data/lib/alpaca.rb +54 -0
- data/lib/rack/alpaca/version.rb +5 -0
- data/lib/rails/generators/alpaca/install/install_generator.rb +13 -0
- data/spec/rack_alpaca_spec.rb +67 -0
- data/spec/spec_helper.rb +20 -0
- metadata +134 -0
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
## Alpaca (outta nowhere)
|
2
|
+
|
3
|
+

|
4
|
+
|
5
|
+
Alpaca (outta nowhere) is a rack middleware that allows developers to quickly and easily configure and manage a whitelist and/or blacklist. The motivation for Alpaca is to address use cases around security concerns such as malicious clients, denial of service, or adding an extra layer of security to an API or a subset of API endpoints.
|
6
|
+
|
7
|
+
In progress
|
8
|
+
----------
|
9
|
+
|
10
|
+
~~Global-level whitelist and blacklist~~
|
11
|
+
~~Configuration and management via YAML~~
|
12
|
+
~~Whitelist-by-default, blacklist-by-default~~
|
13
|
+
Controller-level whitelist and blacklist via `before_filter`
|
14
|
+
|
15
|
+
Getting started
|
16
|
+
----------
|
17
|
+
|
18
|
+
Install standalone or add to your Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'alpaca'
|
22
|
+
```
|
23
|
+
|
24
|
+
Run `bundle install` to install the gem.
|
25
|
+
|
26
|
+
Afer you install Alpaca, run the generator to create a default config file:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
rails generate alpaca:install
|
30
|
+
```
|
31
|
+
|
32
|
+
Add Alpaca to your middleware stack in `config/application.rb`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
config.middleware.use Rack::Alpaca
|
36
|
+
```
|
37
|
+
|
38
|
+
or if you are not using rails, but in another Rack application, in `config.ru`:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
use Rack::Alpaca
|
42
|
+
```
|
43
|
+
|
44
|
+
Usage
|
45
|
+
----------
|
46
|
+
|
47
|
+
Alpaca supports whitelisting and blacklisting single IP addresses (e.g., `0.0.0.1`), hostnames (e.g., `localhost`), and range of IP addresses with subnet masks (e.g., `198.18.0.0/15`, `2001:db8::/32`). You may use IPv4 or IPv6. You can make changes in `config/alpaca.yml` to either list.
|
48
|
+
|
49
|
+
Depending on your strategy, you may choose to enforce a whitelist-by-default or blacklist-by-default approach. You can use the `default` key in the configuration file with either `whitelist` or `blacklist` as its value.
|
50
|
+
|
51
|
+
Performance (WIP)
|
52
|
+
----------
|
53
|
+
|
54
|
+
Through initial testing, Alpaca does not appear to cause noticeable overhead. Future tests under different types of load will be documented here.
|
55
|
+
|
56
|
+
Author
|
57
|
+
----------
|
58
|
+
|
59
|
+
Jeff Chao, @thejeffchao, http://thejeffchao.com
|
data/Rakefile
ADDED
data/lib/alpaca.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'yaml'
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Alpaca
|
7
|
+
class << self
|
8
|
+
attr_reader :whitelist, :blacklist, :default
|
9
|
+
|
10
|
+
def new (app)
|
11
|
+
@app = app
|
12
|
+
config = YAML.load_file('config/alpaca.yml')
|
13
|
+
@whitelist ||= config['whitelist'].map { |ip| IPAddr.new(ip) }.freeze
|
14
|
+
@blacklist ||= config['blacklist'].map { |ip| IPAddr.new(ip) }.freeze
|
15
|
+
@default = config['default']
|
16
|
+
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def call (env)
|
21
|
+
req = Rack::Request.new(env)
|
22
|
+
|
23
|
+
if whitelisted?('whitelist', req)
|
24
|
+
@app.call(env)
|
25
|
+
elsif blacklisted?('blacklist', req)
|
26
|
+
[503, {}, ["Request blocked\n"]]
|
27
|
+
else
|
28
|
+
default_strategy(env)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def default_strategy (env)
|
35
|
+
if @default == 'whitelist'
|
36
|
+
@app.call(env)
|
37
|
+
elsif @default == 'blacklist'
|
38
|
+
[503, {}, ["Request blocked\n"]]
|
39
|
+
else
|
40
|
+
raise 'Unknown default strategy'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def check (type, req)
|
45
|
+
instance_variable_get("@#{type}").any? do |ip|
|
46
|
+
ip.include?(req.ip)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :whitelisted?, :check
|
51
|
+
alias_method :blacklisted?, :check
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Alpaca
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path('../../../../../../config', __FILE__)
|
5
|
+
|
6
|
+
desc 'Copies an Alpaca configuration file to your application.'
|
7
|
+
|
8
|
+
def copy_configuration
|
9
|
+
copy_file 'alpaca.yml', 'config/alpaca.yml'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Rack::Alpaca' do
|
4
|
+
it 'should permit localhost' do
|
5
|
+
get '/', {}, 'REMOTE_ADDR' => '127.0.0.1'
|
6
|
+
last_response.status.must_equal 200
|
7
|
+
last_response.body.must_equal 'foo'
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'Whitelist' do
|
11
|
+
before do
|
12
|
+
@ips = ["0.0.0.1", "198.18.0.0/15", "::/128"].map { |ip| IPAddr.new(ip) }
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have a whitelist' do
|
16
|
+
Rack::Alpaca.whitelist.class.must_equal Array
|
17
|
+
Rack::Alpaca.whitelist.each { |ip| ip.class.must_equal IPAddr }
|
18
|
+
Rack::Alpaca.whitelist.must_equal @ips
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should permit IP addresses on the whitelist' do
|
22
|
+
Rack::Alpaca.whitelist.each do |ip|
|
23
|
+
get '/', {}, 'REMOTE_ADDR' => ip.to_s
|
24
|
+
last_response.status.must_equal 200
|
25
|
+
last_response.body.must_equal 'foo'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'an IP on whitelist and blacklist' do
|
30
|
+
it 'should permit the IP address' do
|
31
|
+
get '/', {}, 'REMOTE_ADDR' => '0.0.0.1'
|
32
|
+
last_response.status.must_equal 200
|
33
|
+
last_response.body.must_equal 'foo'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'Blacklist' do
|
39
|
+
before do
|
40
|
+
@ips = ["0.0.0.1", "0.0.0.2", "2001:db8::/32"].map { |ip| IPAddr.new(ip) }
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should have a blacklist' do
|
44
|
+
Rack::Alpaca.blacklist.class.must_equal Array
|
45
|
+
Rack::Alpaca.whitelist.each { |ip| ip.class.must_equal IPAddr }
|
46
|
+
Rack::Alpaca.blacklist.must_equal @ips
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should reject IP addresses on the blacklist' do
|
50
|
+
Rack::Alpaca.blacklist.each do |ip|
|
51
|
+
get '/', {}, 'REMOTE_ADDR' => ip.to_s
|
52
|
+
unless Rack::Alpaca.whitelist.include? ip
|
53
|
+
last_response.status.must_equal 503
|
54
|
+
last_response.body.must_equal "Request blocked\n"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'an IP on whitelist and blacklist' do
|
60
|
+
it 'should permit the IP address' do
|
61
|
+
get '/', {}, 'REMOTE_ADDR' => '0.0.0.1'
|
62
|
+
last_response.status.must_equal 200
|
63
|
+
last_response.body.must_equal 'foo'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require "minitest/autorun"
|
5
|
+
require "minitest/pride"
|
6
|
+
|
7
|
+
require "rack/test"
|
8
|
+
require 'active_support'
|
9
|
+
require "alpaca"
|
10
|
+
|
11
|
+
class Minitest::Spec
|
12
|
+
include Rack::Test::Methods
|
13
|
+
|
14
|
+
def app
|
15
|
+
Rack::Builder.new {
|
16
|
+
use Rack::Alpaca
|
17
|
+
run lambda { |env| [200, {}, ['foo']] }
|
18
|
+
}.to_app
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alpaca
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeff Chao
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: activesupport
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.0.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: minitest
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rack-test
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: A rack middleware for whitelisting and blacklisting IPs
|
95
|
+
email: jeffchao@me.com
|
96
|
+
executables: []
|
97
|
+
extensions: []
|
98
|
+
extra_rdoc_files: []
|
99
|
+
files:
|
100
|
+
- lib/alpaca.rb
|
101
|
+
- lib/rack/alpaca/version.rb
|
102
|
+
- lib/rails/generators/alpaca/install/install_generator.rb
|
103
|
+
- Rakefile
|
104
|
+
- README.md
|
105
|
+
- spec/rack_alpaca_spec.rb
|
106
|
+
- spec/spec_helper.rb
|
107
|
+
homepage: http://github.com/jeffchao/alpaca
|
108
|
+
licenses: []
|
109
|
+
post_install_message:
|
110
|
+
rdoc_options:
|
111
|
+
- --charset=UTF-8
|
112
|
+
require_paths:
|
113
|
+
- lib
|
114
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
115
|
+
none: false
|
116
|
+
requirements:
|
117
|
+
- - ! '>='
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 1.9.3
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.8.25
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: Whitelist and blacklist IPs
|
132
|
+
test_files:
|
133
|
+
- spec/rack_alpaca_spec.rb
|
134
|
+
- spec/spec_helper.rb
|