diff options
author | Pawel Chojnacki <pawel@chojnacki.ws> | 2017-02-06 15:48:46 +0300 |
---|---|---|
committer | Pawel Chojnacki <pawel@chojnacki.ws> | 2017-03-06 17:41:24 +0300 |
commit | e5cf3f51fb568361a247d715facb6cd9bb15bb16 (patch) | |
tree | d12f9644c8b0dd0765fd0de90d69027848341083 /lib | |
parent | 27729aa3a4666c6b06006c76023f4bff60f8ba25 (diff) |
Allow limiting logging in users from too many different IPs.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/gitlab/auth.rb | 22 | ||||
-rw-r--r-- | lib/gitlab/auth/unique_ips_limiter.rb | 70 | ||||
-rw-r--r-- | lib/gitlab/request_context.rb | 25 |
3 files changed, 108 insertions, 9 deletions
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 0a5abc92190..be055080853 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -22,23 +22,27 @@ module Gitlab user_with_password_for_git(login, password) || Gitlab::Auth::Result.new + Gitlab::Auth::UniqueIpsLimiter.limit_user! { result.actor } + rate_limit!(ip, success: result.success?, login: login) result end def find_with_user_password(login, password) - user = User.by_login(login) + Gitlab::Auth::UniqueIpsLimiter.limit_user! do + user = User.by_login(login) - # If no user is found, or it's an LDAP server, try LDAP. - # LDAP users are only authenticated via LDAP - if user.nil? || user.ldap_user? - # Second chance - try LDAP authentication - return nil unless Gitlab::LDAP::Config.enabled? + # If no user is found, or it's an LDAP server, try LDAP. + # LDAP users are only authenticated via LDAP + if user.nil? || user.ldap_user? + # Second chance - try LDAP authentication + return nil unless Gitlab::LDAP::Config.enabled? - Gitlab::LDAP::Authentication.login(login, password) - else - user if user.valid_password?(password) + Gitlab::LDAP::Authentication.login(login, password) + else + user if user.valid_password?(password) + end end end diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb new file mode 100644 index 00000000000..21307eb35e4 --- /dev/null +++ b/lib/gitlab/auth/unique_ips_limiter.rb @@ -0,0 +1,70 @@ +module Gitlab + module Auth + class TooManyIps < StandardError + attr_reader :user_id, :ip, :unique_ips_count + + def initialize(user_id, ip, unique_ips_count) + @user_id = user_id + @ip = ip + @unique_ips_count = unique_ips_count + end + + def message + "User #{user_id} from IP: #{ip} tried logging from too many ips: #{unique_ips_count}" + end + end + + class UniqueIpsLimiter + USER_UNIQUE_IPS_PREFIX = 'user_unique_ips' + + class << self + def limit_user_id!(user_id) + if config.unique_ips_limit_enabled + ip = RequestContext.client_ip + unique_ips = count_unique_ips(user_id, ip) + raise TooManyIps.new(user_id, ip, unique_ips) if unique_ips > config.unique_ips_limit_per_user + end + end + + def limit_user!(user = nil) + user = yield if user.nil? + limit_user_id!(user.id) unless user.nil? + user + end + + def config + Gitlab::CurrentSettings.current_application_settings + end + + def count_unique_ips(user_id, ip) + time = Time.now.to_i + key = "#{USER_UNIQUE_IPS_PREFIX}:#{user_id}" + + Gitlab::Redis.with do |redis| + unique_ips_count = nil + redis.multi do |r| + r.zadd(key, time, ip) + r.zremrangebyscore(key, 0, time - config.unique_ips_limit_time_window) + unique_ips_count = r.zcard(key) + end + unique_ips_count.value + end + end + end + + def initialize(app) + @app = app + end + + def call(env) + begin + @app.call(env) + rescue TooManyIps => ex + + Rails.logger.info ex.message + [429, {'Content-Type' => 'text/plain', 'Retry-After' => UniqueIpsLimiter.config.unique_ips_limit_time_window }, ["Retry later\n"]] + end + end + end + end +end diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb new file mode 100644 index 00000000000..5daf04dc92b --- /dev/null +++ b/lib/gitlab/request_context.rb @@ -0,0 +1,25 @@ +module Gitlab + class RequestStoreNotActive < StandardError + end + + class RequestContext + class << self + def client_ip + RequestStore[:client_ip] + end + end + + def initialize(app) + @app = app + end + + def call(env) + raise RequestStoreNotActive.new unless RequestStore.active? + req = Rack::Request.new(env) + + RequestStore[:client_ip] = req.ip + + @app.call(env) + end + end +end
\ No newline at end of file |