Add group REST CRUD endpoints for policy store

What does this MR do and why?

Adds group REST CRUD endpoints for the policy store, allowing group owners to manage security policies scoped to their group without requiring organization-level access.

This is MR 2 of 4 splitting the group-scoped policy store access work from !252678 (closed).

Depends on: !253003 (merged) (MR 1: Container-based authorization substrate) - now merged ✅

Changes

API endpoints (ee/lib/api/govern/policies.rb):

  • GET /groups/:id/security/policy_store - List policies scoped to a group
  • GET /groups/:id/security/policy_store/:policy_id - Get a single policy
  • POST /groups/:id/security/policy_store - Create a policy scoped to a group
  • PATCH /groups/:id/security/policy_store/:policy_id - Update a policy
  • DELETE /groups/:id/security/policy_store/:policy_id - Delete a policy

GPAT boundaries (config/authz/permission_groups/assignable_permissions/application_security/govern_policy/):

  • Added group boundary to create.yml, read.yml, update.yml, delete.yml
  • Enables group-scoped granular access tokens to authorize against these endpoints

Code quality:

  • Extracted render_policy_store_error! to class-level helpers (DRY - was duplicated)

Authorization model

  • Group owners can manage policies within their group
  • Non-owners get 403 Forbidden
  • Non-members of private groups get 404 Not Found
  • Policies are scoped by namespace_id - a group cannot see/modify organization-wide policies or other groups' policies

How to set up and validate locally

  1. Enable the feature flag and experiment settings in Rails console:

    Feature.enable(:security_policies_v2)
    ApplicationSetting.current.update!(policy_store_experiment_enabled: true)
  2. Enable the experiment for your group's organization and namespace:

    group = Group.find_by_full_path('your-group')
    Organizations::OrganizationSetting.for(group.organization_id).update!(policy_store_experiment_enabled: true)
    NamespaceSetting.find_or_initialize_by(namespace_id: group.id).update!(policy_store_experiment_enabled: true)
  3. Create a personal access token with api scope for a group owner

  4. Test the endpoints:

    # List policies (empty initially)
    curl --header "PRIVATE-TOKEN: <token>" "http://localhost:3000/api/v4/groups/<group_id>/security/policy_store"
    
    # Create a policy
    curl --request POST --header "PRIVATE-TOKEN: <token>" \
      --header "Content-Type: application/json" \
      --data '{"name": "Test policy", "trigger_type": "deployment_requested", "rules": [{"type": "custom", "value": "test"}]}' \
      "http://localhost:3000/api/v4/groups/<group_id>/security/policy_store"
    
    # Get the created policy
    curl --header "PRIVATE-TOKEN: <token>" "http://localhost:3000/api/v4/groups/<group_id>/security/policy_store/<policy_id>"
    
    # Update the policy
    curl --request PATCH --header "PRIVATE-TOKEN: <token>" \
      --header "Content-Type: application/json" \
      --data '{"name": "Renamed policy"}' \
      "http://localhost:3000/api/v4/groups/<group_id>/security/policy_store/<policy_id>"
    
    # Delete the policy
    curl --request DELETE --header "PRIVATE-TOKEN: <token>" \
      "http://localhost:3000/api/v4/groups/<group_id>/security/policy_store/<policy_id>"
  5. Verify authorization by testing with a non-owner user (should get 403)

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

References

Edited by Andy Schoenen

Merge request reports

Loading
Loading