diff --git a/CHANGELOG b/CHANGELOG index 2061237fb42..e4d180359b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +v 7.7.0 + - Block Git HTTP access after 10 failed authentication attempts + v 7.6.0 - Fork repository to groups - New rugged version diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 7b4c180fccc..b474063505f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -298,6 +298,17 @@ production: &base # ![Company Logo](http://www.companydomain.com/logo.png) # [Learn more about CompanyName](http://www.companydomain.com/) + rack_attack: + git_basic_auth: + # Limit the number of Git HTTP authentication attempts per IP + # maxretry: 10 + # + # Reset the auth attempt counter per IP after 60 seconds + # findtime: 60 + # + # Ban an IP for one hour (3600s) after too many auth attempts + # bantime: 3600 + development: <<: *base diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 27bb83784ba..4464d9d0001 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -171,6 +171,15 @@ Settings.satellites['timeout'] ||= 30 # Settings['extra'] ||= Settingslogic.new({}) +# +# Rack::Attack settings +# +Settings['rack_attack'] ||= Settingslogic.new({}) +Settings.rack_attack['git_basic_auth'] ||= Settingslogic.new({}) +Settings.rack_attack.git_basic_auth['maxretry'] ||= 10 +Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute +Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour + # # Testing settings # diff --git a/config/initializers/rack_attack_git_basic_auth.rb b/config/initializers/rack_attack_git_basic_auth.rb new file mode 100644 index 00000000000..2348768ff16 --- /dev/null +++ b/config/initializers/rack_attack_git_basic_auth.rb @@ -0,0 +1,10 @@ +unless Rails.env.test? + Rack::Attack.blacklist('Git HTTP Basic Auth') do |req| + Rack::Attack::Allow2Ban.filter(req.ip, Gitlab.config.rack_attack.git_basic_auth) do + # This block only gets run if the IP was not already banned. + # Return false, meaning that we do not see anything wrong with the + # request at this time + false + end + end +end diff --git a/config/initializers/redis-store-fix-expiry.rb b/config/initializers/redis-store-fix-expiry.rb new file mode 100644 index 00000000000..dd27596cd0b --- /dev/null +++ b/config/initializers/redis-store-fix-expiry.rb @@ -0,0 +1,21 @@ +# Monkey-patch Redis::Store to make 'setex' and 'expire' work with namespacing + +module Gitlab + class Redis + class Store + module Namespace + def setex(key, expires_in, value, options=nil) + namespace(key) { |key| super(key, expires_in, value) } + end + + def expire(key, expires_in) + namespace(key) { |key| super(key, expires_in) } + end + end + end + end +end + +Redis::Store.class_eval do + include Gitlab::Redis::Store::Namespace +end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 762639414e0..ab5d2ef3da4 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -72,8 +72,18 @@ module Grack end def authenticate_user(login, password) - auth = Gitlab::Auth.new - auth.find(login, password) + user = Gitlab::Auth.new.find(login, password) + return user if user.present? + + # At this point, we know the credentials were wrong. We let Rack::Attack + # know there was a failed authentication attempt from this IP + Rack::Attack::Allow2Ban.filter(@request.ip, Gitlab.config.rack_attack.git_basic_auth) do + # Return true, so that Allow2Ban increments the counter (stored in + # Rails.cache) for the IP + true + end + + nil # No user was found end def authorized_request?