Skip to content

Commit

Permalink
Add the option to geo-block IP addresses
Browse files Browse the repository at this point in the history
If there is a persistent attack using botnets from abroad we want
to have the option to block ip addresses from outside the UK.
  • Loading branch information
pixeltrix committed Sep 10, 2016
1 parent eab39cd commit b7b6cd3
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 11 deletions.
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ MODERATE_HOST=localhost
SITE_TITLE="Petition parliament (Development)"
MEMCACHE_SERVERS=localhost:11211
APPSIGNAL_APP_NAME=epetitions-dev
GEOIP_DB_PATH=/usr/local/var/GeoIP/GeoLite2-Country.mmdb
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ MODERATE_HOST=moderate.petition.parliament.uk
SITE_TITLE="Petition parliament (Test)"
MEMCACHE_SERVERS=localhost:11211
APPSIGNAL_APP_NAME=epetitions-test
GEOIP_DB_PATH=/path/to/GeoLite2-Country.mmdb
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ gem 'lograge'
gem 'logstash-logger'
gem 'jbuilder'
gem 'paperclip'
gem 'maxminddb'

# Two AWS libraries:
# - aws-sdk v2 for CodeDeploy, which neither Fog nor aws-sdk v1 support
Expand Down
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ GEM
nokogiri (>= 1.5.9)
mail (2.6.4)
mime-types (>= 1.16, < 4)
maxminddb (0.1.11)
method_source (0.8.2)
mime-types (2.99.2)
mimemagic (0.3.0)
Expand Down Expand Up @@ -333,6 +334,7 @@ DEPENDENCIES
launchy
lograge
logstash-logger
maxminddb
net-http-persistent
nokogiri
paperclip
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/admin/rate_limits_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ def rate_limit_params
end

def rate_limit_attributes
%i[burst_rate burst_period sustained_rate sustained_period domain_whitelist ip_whitelist domain_blacklist ip_blacklist]
%i[
burst_rate burst_period sustained_rate sustained_period
domain_whitelist ip_whitelist domain_blacklist ip_blacklist
geoblocking_enabled countries
]
end

def find_rate_limit
Expand Down
37 changes: 37 additions & 0 deletions app/models/rate_limit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class RateLimit < ActiveRecord::Base
validates :ip_whitelist, length: { maximum: 10000, allow_blank: true }
validates :domain_blacklist, length: { maximum: 50000, allow_blank: true }
validates :ip_blacklist, length: { maximum: 50000, allow_blank: true }
validates :countries, length: { maximum: 2000, allow_blank: true }

validate do
unless sustained_rate.nil? || burst_rate.nil?
Expand Down Expand Up @@ -59,6 +60,7 @@ def exceeded?(signature)
return false if ip_whitelisted?(signature.ip_address)
return true if domain_blacklisted?(signature.domain)
return true if ip_blacklisted?(signature.ip_address)
return true if ip_geoblocked?(signature.ip_address)

burst_rate_exceeded?(signature) || sustained_rate_exceeded?(signature)
end
Expand Down Expand Up @@ -99,6 +101,15 @@ def blacklisted_ips
@blacklisted_ips ||= build_ip_blacklist
end

def allowed_countries
@allowed_countries ||= build_allowed_countries
end

def countries=(value)
@allowed_countries = nil
super(normalize_lines(value))
end

private

def strip_comments(list)
Expand Down Expand Up @@ -153,6 +164,32 @@ def ip_blacklisted?(ip)
blacklisted_ips.any?{ |i| i.include?(ip) }
end

def build_allowed_countries
strip_blank_lines(strip_comments(countries)).map(&:strip)
end

def ip_geoblocked?(ip)
geoblocking_enabled? && country_blocked?(ip)
end

def country_blocked?(ip)
allowed_countries.exclude?(country_for_ip(ip))
end

def country_for_ip(ip)
result = geoip_db.lookup(ip)

if result.found?
result.country.name
else
"UNKNOWN"
end
end

def geoip_db
@geoip_db ||= MaxMindDB.new(ENV.fetch('GEOIP_DB_PATH'))
end

def convert_glob(pattern)
pattern.gsub(GLOB_PATTERN) do |match|
if match == RECURSIVE_GLOB
Expand Down
42 changes: 37 additions & 5 deletions app/views/admin/rate_limits/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,43 @@
Domain Whitelist |
<%= link_to "Domain Blacklist", edit_admin_rate_limits_path(tab: 'domain_blacklist') %> |
<%= link_to "IP Whitelist", edit_admin_rate_limits_path(tab: 'ip_whitelist') %> |
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %>
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %> |
<%= link_to "Countries", edit_admin_rate_limits_path(tab: 'countries') %>
<% elsif params[:tab] == "domain_blacklist" %>
<%= link_to "Rate Limits", edit_admin_rate_limits_path %> |
<%= link_to "Domain Whitelist", edit_admin_rate_limits_path(tab: 'domain_whitelist') %> |
Domain Blacklist |
<%= link_to "IP Whitelist", edit_admin_rate_limits_path(tab: 'ip_whitelist') %> |
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %>
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %> |
<%= link_to "Countries", edit_admin_rate_limits_path(tab: 'countries') %>
<% elsif params[:tab] == "ip_whitelist" %>
<%= link_to "Rate Limits", edit_admin_rate_limits_path %> |
<%= link_to "Domain Whitelist", edit_admin_rate_limits_path(tab: 'domain_whitelist') %> |
<%= link_to "Domain Blacklist", edit_admin_rate_limits_path(tab: 'domain_blacklist') %> |
IP Whitelist |
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %>
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %> |
<%= link_to "Countries", edit_admin_rate_limits_path(tab: 'countries') %>
<% elsif params[:tab] == "ip_blacklist" %>
<%= link_to "Rate Limits", edit_admin_rate_limits_path %> |
<%= link_to "Domain Whitelist", edit_admin_rate_limits_path(tab: 'domain_whitelist') %> |
<%= link_to "Domain Blacklist", edit_admin_rate_limits_path(tab: 'domain_blacklist') %> |
<%= link_to "IP Whitelist", edit_admin_rate_limits_path(tab: 'ip_whitelist') %> |
IP Blacklist
IP Blacklist |
<%= link_to "Countries", edit_admin_rate_limits_path(tab: 'countries') %>
<% elsif params[:tab] == "countries" %>
<%= link_to "Rate Limits", edit_admin_rate_limits_path %> |
<%= link_to "Domain Whitelist", edit_admin_rate_limits_path(tab: 'domain_whitelist') %> |
<%= link_to "Domain Blacklist", edit_admin_rate_limits_path(tab: 'domain_blacklist') %> |
<%= link_to "IP Whitelist", edit_admin_rate_limits_path(tab: 'ip_whitelist') %> |
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %> |
Countries
<% else %>
Rate Limits |
<%= link_to "Domain Whitelist", edit_admin_rate_limits_path(tab: 'domain_whitelist') %> |
<%= link_to "Domain Blacklist", edit_admin_rate_limits_path(tab: 'domain_blacklist') %> |
<%= link_to "IP Whitelist", edit_admin_rate_limits_path(tab: 'ip_whitelist') %> |
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %>
<%= link_to "IP Blacklist", edit_admin_rate_limits_path(tab: 'ip_blacklist') %> |
<%= link_to "Countries", edit_admin_rate_limits_path(tab: 'countries') %>
<% end %>
</p>

Expand Down Expand Up @@ -74,6 +86,26 @@
<p><small>use CIDR addressing to match ranges, e.g. 192.168.0.0/24</small><p>
<% end %>

<% elsif params[:tab] == "countries" %>
<%= hidden_field_tag :tab, "countries" %>

<%= form_row for: [form.object, :countries] do %>
<%= error_messages_for_field @rate_limit, :countries %>
<%= form.text_area :countries, tabindex: increment, rows: 8, class: 'form-control' %>
<p><small>Add countries that are allowed to sign petitions, e.g. United Kingdom</small><p>
<% end %>

<%= form_row for: [form.object, :geoblocking_enabled], class: 'inline' do %>
<%= form.label :geoblocking_enabled, "Enable geoblocking of signatures?", class: 'form-label' %>
<%= error_messages_for_field @rate_limit, :geoblocking_enabled %>
<%= form.label :geoblocking_enabled_true, nil, class: 'block-label' do %>
<%= form.radio_button :geoblocking_enabled, true %> Yes
<% end %>
<%= form.label :geoblocking_enabled_false, nil, class: 'block-label' do %>
<%= form.radio_button :geoblocking_enabled, false %> No
<% end %>
<% end %>

<% else %>

<h2>Short period signing rate per IP address</h2>
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20160820162023_add_geoblocking_to_rate_limits.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddGeoblockingToRateLimits < ActiveRecord::Migration
def change
add_column :rate_limits, :geoblocking_enabled, :boolean, null: false, default: false
end
end
5 changes: 5 additions & 0 deletions db/migrate/20160820165029_add_countries_to_rate_limits.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddCountriesToRateLimits < ActiveRecord::Migration
def change
add_column :rate_limits, :countries, :string, limit: 2000, null: false, default: ""
end
end
8 changes: 7 additions & 1 deletion db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,9 @@ CREATE TABLE rate_limits (
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL,
domain_blacklist character varying(50000) DEFAULT ''::character varying NOT NULL,
ip_blacklist character varying(50000) DEFAULT ''::character varying NOT NULL
ip_blacklist character varying(50000) DEFAULT ''::character varying NOT NULL,
geoblocking_enabled boolean DEFAULT false NOT NULL,
countries character varying(2000) DEFAULT ''::character varying NOT NULL
);


Expand Down Expand Up @@ -1852,5 +1854,9 @@ INSERT INTO schema_migrations (version) VALUES ('20160819062058');

INSERT INTO schema_migrations (version) VALUES ('20160820132056');

INSERT INTO schema_migrations (version) VALUES ('20160820162023');

INSERT INTO schema_migrations (version) VALUES ('20160820165029');

INSERT INTO schema_migrations (version) VALUES ('20160822064645');

9 changes: 9 additions & 0 deletions features/admin/updating_rate_limits.feature
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,14 @@ Feature: Sysadmin updates the rate limits
And I press "Save"
Then I should see "IP blacklist is invalid"
When I fill in "rate_limit_ip_blacklist" with "127.0.0.1/32"

Scenario: Sysadmin updates the countries
When I am logged in as a sysadmin
And I am on the admin home page
And I follow "Rate Limits"
Then I should see "Edit Rate Limits"
When I follow "Countries"
Then I should see "Add countries that are allowed to sign petitions, e.g. United Kingdom"
When I fill in "rate_limit_countries" with "United Kingdom"
And I press "Save"
Then I should see "Rate limits updated successfully"
44 changes: 43 additions & 1 deletion spec/controllers/admin/rate_limits_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,51 @@
end
end

context "when submitting just the domain blacklist" do
let :params do
{ domain_blacklist: "example.com" }
end

it "redirects to the edit page" do
expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/rate-limits/edit")
end

it "sets the flash notice message" do
expect(flash[:notice]).to eq("Rate limits updated successfully")
end
end

context "when submitting just the ip whitelist" do
let :params do
{ domain_whitelist: "127.0.0.1" }
{ ip_whitelist: "127.0.0.1" }
end

it "redirects to the edit page" do
expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/rate-limits/edit")
end

it "sets the flash notice message" do
expect(flash[:notice]).to eq("Rate limits updated successfully")
end
end

context "when submitting just the ip blacklist" do
let :params do
{ ip_blacklist: "127.0.0.1" }
end

it "redirects to the edit page" do
expect(response).to redirect_to("https://moderate.petition.parliament.uk/admin/rate-limits/edit")
end

it "sets the flash notice message" do
expect(flash[:notice]).to eq("Rate limits updated successfully")
end
end

context "when submitting just the countries list" do
let :params do
{ countries: "127.0.0.1", geoblocking_enabled: "true" }
end

it "redirects to the edit page" do
Expand Down
Loading

0 comments on commit b7b6cd3

Please sign in to comment.