Protected terraform states: REST API for protection rules

Problem Statement

As part of the Protected Terraform States epic, we are adding protection rules that control who can write to a Terraform state and from where.

This issue covers the REST API for querying and managing Terraform state protection rules. It complements the GraphQL API tracked in #594002 (closed) and provides parity for users and tooling (CLI, scripts, Terraform provider) that consume the REST API.

Proposal

Add CRUD endpoints under /projects/:id/terraform/state_protection_rules for listing, creating, updating, and deleting Terraform state protection rules.

All endpoints are scoped to the project and require maintainer-level permissions.

List protection rules

GET /api/v4/projects/:id/terraform/state_protection_rules

Returns all protection rules for the project.

Example response:

[
  {
    "id": 1,
    "project_id": 42,
    "state_name": "production",
    "minimum_access_level_for_write": "maintainer",
    "allowed_from": "ci_on_protected_branch_only"
  }
]

Create a protection rule

POST /api/v4/projects/:id/terraform/state_protection_rules
Attribute Type Required Description
state_name string yes Terraform state to protect. Must be unique per project.
minimum_access_level_for_write string yes Minimum role for write operations. One of: developer, maintainer, owner, admin.
allowed_from string no Source restriction. One of: anywhere (default), ci_only, ci_on_protected_branch_only.

Returns 201 Created with the created rule, or 422 Unprocessable Entity when a rule for the same state_name already exists, or 400 Bad Request on invalid enum values.

Update a protection rule

PATCH /api/v4/projects/:id/terraform/state_protection_rules/:terraform_state_protection_rule_id
Attribute Type Required Description
minimum_access_level_for_write string no Minimum role for write operations.
allowed_from string no Source restriction.

Supports partial updates — only provided attributes are changed. state_name is immutable once the rule is created.

Delete a protection rule

DELETE /api/v4/projects/:id/terraform/state_protection_rules/:terraform_state_protection_rule_id

Returns 204 No Content on success.

Authorization

  • All endpoints require maintainer-level access (admin_terraform_state) on the project.
  • Developer and below receive 403 Forbidden.
  • Unauthenticated requests receive 401 Unauthorized.
  • Follows the same admin_terraform_state permission as existing Terraform state endpoints.

Feature flag

Gated behind terraform_state_protection_rules (gitlab_com_derisk). When the flag is disabled for the project, all endpoints return 404 Not Found.

Scope

In scope:

  • GET endpoint to list protection rules for a project
  • POST endpoint to create a rule (required: state_name, minimum_access_level_for_write; optional: allowed_from)
  • PATCH endpoint for partial updates of an existing rule
  • DELETE endpoint to remove a rule
  • Grape entity exposing: id, project_id, state_name, minimum_access_level_for_write, allowed_from
  • Request specs for all endpoints (authorized, unauthorized, invalid token, feature flag disabled)
  • OpenAPI documentation generated from the Grape desc blocks

Out of scope:

  • GraphQL API for rules CRUD (see #594002 (closed))
  • Frontend UI (separate issue)
  • Internal events / instrumentation (separate issue)

Reference

This follows the packages protection rules REST API pattern.

Reference files in the codebase:

  • lib/api/project_packages_protection_rules.rb
  • lib/api/entities/projects/packages/protection/rule.rb
  • spec/requests/api/project_packages_protection_rules_spec.rb
  • spec/support/shared_examples/requests/api/protection_rules_shared_examples.rb

Implementation checklist

  • Grape entity API::Entities::Terraform::StateProtectionRule
  • API class API::Terraform::StateProtectionRules mounted in lib/api/api.rb
  • GET endpoint (list)
  • POST endpoint (create — reuses CreateRuleService from the GraphQL API)
  • PATCH endpoint (update — reuses UpdateRuleService from the GraphQL API)
  • DELETE endpoint (delete — reuses DeleteRuleService from the GraphQL API)
  • Request specs for all endpoints (authorized, unauthorized, invalid token, feature flag disabled)
  • Update OpenAPI docs (bundle exec rake gitlab:openapi:generate)
Edited by 🤖 GitLab Bot 🤖