Add the policy store create endpoint

What does this MR do and why?

Adds POST /organizations/278964/security/policy_store to API::Govern::Policies, gated by create_govern_policy for organization owners and instance admins. CreateService already existed, so this contributes the route, its parameters, the granular token permission group entry, and the specs.

Each rules and actions entry declares its shape, so type is checked against Rules::ALL and Actions::ALL the way trigger_type is checked against Triggers::ALL. A rule's value takes a String or a Hash, since a custom rule carries Rego source while calendar and environment rules carry a configuration hash. An action's value takes only a Hash, since neither block nor require_approval has a string form. mode and lifecycle_state validate against MODES and LIFECYCLE_STATES on Ports::PolicyRepository, which match the Govern::Policy enums already on master, so the vocabulary survives the move to the ActiveRecord-backed store.

rules is declared with allow_blank: false rather than a hand-written params[:rules] check. Grape's presence validator only checks that a key is present, so a JSON null would reach the route body as nil and raise NoMethodError, which handle_api_exception reports as a 500. An empty array is rejected on the same grounds: a policy with no rules can never match anything.

name, description, and scope_rego carry a limit: read from TEXT_LIMITS, which doc/development/api_styleguide.md asks for so a client cannot send an unbounded payload before anything validates it. Neither rules nor actions carries a count bound today. TEXT_LIMITS on Gitlab::PolicyStore::Ports::PolicyRepository covers only name, description, and scope_rego, and compiled_scope_rego builds the compiled scope program from policy_scope and name alone, so neither rules nor actions ever reaches that cap. A count bound belongs next to TEXT_LIMITS in the repository rather than at one endpoint, since another caller of CreateService (a future GraphQL mutation, a rake task) would walk straight past an endpoint-only check. Tracked in https://gitlab.com/gitlab-org/gitlab/-/work_items/612905.

doc/api/openapi/openapi_v3.yaml does not change because the route is hidden true. doc/auth/tokens/fine_grained_access_tokens_rest.md gains one row, regenerated with bundle exec rake gitlab:permissions:routes:compile_docs.

How to set up and validate locally

Requires an Ultimate licence. The policy store is in-memory per process, so a POST mutates only the worker that served it.

  1. Reduce Puma to one worker, since GDK runs 2 by default. Run only these commands now, because step 7 puts the count back
gdk config set gitlab.rails.puma.workers 1
gdk reconfigure
gdk restart rails-web

pgrep -f 'puma: cluster worker' | wc -l   # => 1

Counting the running processes is the check that works, since gdk config get only reads back gdk.yml.

  1. Enable the security_policies_v2 feature flag on the rails console
Feature.enable(:security_policies_v2)
  1. As an administrator, go to Admin > Settings > Security and compliance, expand Security policies, select the Allow Policy Store experiment for groups checkbox, then select Save changes. The label says "groups" even though these routes are organization-scoped.
  2. On the rails console, confirm both gates and note the base URL
organization = Organizations::Organization.find(Organizations::Organization::DEFAULT_ORGANIZATION_ID)
user = User.find_by_username('root')

organization.policy_store_experiment_active?                 # => true
Ability.allowed?(user, :create_govern_policy, organization)  # => true

puts Gitlab.config.gitlab.url   # for example https://gdk.test:3443
  1. Create a token for root, on the console already open from step 4
puts user.personal_access_tokens.create!(name: 'policy store check', scopes: [:api], expires_at: 30.days.from_now).token
  1. Create a policy. --write-out is what makes the status visible, since the body alone does not show it. The name has to be one no policy in the organization already uses, because the store rejects a duplicate with 400 {"message":"Name has already been taken"}
curl --request POST --header "PRIVATE-TOKEN: <your token>" \
  --data "name=Block deployments on critical findings" \
  --data "trigger_type=deployment_requested" \
  --data "rules[][type]=custom" \
  --data "rules[][value]=package governance" \
  --write-out '\nHTTP %{http_code}\n' \
  --url "<your GDK URL>/api/v4/organizations/1/security/policy_store"

Verify HTTP 201 and that the returned policy carries the name, trigger_type and rule that were sent, plus a compiled scope_rego.

  1. Restore the worker count, since gdk config has no unset
gdk config set gitlab.rails.puma.workers 2
gdk reconfigure
gdk restart rails-web

References

Edited by Marcos Rocha

Merge request reports

Loading
Loading