summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStan Hu <stanhu@gmail.com>2017-12-09 01:01:42 -0800
committerStan Hu <stanhu@gmail.com>2017-12-12 15:07:25 -0800
commit54f13b1ec8542dc5085e0367734e8344c2c3d01e (patch)
treeb5557f077e3d1d13e7148a5eaba682b9000153ca
parentf8c3a58a54d622193a0cf15777a0d0631289278c (diff)
downloadgitlab-ce-54f13b1ec8542dc5085e0367734e8344c2c3d01e.tar.gz
Add rate limiting to guard against excessive scheduling of pipelines
-rw-r--r--app/controllers/projects/pipeline_schedules_controller.rb11
-rw-r--r--lib/gitlab/action_rate_limiter.rb31
-rw-r--r--spec/controllers/projects/pipeline_schedules_controller_spec.rb2
3 files changed, 43 insertions, 1 deletions
diff --git a/app/controllers/projects/pipeline_schedules_controller.rb b/app/controllers/projects/pipeline_schedules_controller.rb
index fe77d8eabeb..b7a0a3591cd 100644
--- a/app/controllers/projects/pipeline_schedules_controller.rb
+++ b/app/controllers/projects/pipeline_schedules_controller.rb
@@ -42,6 +42,13 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
end
def play
+ limiter = ::Gitlab::ActionRateLimiter.new(action: 'play_pipeline_schedule')
+
+ if limiter.throttled?(throttle_key, 1)
+ flash[:notice] = 'You cannot play this scheduled pipeline at the moment. Please wait a minute.'
+ return redirect_to pipeline_schedules_path(@project)
+ end
+
job_id = RunPipelineScheduleWorker.perform_async(schedule.id, current_user.id)
flash[:notice] =
@@ -74,6 +81,10 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
private
+ def throttle_key
+ "user:#{current_user.id}:schedule:#{schedule.id}"
+ end
+
def schedule
@schedule ||= project.pipeline_schedules.find(params[:id])
end
diff --git a/lib/gitlab/action_rate_limiter.rb b/lib/gitlab/action_rate_limiter.rb
new file mode 100644
index 00000000000..c3af583a3ed
--- /dev/null
+++ b/lib/gitlab/action_rate_limiter.rb
@@ -0,0 +1,31 @@
+module Gitlab
+ # This class implements a simple rate limiter that can be used to throttle
+ # certain actions. Unlike Rack Attack and Rack::Throttle, which operate at
+ # the middleware level, this can be used at the controller level.
+ class ActionRateLimiter
+ TIME_TO_EXPIRE = 60 # 1 min
+
+ attr_accessor :action, :expiry_time
+
+ def initialize(action:, expiry_time: TIME_TO_EXPIRE)
+ @action = action
+ @expiry_time = expiry_time
+ end
+
+ def increment(key)
+ value = 0
+
+ Gitlab::Redis::Cache.with do |redis|
+ cache_key = "action_rate_limiter:#{action}:#{key}"
+ value = redis.incr(cache_key)
+ redis.expire(cache_key, expiry_time) if value == 1
+ end
+
+ value.to_i
+ end
+
+ def throttled?(key, threshold_value)
+ self.increment(key) > threshold_value
+ end
+ end
+end
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index 8888573e882..844c62ef005 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -366,7 +366,7 @@ describe Projects::PipelineSchedulesController do
end
end
- describe 'POST #play' do
+ describe 'POST #play', :clean_gitlab_redis_cache do
set(:user) { create(:user) }
let(:ref) { 'master' }