From 62c328e46d4ef5831a557dcc1943a8055af60bb2 Mon Sep 17 00:00:00 2001
From: Kent Japhet Ballon <kballon@gitlab.com>
Date: Thu, 28 Jul 2022 17:23:01 +0000
Subject: [PATCH] Add auditor access for group runners

Allows the auditor user to access the CI/CD runners within a group.

Changelog: fixed
EE: true
---
 app/policies/ci/runner_policy.rb              |  2 +
 ee/app/policies/ee/ci/runner_policy.rb        | 27 +++++++
 ee/app/policies/ee/group_policy.rb            |  8 ++
 .../auditor_group_runner_access.yml           |  8 ++
 ee/spec/policies/ee/ci/runner_policy_spec.rb  | 81 +++++++++++++++++++
 ee/spec/policies/group_policy_spec.rb         | 29 ++++++-
 6 files changed, 154 insertions(+), 1 deletion(-)
 create mode 100644 ee/app/policies/ee/ci/runner_policy.rb
 create mode 100644 ee/config/feature_flags/development/auditor_group_runner_access.yml
 create mode 100644 ee/spec/policies/ee/ci/runner_policy_spec.rb

diff --git a/app/policies/ci/runner_policy.rb b/app/policies/ci/runner_policy.rb
index 6dfe9cc496bf9..8a99f4d1a3e04 100644
--- a/app/policies/ci/runner_policy.rb
+++ b/app/policies/ci/runner_policy.rb
@@ -31,3 +31,5 @@ class RunnerPolicy < BasePolicy
     rule { ~admin & locked }.prevent :assign_runner
   end
 end
+
+Ci::RunnerPolicy.prepend_mod_with('Ci::RunnerPolicy')
diff --git a/ee/app/policies/ee/ci/runner_policy.rb b/ee/app/policies/ee/ci/runner_policy.rb
new file mode 100644
index 0000000000000..014bf5d04a19b
--- /dev/null
+++ b/ee/app/policies/ee/ci/runner_policy.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module EE
+  module Ci
+    module RunnerPolicy
+      extend ActiveSupport::Concern
+
+      prepended do
+        condition(:is_group_runner) do
+          @subject.group_type?
+        end
+
+        condition(:is_project_runner) do
+          @subject.project_type?
+        end
+
+        condition(:enable_auditor_group_runner_access) do
+          ::Feature.enabled?(:auditor_group_runner_access)
+        end
+
+        rule { enable_auditor_group_runner_access & auditor & (is_group_runner | is_project_runner) }.policy do
+          enable :read_runner
+        end
+      end
+    end
+  end
+end
diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb
index ec9198bf1715e..c4fe0471c6062 100644
--- a/ee/app/policies/ee/group_policy.rb
+++ b/ee/app/policies/ee/group_policy.rb
@@ -165,6 +165,10 @@ module GroupPolicy
         @user.banned_from_namespace?(root_namespace)
       end
 
+      condition(:enable_auditor_group_runner_access) do
+        ::Feature.enabled?(:auditor_group_runner_access)
+      end
+
       rule { ~public_group & ~can?(:owner_access) & user_banned_from_group }.policy do
         prevent :read_group
       end
@@ -207,6 +211,10 @@ module GroupPolicy
         enable :read_wiki
       end
 
+      rule { enable_auditor_group_runner_access & auditor }.policy do
+        enable :read_group_runners
+      end
+
       rule { group_level_compliance_dashboard_enabled & auditor }.policy do
         enable :read_group_compliance_dashboard
       end
diff --git a/ee/config/feature_flags/development/auditor_group_runner_access.yml b/ee/config/feature_flags/development/auditor_group_runner_access.yml
new file mode 100644
index 0000000000000..77db21aad3708
--- /dev/null
+++ b/ee/config/feature_flags/development/auditor_group_runner_access.yml
@@ -0,0 +1,8 @@
+---
+name: auditor_group_runner_access
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91553
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368089
+milestone: '15.3'
+type: development
+group: group::runner
+default_enabled: false
diff --git a/ee/spec/policies/ee/ci/runner_policy_spec.rb b/ee/spec/policies/ee/ci/runner_policy_spec.rb
new file mode 100644
index 0000000000000..2cd539523dae6
--- /dev/null
+++ b/ee/spec/policies/ee/ci/runner_policy_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::RunnerPolicy do
+  describe 'cicd runners' do
+    let_it_be(:group) { create(:group) }
+    let_it_be(:project) { create(:project, group: group) }
+
+    subject(:policy) { described_class.new(user, runner) }
+
+    context 'with auditor access' do
+      let_it_be(:user) { create(:auditor) }
+      let_it_be(:instance_runner) { create(:ci_runner, :instance) }
+      let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+      let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project]) }
+
+      context 'when auditor_group_runner_access FF disabled' do
+        before do
+          stub_feature_flags(auditor_group_runner_access: false)
+        end
+
+        context 'with instance runner' do
+          let(:runner) { instance_runner }
+
+          it 'disallows all permissions' do
+            expect_disallowed :read_runner, :assign_runner, :update_runner, :delete_runner
+          end
+        end
+
+        context 'with group runner' do
+          let(:runner) { group_runner }
+
+          it 'disallows all permissions' do
+            expect_disallowed :read_runner, :assign_runner, :update_runner, :delete_runner
+          end
+        end
+
+        context 'with project runner' do
+          let(:runner) { project_runner }
+
+          it 'disallows all permissions' do
+            expect_disallowed :read_runner, :assign_runner, :update_runner, :delete_runner
+          end
+        end
+      end
+
+      context 'when auditor_group_runner_access FF enabled' do
+        before do
+          stub_feature_flags(auditor_group_runner_access: true)
+        end
+
+        context 'with instance runner' do
+          let(:runner) { instance_runner }
+
+          it 'disallows all permissions' do
+            expect_disallowed :read_runner, :assign_runner, :update_runner, :delete_runner
+          end
+        end
+
+        context 'with group runner' do
+          let(:runner) { group_runner }
+
+          it 'allows only read permissions' do
+            expect_allowed :read_runner
+            expect_disallowed :assign_runner, :update_runner, :delete_runner
+          end
+        end
+
+        context 'with project runner' do
+          let(:runner) { project_runner }
+
+          it 'allows only read permissions' do
+            expect_allowed :read_runner
+            expect_disallowed :assign_runner, :update_runner, :delete_runner
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb
index af1baec9905cb..996427480d1a2 100644
--- a/ee/spec/policies/group_policy_spec.rb
+++ b/ee/spec/policies/group_policy_spec.rb
@@ -21,6 +21,7 @@
       read_group
       read_group_security_dashboard
       read_cluster
+      read_group_runners
     ]
   end
 
@@ -1244,7 +1245,7 @@ def stub_group_saml_config(enabled)
         expect_disallowed(*reporter_permissions)
         expect_disallowed(*(developer_permissions - auditor_permissions))
         expect_disallowed(*maintainer_permissions)
-        expect_disallowed(*owner_permissions)
+        expect_disallowed(*(owner_permissions - auditor_permissions))
       end
     end
   end
@@ -2259,4 +2260,30 @@ def expect_private_group_permissions_as_if_non_member
       end
     end
   end
+
+  describe 'group cicd runners' do
+    context 'auditor' do
+      let(:current_user) { auditor }
+
+      context 'with auditor_group_runner_access FF disabled' do
+        before do
+          stub_feature_flags(auditor_group_runner_access: false)
+        end
+
+        it { is_expected.to be_disallowed(:read_group_runners) }
+        it { is_expected.to be_disallowed(:admin_group_runners) }
+        it { is_expected.to be_disallowed(:register_group_runners) }
+      end
+
+      context 'with auditor_group_runner_access FF enabled' do
+        before do
+          stub_feature_flags(auditor_group_runner_access: true)
+        end
+
+        it { is_expected.to be_allowed(:read_group_runners) }
+        it { is_expected.to be_disallowed(:admin_group_runners) }
+        it { is_expected.to be_disallowed(:register_group_runners) }
+      end
+    end
+  end
 end
-- 
GitLab