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.
- Global-level whitelisting and blacklisting at the rack layer
- Controller-level whitelisting and blacklisting via
before_filter
- Per-action whitelisting and blacklisting at the controller level
- Configuration management via YAML
Install standalone or add to your Gemfile:
gem 'alpaca'
Run bundle install
to install the gem.
Afer you install Alpaca, run the generator to create a default config file:
rails generate alpaca:install
Add Alpaca to your middleware stack in config/application.rb
:
config.middleware.use Rack::Alpaca
or if you are not using rails, but in another Rack application, in config.ru
:
use Rack::Alpaca
Alpaca supports:
- whitelisting and blacklisting single IP addresses (e.g.,
0.0.0.1
) - hostnames (e.g.,
127.0.0.1
also affectslocalhost
) - range of IP addresses via subnet masks (e.g.,
198.18.0.0/15
,2001:db8::/32
).
You may use IPv4 or IPv6. Make changes in config/alpaca.yml
by adding or removing IPs to and from either list. Your file should resemble the following:
whitelist:
- 0.0.0.1
- 198.18.0.0/15
- "::/128"
blacklist:
- 0.0.0.1
- 0.0.0.2
- "2001:db8::/32"
default: allow
blocked_message: 'Request Blocked'
Depending on your strategy, you may choose to enforce an allow-by-default or deny-by-default approach. You can use the default
key in the configuration file with either allow
or deny
as its value.
A note about precedence: If an IP exists in both the whitelist and blacklist, then whitelist will take precedence and allow the IP.
There exists two methods for handling IPs at the controller level. You must have your global-level default set to allow
for it to be useful. This is because a global-level deny
would have already blocked all IPs at the rack layer.
before_filter :enable_whitelist_and_deny_by_default
# or
before_filter :enable_blacklist_and_allow_by_default
You may optionally attach this filter to specific method(s):
before_filter :enable_whitlist_and_deny_by_default, only: [:create, :update]
Lastly, you may add additional IPs that were not previously defined in your alpaca.yml`:
before_filter only: [:create, :update] { |f| f.enable_whitelist_and_deny_by_default(['0.0.0.1']) }
Given that some configuration permuations may be unecessary or illogical, the following is a table of typical use cases. The cells represent the resulting behavior:
global allow-by-default | global deny-by-default | |
---|---|---|
no controller filter | all IPs allowed | all IPs denied |
controller filter whitelist, no added IPs | IPs in whitelist from alpaca.yml allowed for controller. All other IPs denied for controller. All IPs allowed everywhere else |
all IPs denied |
controller filter whitelist, added IPs | IPs in whitelist from alpaca.yml and arguments to filter allowed for controller. All other IPs denied for controller. All IPs allowed everywhere else |
all IPs denied |
controller filter blacklist, no added IPs | IPs in blacklist from alpaca.yml denied for controller. All other IPs allowed for controller. All IPs allowed everywhere else |
all IPs denied |
controller filter blacklist, added IPs | IPs in blacklist from alpaca.yml and arguments denied for controller. All other IPs allowed for controller. All IPs allowed everywhere else |
all IPs denied |
Through initial testing, Alpaca does not appear to cause noticeable overhead. Future tests under different types of load will be documented here.
Jeff Chao, @thejeffchao, http://thejeffchao.com