-
-
Notifications
You must be signed in to change notification settings - Fork 929
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reset push rate limit on successful gem push #2311
Conversation
We have rate limit with exp bacioff on endpoints with mfa to mitigate brute force of the otp code https://security.stackexchange.com/questions/185905/maximum-tries-for-2fa-code Ideally, we only need to throttle 401 unauthorized requests to mitigate this, however rack-attack doesn't have option of rate limit on response status. resetting rate limit cache on successful push is the closest alternative (smiliar [usecase](https://github.com/gitlabhq/gitlabhq/blob/4529c19950e412f0461910585414f8633d3b1b18/lib/gitlab/auth/ip_rate_limiter.rb#L17)). Exp rate limit was removed from push as we had used incorrect back off period and users were seeing unreasonable backoff time. This has since been fixed, max back off period is now of 11 days.
Not sure how hurry are you in here @sonalkr132, but alternativelly I can take a look if it would not be possible to contribute this to rack-test actually. Would that work in here? Rack::Attack.throttle("requests by ip", limit: 5, period: 2, status: 401) do |request|
request.ip
end |
app/models/pusher.rb
Outdated
@@ -105,6 +106,7 @@ def after_write | |||
Delayed::Job.enqueue Indexer.new, priority: PRIORITIES[:push] | |||
rubygem.delay.index_document | |||
GemCachePurger.call(rubygem.name) | |||
RackAttackReset.gem_push_backoff(@remote_ip) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do that only when @remote_ip.present?
?
This attack is only possible if you already know the api key or the password. Even after that success of the brute force (after continuous attempt for days) is very probabilistic. So, not urgent.
They have declined the request to limit on response in past rack/rack-attack#216 |
test/integration/rack_attack_test.rb
Outdated
should "reset gem push rate limit rack attack key" do | ||
Rack::Attack::EXP_BACKOFF_LEVELS.each do |level| | ||
push_exp_throttle_level_key = "#{Rack::Attack::PUSH_EXP_THROTTLE_KEY}/#{level}:#{@ip_address}" | ||
assert_equal 1, Rack::Attack.cache.count(push_exp_throttle_level_key, exp_base_limit_period**level) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling Rack::Attack.cache.count
increments the key, hence this asserts for 1, instead of 0. I couldn't get read cache to work.. something about key prefix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll take a look. 👀
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, you need to specify "time" part manually for read
this code works (and fails if I comment out reset in rack_attack_reset.rb)
should "reset gem push rate limit rack attack key" do
Rack::Attack::EXP_BACKOFF_LEVELS.each do |level|
push_exp_throttle_level_key = "#{Rack::Attack::PUSH_EXP_THROTTLE_KEY}/#{level}:#{@ip_address}"
period = exp_base_limit_period**level
key = "#{Time.now.to_i / period}:#{push_exp_throttle_level_key}"
assert_equal nil, Rack::Attack.cache.read(key)
end
end
got inspired at https://github.com/kickstarter/rack-attack/blob/580368faddcb563b55ad2ec4aa4cd05aae845547/spec/rack_attack_throttle_spec.rb#L72-L78
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks simi. Updated to this.
#{Time.now.to_i / period}
Doesn't this mean if period was much smaller 1-2 seconds, key could change between setup and assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can happen with any period actually. It should be safer to wrap those specs with freeze_time
. 👍
https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-freeze_time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea of the key changing between the time key is set (in rack_attack.rb) and reset (in pusher.rb) did cross my mind but I assumed their reset_count
implementation must be correct. Looking at this more closing after your comment, I don't think it is. Consider following:
# time during `count` in rack_attack.rb
> Time.now.to_i
=> 1587326999
> Time.now.to_i / 100
=> 1587326
# churn code, query pg, upload to s3. time during `reset_count` in pusher.rb
> Time.now.to_i
=> 1587327001
> Time.now.to_i / 100
=> 1587327
To reiterate, key_and_expiry
gets called from both count
and reset_count
but there is no grantee that Time.now
will be the same.
I suppose similar to their workaround for key expiry rack/rack-attack#85 we should purge be purging two keys (rack::attack:1587326:..
and rack::attack:1587327:..
).
also adds tests for throttle condition
@simi updated this to reset two keys. check last commit message or this comment |
8b4b985
to
c8e7a0a
Compare
we need to delete two keys to ensure push rate limit is reset. time_counter (`(Time.now.to_i / period).to_i`) used in rack attack key can change between the time the key was set and the time when we reset/delete it. time during `count` in rack_attack.rb > Time.now.to_i => 1587326999 > Time.now.to_i / 100 => 1587326 churn code, query pg, upload to s3. time during `gem_push_backoff` in pusher.rb > Time.now.to_i => 1587327001 > Time.now.to_i / 100 => 1587327
c8e7a0a
to
c4e06aa
Compare
tests Fixes: NameError: undefined local variable or method `expected_retry_after' for #<RackAttackTest:0x000000000c99e1f0> https://travis-ci.org/github/rubygems/rubygems.org/jobs/690929419 test failing in the build was added in rubygems#2311
We have rate limit with exp bacioff on endpoints with mfa to mitigate brute force of the otp code
https://security.stackexchange.com/questions/185905/maximum-tries-for-2fa-code
Check #2078 for more info on the attack.
Ideally, we only need to throttle 401 unauthorized requests to mitigate this, however rack-attack doesn't have option of rate limit on response status. resetting rate limit cache on successful push is the closest alternative (smiliar usecase).
Exp rate limit was removed from push as we had used incorrect back off period and users were seeing unreasonable backoff time. This has since been fixed, max back off period is now of 11 days.