diff --git a/db/migrate/20240101031938_add_admin_terraform_state_to_member_roles.rb b/db/migrate/20240101031938_add_admin_terraform_state_to_member_roles.rb new file mode 100644 index 0000000000000000000000000000000000000000..89222664d014066b8b504c3ffcb350b0fd38d111 --- /dev/null +++ b/db/migrate/20240101031938_add_admin_terraform_state_to_member_roles.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddAdminTerraformStateToMemberRoles < Gitlab::Database::Migration[2.2] + milestone '16.8' + enable_lock_retries! + + def change + add_column :member_roles, :admin_terraform_state, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20240101031938 b/db/schema_migrations/20240101031938 new file mode 100644 index 0000000000000000000000000000000000000000..5b9395a568f8eec12442eaf3e69c6bab878dc2e2 --- /dev/null +++ b/db/schema_migrations/20240101031938 @@ -0,0 +1 @@ +d0cb92dc098f069e02d457f7c497dc24f544f6a27a8426dcd3446ad16bd9cc44 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c74d4b69bafe64d0fd8553e801587647bdc37046..0a048082a9351b3cced303b06dff08fe36ab4c0b 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18948,6 +18948,7 @@ CREATE TABLE member_roles ( archive_project boolean DEFAULT false NOT NULL, manage_group_access_tokens boolean DEFAULT false NOT NULL, remove_project boolean DEFAULT false NOT NULL, + admin_terraform_state boolean DEFAULT false NOT NULL, CONSTRAINT check_4364846f58 CHECK ((char_length(description) <= 255)), CONSTRAINT check_9907916995 CHECK ((char_length(name) <= 255)) ); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index ee2f526f7f3ab05eeba3dca89e0fc0d5dedafb16..fed954b7199a76de35349f86e57cfe84ab9478c4 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -30859,6 +30859,7 @@ Member role permission. | ----- | ----------- | | <a id="memberrolepermissionadmin_group_member"></a>`ADMIN_GROUP_MEMBER` | Allows to admin group members. | | <a id="memberrolepermissionadmin_merge_request"></a>`ADMIN_MERGE_REQUEST` | Allows to approve merge requests. | +| <a id="memberrolepermissionadmin_terraform_state"></a>`ADMIN_TERRAFORM_STATE` | Allows to admin terraform state. | | <a id="memberrolepermissionadmin_vulnerability"></a>`ADMIN_VULNERABILITY` | Allows admin access to the vulnerability reports. | | <a id="memberrolepermissionarchive_project"></a>`ARCHIVE_PROJECT` | Allows to archive projects. | | <a id="memberrolepermissionmanage_group_access_tokens"></a>`MANAGE_GROUP_ACCESS_TOKENS` | Allows manage access to the group access tokens. | diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index 2fd10d99fda89dda472645f32500b3913bbca8e7..2bfbc29081fa1fae07b0693b19ac748bc5f22a5b 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -19,6 +19,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Archive project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) in GitLab 16.7. > - [Delete project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139696) in GitLab 16.8. > - [Manage group access tokens introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115) in GitLab 16.8. +> - [Admin terraform state introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140759) in GitLab 16.8. FLAG: On self-managed GitLab, by default these features are not available. To make them available, an administrator can [enable the feature flags](../administration/feature_flags.md) named `admin_group_member` and `manage_project_access_tokens`. @@ -46,6 +47,7 @@ If successful, returns [`200`](rest/index.md#status-codes) and the following res | `[].group_id` | integer | The ID of the group that the member role belongs to. | | `[].base_access_level` | integer | Base access level for member role. Valid values are 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), or 50 (Owner).| | `[].admin_merge_request` | boolean | Permission to admin project merge requests and enables the ability to `download_code`. | +| `[].admin_terraform_state` | boolean | Permission to admin project terraform state. | | `[].admin_vulnerability` | boolean | Permission to admin project vulnerabilities. | | `[].read_code` | boolean | Permission to read project code. | | `[].read_dependency` | boolean | Permission to read project dependencies. | @@ -73,6 +75,7 @@ Example response: "group_id": 84, "base_access_level": 10, "admin_merge_request": false, + "admin_terraform_state": false, "admin_vulnerability": false, "read_code": true, "read_dependency": false, @@ -88,8 +91,9 @@ Example response: "description: "Custom guest that read and admin security entities", "group_id": 84, "base_access_level": 10, - "admin_merge_request": false, "admin_vulnerability": true, + "admin_merge_request": false, + "admin_terraform_state": false, "read_code": false, "read_dependency": true, "read_vulnerability": true, @@ -120,6 +124,7 @@ To add a member role to a group, the group must be at root-level (have no parent | `description` | string | no | The description of the member role. | | `base_access_level` | integer | yes | Base access level for configured role. Valid values are 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), or 50 (Owner).| | `admin_merge_request` | boolean | no | Permission to admin project merge requests. | +| `admin_terraform_state` | boolean | no | Permission to admin project terraform state. | | `admin_vulnerability` | boolean | no | Permission to admin project vulnerabilities. | | `read_code` | boolean | no | Permission to read project code. | | `read_dependency` | boolean | no | Permission to read project dependencies. | @@ -135,6 +140,7 @@ If successful, returns [`201`](rest/index.md#status-codes) and the following att | `group_id` | integer | The ID of the group that the member role belongs to. | | `base_access_level` | integer | Base access level for member role. | | `admin_merge_request` | boolean | Permission to admin project merge requests. | +| `admin_terraform_state` | boolean | Permission to admin project terraform state. | | `admin_vulnerability` | boolean | Permission to admin project vulnerabilities. | | `read_code` | boolean | Permission to read project code. | | `read_dependency` | boolean | Permission to read project dependencies. | diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index 86689a19adb96d70a57b3698fe9850566842966a..36ddf81df6561265074d5d4ee71b4e4f8ca40c20 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -257,6 +257,15 @@ module ProjectPolicy ).has_ability? end + desc "Custom role on project that enables admin terraform state" + condition(:role_enables_admin_terraform_state) do + ::Auth::MemberRoleAbilityLoader.new( + user: @user, + resource: @subject, + ability: :admin_terraform_state + ).has_ability? + end + desc "Custom role on project that enables admin vulnerability" condition(:role_enables_admin_vulnerability) do ::Auth::MemberRoleAbilityLoader.new( @@ -769,6 +778,11 @@ module ProjectPolicy enable :download_code # required to negate https://gitlab.com/gitlab-org/gitlab/-/blob/3061d30d9b3d6d4c4dd5abe68bc1e4a8a93c7966/app/policies/project_policy.rb#L603-607 end + rule { custom_roles_allowed & role_enables_admin_terraform_state }.policy do + enable :read_terraform_state + enable :admin_terraform_state + end + rule { custom_roles_allowed & role_enables_admin_vulnerability }.policy do enable :admin_vulnerability end diff --git a/ee/config/custom_abilities/admin_terraform_state.yml b/ee/config/custom_abilities/admin_terraform_state.yml new file mode 100644 index 0000000000000000000000000000000000000000..1408c5c6a5230adae1bceeeb204d4bbeb3dce753 --- /dev/null +++ b/ee/config/custom_abilities/admin_terraform_state.yml @@ -0,0 +1,10 @@ +--- +name: admin_terraform_state +description: Allows to admin terraform state +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/421789 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140759 +feature_category: infrastructure_as_code +milestone: '16.8' +group_ability: false +project_ability: true +requirement: '' diff --git a/ee/spec/factories/member_roles.rb b/ee/spec/factories/member_roles.rb index 42abbfe88c44c88a5f01b9f6a10407dcdcacdb10..cfe42c09a070f0e3b8ca6a3bdd269e1da68bbee2 100644 --- a/ee/spec/factories/member_roles.rb +++ b/ee/spec/factories/member_roles.rb @@ -18,6 +18,10 @@ read_vulnerability { true } end + trait :admin_terraform_state do + admin_terraform_state { true } + end + # this trait can be used only for self-managed trait(:instance) { namespace { nil } } end diff --git a/ee/spec/lib/ee/api/entities/member_role_spec.rb b/ee/spec/lib/ee/api/entities/member_role_spec.rb index 8bdc4f85897ad3c5b7dbf09953c831f3e40cedbb..eb773e1f3218a0def4b5fc20f7fa29fb4c036276 100644 --- a/ee/spec/lib/ee/api/entities/member_role_spec.rb +++ b/ee/spec/lib/ee/api/entities/member_role_spec.rb @@ -19,6 +19,7 @@ expect(subject[:base_access_level]).to eq member_role.base_access_level expect(subject[:read_code]).to eq member_role.read_code expect(subject[:read_vulnerability]).to eq member_role.read_vulnerability + expect(subject[:admin_terraform_state]).to eq member_role.admin_terraform_state expect(subject[:admin_vulnerability]).to eq member_role.admin_vulnerability expect(subject[:manage_group_access_tokens]).to eq member_role.manage_group_access_tokens expect(subject[:manage_project_access_tokens]).to eq member_role.manage_project_access_tokens diff --git a/ee/spec/models/ee/user_spec.rb b/ee/spec/models/ee/user_spec.rb index 6fd4f88ca23d9b6c77930a044a782cff6806a419..4464aa2101836aae789826baff1adcb945e153ec 100644 --- a/ee/spec/models/ee/user_spec.rb +++ b/ee/spec/models/ee/user_spec.rb @@ -1222,6 +1222,7 @@ OR "members"."access_level" = 10 AND \(admin_group_member = true OR admin_merge_request = true + OR admin_terraform_state = true OR admin_vulnerability = true OR archive_project = true OR manage_group_access_tokens = true diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index 9caa4cd833297c0c0811466686f116529aefb65f..1acbdd393da7c67fc2caef71300576db3516cb1e 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -2700,6 +2700,13 @@ def create_member_role(member, abilities = member_role_abilities) end end + context 'for a member role with admin_terraform_state true' do + let(:member_role_abilities) { { admin_terraform_state: true } } + let(:allowed_abilities) { [:read_terraform_state, :admin_terraform_state] } + + it_behaves_like 'custom roles abilities' + end + context 'for a member role with admin_vulnerability true' do let(:member_role_abilities) { { read_vulnerability: true, admin_vulnerability: true } } let(:allowed_abilities) do diff --git a/ee/spec/requests/api/member_roles_spec.rb b/ee/spec/requests/api/member_roles_spec.rb index cacfa8f9b5e1b0157cd9c2107cbe249fe3de3096..f4affed98450526be3f6c0903ed7f7254056a688 100644 --- a/ee/spec/requests/api/member_roles_spec.rb +++ b/ee/spec/requests/api/member_roles_spec.rb @@ -104,6 +104,7 @@ "read_vulnerability" => true, "admin_group_member" => false, "admin_merge_request" => false, + "admin_terraform_state" => false, "admin_vulnerability" => false, "manage_group_access_tokens" => false, "manage_project_access_tokens" => false, @@ -121,6 +122,7 @@ "read_vulnerability" => false, "admin_group_member" => false, "admin_merge_request" => true, + "admin_terraform_state" => false, "admin_vulnerability" => false, "manage_group_access_tokens" => false, "manage_project_access_tokens" => false, diff --git a/ee/spec/requests/custom_roles/admin_terraform_state/request_spec.rb b/ee/spec/requests/custom_roles/admin_terraform_state/request_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9b57adceb484c2277f1821f0cbb308c1baddb4a4 --- /dev/null +++ b/ee/spec/requests/custom_roles/admin_terraform_state/request_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User with admin_terraform_state custom role', feature_category: :permissions do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :in_group) } + + let_it_be(:role) { create(:member_role, :guest, namespace: project.group, admin_terraform_state: true) } + let_it_be(:member) { create(:project_member, :guest, member_role: role, user: user, project: project) } + + before do + stub_licensed_features(custom_roles: true) + + sign_in(user) + end + + describe Projects::TerraformController do + describe '#index' do + it 'user has access via a custom role' do + get project_terraform_index_path(project) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + describe Mutations::Terraform::State do + include GraphqlHelpers + + before do + post_graphql_mutation(mutation, current_user: user) + end + + context 'when locking a terraform state' do + let(:state) { create(:terraform_state, project: project) } + let(:mutation) { graphql_mutation(:terraform_state_lock, id: state.to_global_id.to_s) } + + it_behaves_like 'a working graphql query' + end + + context 'when unlocking a terraform state' do + let(:state) { create(:terraform_state, :locked, project: project) } + let(:mutation) { graphql_mutation(:terraform_state_unlock, id: state.to_global_id.to_s) } + + it_behaves_like 'a working graphql query' + end + + context 'when deleting a terraform state' do + let(:state) { create(:terraform_state, project: project) } + let(:mutation) { graphql_mutation(:terraform_state_delete, id: state.to_global_id.to_s) } + + it_behaves_like 'a working graphql query' + end + end +end