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 ed956576efde344341bbc3946607a291fe2a42e1..1da78dd3aff7e0f7005eca330a67ca218b7c2553 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18649,6 +18649,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 a7e6409b8aa3751446b6d20a21f4ee0b47df3637..6f9bb9bf7df54aa6fa6044bff1b25e938c6eced0 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -30860,6 +30860,7 @@ Member role permission.
| ----- | ----------- |
| `ADMIN_GROUP_MEMBER` | Allows to admin group members. |
| `ADMIN_MERGE_REQUEST` | Allows to approve merge requests. |
+| `ADMIN_TERRAFORM_STATE` | Allows to admin terraform state. |
| `ADMIN_VULNERABILITY` | Allows admin access to the vulnerability reports. |
| `ARCHIVE_PROJECT` | Allows to archive projects. |
| `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 756fb04fc9fcb2616ade7ffdb22a42cdf80ff125..d20a38cec041dc5cbe151d93b7a78d4b79951bd8 100644
--- a/ee/app/policies/ee/project_policy.rb
+++ b/ee/app/policies/ee/project_policy.rb
@@ -252,6 +252,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(
@@ -764,6 +773,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 001a295da47d952ccb3c511f48aee9848f0c1b76..48237af91d108a05f7b94b3a8ad682085e449aea 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 36a7ce13b413a98b5e5c3f873d21d57744c9e580..987b65f98a16dba4168f48e1cd6057fc620f5033 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