summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRubén Dávila <rdavila84@gmail.com>2016-01-20 15:07:00 -0500
committerRubén Dávila <rdavila84@gmail.com>2016-01-20 15:07:00 -0500
commit985297d82ac706bee0a310bf61003ac1f5c56b8a (patch)
tree309512a384624ac927e8cc11b1adc340134cf79b
parent86b75f4713fa6cb14cbf443c1e29ca6384204b65 (diff)
parent1553c560e0d02e670b7cec2b443545e67418b569 (diff)
downloadgitlab-ce-issue_3945.tar.gz
Merge branch 'master' into issue_3945issue_3945
-rw-r--r--CHANGELOG1
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb5
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml16
-rw-r--r--db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb6
-rw-r--r--db/schema.rb2
-rw-r--r--lib/dnsxl_check.rb105
-rw-r--r--lib/gitlab/ip_check.rb34
-rw-r--r--spec/lib/dnsxl_check_spec.rb68
10 files changed, 241 insertions, 0 deletions
diff --git a/CHANGELOG b/CHANGELOG
index cd147dc7662..9490540f1b1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -73,6 +73,7 @@ v 8.4.0 (unreleased)
- Expose button to CI Lint tool on project builds page
- Fix: Creator should be added as a master of the project on creation
- Added X-GitLab-... headers to emails from CI and Email On Push services (Anton Baklanov)
+ - Add IP check against DNSBLs at account sign-up
v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 9943745208e..094eef28a43 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -74,6 +74,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_timeout,
:metrics_method_call_threshold,
:metrics_sample_interval,
+ :ip_blocking_enabled,
+ :dnsbl_servers_list,
:recaptcha_enabled,
:recaptcha_site_key,
:recaptcha_private_key,
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index c48175a4c5a..5efdd613e79 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -8,6 +8,11 @@ class RegistrationsController < Devise::RegistrationsController
def create
if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
+ if Gitlab::IpCheck.new(request.remote_ip).spam?
+ flash[:alert] = 'Could not create an account. This IP is listed for spam.'
+ return render action: 'new'
+ end
+
super
else
flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 59563b8823c..2f3487b53ac 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -43,6 +43,8 @@
# metrics_port :integer default(8089)
# sentry_enabled :boolean default(FALSE)
# sentry_dsn :string
+# ip_blocking_enabled :boolean default(FALSE)
+# dns_blacklist_threshold :float default(0.33)
#
class ApplicationSetting < ActiveRecord::Base
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 35e4dd761ab..c4020c8273b 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -215,6 +215,22 @@
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
+ = f.label :ip_blocking_enabled do
+ = f.check_box :ip_blocking_enabled
+ Enable IP check against blacklist at sign-up
+ .help-block Helps preventing accounts creation from 'known spam sources'
+
+ .form-group
+ = f.label :dnsbl_servers_list, class: 'control-label col-sm-2' do
+ DNSBL servers list
+ .col-sm-10
+ = f.text_field :dnsbl_servers_list, class: 'form-control'
+ .help-block
+ Please enter DNSBL servers separated with comma
+
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
= f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled
Enable reCAPTCHA
diff --git a/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb b/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
new file mode 100644
index 00000000000..26606b10b54
--- /dev/null
+++ b/db/migrate/20160118232755_add_ip_blocking_settings_to_application_settings.rb
@@ -0,0 +1,6 @@
+class AddIpBlockingSettingsToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :ip_blocking_enabled, :boolean, default: false
+ add_column :application_settings, :dnsbl_servers_list, :text
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a7cadacc0ce..97594011a02 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -64,6 +64,8 @@ ActiveRecord::Schema.define(version: 20160120172143) do
t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false
t.string "sentry_dsn"
+ t.boolean "ip_blocking_enabled", default: false
+ t.text "dnsbl_servers_list"
end
create_table "audit_events", force: :cascade do |t|
diff --git a/lib/dnsxl_check.rb b/lib/dnsxl_check.rb
new file mode 100644
index 00000000000..1e506b2d9cb
--- /dev/null
+++ b/lib/dnsxl_check.rb
@@ -0,0 +1,105 @@
+require 'resolv'
+
+class DNSXLCheck
+
+ class Resolver
+ def self.search(query)
+ begin
+ Resolv.getaddress(query)
+ true
+ rescue Resolv::ResolvError
+ false
+ end
+ end
+ end
+
+ IP_REGEXP = /\A(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\z/
+ DEFAULT_THRESHOLD = 0.33
+
+ def self.create_from_list(list)
+ dnsxl_check = DNSXLCheck.new
+
+ list.each do |entry|
+ dnsxl_check.add_list(entry.domain, entry.weight)
+ end
+
+ dnsxl_check
+ end
+
+ def test(ip)
+ if use_threshold?
+ test_with_threshold(ip)
+ else
+ test_strict(ip)
+ end
+ end
+
+ def test_with_threshold(ip)
+ return false if lists.empty?
+
+ search(ip)
+ final_score >= threshold
+ end
+
+ def test_strict(ip)
+ return false if lists.empty?
+
+ search(ip)
+ @score > 0
+ end
+
+ def use_threshold=(value)
+ @use_threshold = value == true
+ end
+
+ def use_threshold?
+ @use_threshold &&= true
+ end
+
+ def threshold=(threshold)
+ raise ArgumentError, "'threshold' value must be grather than 0 and less than or equal to 1" unless threshold > 0 && threshold <= 1
+ @threshold = threshold
+ end
+
+ def threshold
+ @threshold ||= DEFAULT_THRESHOLD
+ end
+
+ def add_list(domain, weight)
+ @lists ||= []
+ @lists << { domain: domain, weight: weight }
+ end
+
+ def lists
+ @lists ||= []
+ end
+
+ private
+
+ def search(ip)
+ raise ArgumentError, "'ip' value must be in #{IP_REGEXP} format" unless ip.match(IP_REGEXP)
+
+ @score = 0
+
+ reversed = reverse_ip(ip)
+ search_in_rbls(reversed)
+ end
+
+ def reverse_ip(ip)
+ ip.split('.').reverse.join('.')
+ end
+
+ def search_in_rbls(reversed_ip)
+ lists.each do |rbl|
+ query = "#{reversed_ip}.#{rbl[:domain]}"
+ @score += rbl[:weight] if Resolver.search(query)
+ end
+ end
+
+ def final_score
+ weights = lists.map{ |rbl| rbl[:weight] }.reduce(:+).to_i
+ return 0 if weights == 0
+
+ (@score.to_f / weights.to_f).round(2)
+ end
+end
diff --git a/lib/gitlab/ip_check.rb b/lib/gitlab/ip_check.rb
new file mode 100644
index 00000000000..f2e9b50d225
--- /dev/null
+++ b/lib/gitlab/ip_check.rb
@@ -0,0 +1,34 @@
+module Gitlab
+ class IpCheck
+
+ def initialize(ip)
+ @ip = ip
+
+ application_settings = ApplicationSetting.current
+ @ip_blocking_enabled = application_settings.ip_blocking_enabled
+ @dnsbl_servers_list = application_settings.dnsbl_servers_list
+ end
+
+ def spam?
+ @ip_blocking_enabled && blacklisted?
+ end
+
+ private
+
+ def blacklisted?
+ on_dns_blacklist?
+ end
+
+ def on_dns_blacklist?
+ dnsbl_check = DNSXLCheck.new
+ prepare_dnsbl_list(dnsbl_check)
+ dnsbl_check.test(@ip)
+ end
+
+ def prepare_dnsbl_list(dnsbl_check)
+ @dnsbl_servers_list.split(',').map(&:strip).reject(&:empty?).each do |domain|
+ dnsbl_check.add_list(domain, 1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/dnsxl_check_spec.rb b/spec/lib/dnsxl_check_spec.rb
new file mode 100644
index 00000000000..a35a1be0c90
--- /dev/null
+++ b/spec/lib/dnsxl_check_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+require 'ostruct'
+
+describe 'DNSXLCheck', lib: true, no_db: true do
+ let(:spam_ip) { '127.0.0.2' }
+ let(:no_spam_ip) { '127.0.0.3' }
+ let(:invalid_ip) { 'a.b.c.d' }
+ let!(:dnsxl_check) { DNSXLCheck.create_from_list([OpenStruct.new({ domain: 'test', weight: 1 })]) }
+
+ before(:context) do
+ class DNSXLCheck::Resolver
+ class << self
+ alias_method :old_search, :search
+ def search(query)
+ return false if query.match(/always\.failing\.domain\z/)
+ return true if query.match(/\A2\.0\.0\.127\./)
+ return false if query.match(/\A3\.0\.0\.127\./)
+ end
+ end
+ end
+ end
+
+ describe '#test' do
+ before do
+ dnsxl_check.threshold = 0.75
+ dnsxl_check.add_list('always.failing.domain', 1)
+ end
+
+ context 'when threshold is used' do
+ before { dnsxl_check.use_threshold= true }
+
+ it { expect(dnsxl_check.test(spam_ip)).to be_falsey }
+ end
+
+ context 'when threshold is not used' do
+ before { dnsxl_check.use_threshold= false }
+
+ it { expect(dnsxl_check.test(spam_ip)).to be_truthy }
+ end
+ end
+
+ describe '#test_with_threshold' do
+ it { expect{ dnsxl_check.test_with_threshold(invalid_ip) }.to raise_error(ArgumentError) }
+
+ it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_truthy }
+ it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
+ end
+
+ describe '#test_strict' do
+ before do
+ dnsxl_check.threshold = 1
+ dnsxl_check.add_list('always.failing.domain', 1)
+ end
+
+ it { expect{ dnsxl_check.test_strict(invalid_ip) }.to raise_error(ArgumentError) }
+
+ it { expect(dnsxl_check.test_with_threshold(spam_ip)).to be_falsey }
+ it { expect(dnsxl_check.test_with_threshold(no_spam_ip)).to be_falsey }
+ it { expect(dnsxl_check.test_strict(spam_ip)).to be_truthy }
+ it { expect(dnsxl_check.test_strict(no_spam_ip)).to be_falsey }
+ end
+
+ describe '#threshold=' do
+ it { expect{ dnsxl_check.threshold = 0 }.to raise_error(ArgumentError) }
+ it { expect{ dnsxl_check.threshold = 1.1 }.to raise_error(ArgumentError) }
+ it { expect{ dnsxl_check.threshold = 0.5 }.not_to raise_error }
+ end
+end