diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 92006cb331c6a4cad3134703da5f5233f06869c9..de410be7f14162a0319612a19e1a4d561eb7f354 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -1,5 +1,7 @@
 module Ci
   class RetryBuildService
+    include Gitlab::Allowable
+
     def initialize(build, user)
       @build = build
       @user = user
@@ -7,6 +9,10 @@ module Ci
     end
 
     def retry!
+      unless can?(@user, :update_build, @build)
+        raise Gitlab::Access::AccessDeniedError
+      end
+
       clone_build.tap do |new_build|
         new_build.enqueue!
 
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3c4acc1373b07dd6b4893298ff17a22fe647c07b
--- /dev/null
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe Ci::RetryBuildService, :services do
+  let(:user) { create(:user) }
+  let(:project) { create(:empty_project) }
+  let(:pipeline) { create(:ci_pipeline, project: project) }
+  let(:build) { create(:ci_build, pipeline: pipeline) }
+
+  let(:service) do
+    described_class.new(build, user)
+  end
+
+  describe '#retry!' do
+    let(:new_build) { service.retry! }
+
+    context 'when user has ability to retry build' do
+      before do
+        project.team << [user, :developer]
+      end
+
+      it 'creates a new build that represents the old one' do
+        expect(new_build.name).to eq build.name
+      end
+
+      it 'enqueues the new build' do
+        expect(new_build).to be_pending
+      end
+
+      it 'resolves todos for old build that failed' do
+       expect(MergeRequests::AddTodoWhenBuildFailsService)
+          .to receive_message_chain(:new, :close)
+
+        service.retry!
+      end
+
+      context 'when there are subsequent builds that are skipped' do
+        let!(:subsequent_build) do
+          create(:ci_build, :skipped, stage_idx: 1, pipeline: pipeline)
+        end
+
+        it 'resumes pipeline processing in subsequent stages' do
+          service.retry!
+
+          expect(subsequent_build.reload).to be_created
+        end
+      end
+    end
+
+    context 'when user does not have ability to retry build' do
+      it 'raises an error' do
+        expect { service.retry! }
+          .to raise_error Gitlab::Access::AccessDeniedError
+      end
+    end
+  end
+end