Move the policy store services to organization tenancy
What does this MR do and why?
Moves the four Policy Store authoring services from group to organization tenancy. Split out of the list endpoint MR, which carried this alongside the route.
The move is mechanical. All four services took group: only to reduce it to
group.organization_id on the next line, in BaseService#find_policy,
ListService#execute, and CreateService#policy_attributes. The store has always been
keyed by organization, so the group was a lookup vehicle for a value the caller already
had. All four rename together because they share a constructor through super, even
though only ListService has an endpoint so far. The store gem is untouched, and nothing
outside the specs constructs these services yet.
CreateService stops setting namespace_id, which an organization-owned policy has
nothing to supply. The gem stopped requiring it in
!249164 (merged) (merged).
Cross-organization isolation stays a Ruby-side filter in BaseService#find_policy, now
comparing organization.id directly, because the store's find(id) and delete(id) take
no organization and a policy id is global.
The gate changes shape. Group#policy_store_experiment_active? requires root? and the
group's own namespace_settings.policy_store_experiment_enabled, and an organization has
neither. The new Organization#policy_store_experiment_active? is therefore the feature
flag (against :instance, matching the API's before block and the admin settings
partial), the instance setting, and the licence. Two consequences worth naming:
- The licence check widens from
licensed_feature_available?, which resolves the namespace's plan on GitLab.com, to::License.feature_available?. Organizations have no plan. Self-managed is unaffected, and the namespace-scoped check returns with the ActiveRecord-backed repository. - The group UI stays gated more tightly, since
Groups::Security::PolicyStoreControllerand the sidebar menu still call the group method. The two surfaces converge when the group UI moves to the organization endpoints.
How to set up and validate locally
Requires an Ultimate licence. Every step runs on the rails console, apart from the admin setting in step 2.
- Enable the
security_policies_v2feature flag
Feature.enable(:security_policies_v2)- As an administrator, go to Admin > Settings > Security and compliance and turn on the policy store experiment
- Confirm the new organization gate is satisfied, since it is what the services now read
organization = Organizations::Organization.find(1)
organization.policy_store_experiment_active? # => true- Create a policy and verify it is keyed by the organization with no namespace
result = Security::SecurityOrchestrationPolicies::PolicyStore::CreateService.new(
organization: organization,
params: { name: 'Block deployments on critical findings', trigger_type: 'deployment_requested' }
).execute
puts(result.success? ? result.payload[:policy].to_h : result.message)organization_id matches step 3 and namespace_id is nil. Before this change the same
call took group: and namespace_id came back as the group's id.
- Verify the list and find services agree, reading through the same organization
policy_id = Security::SecurityOrchestrationPolicies::PolicyStore::ListService
.new(organization: organization).execute.payload[:policies].last.id
Security::SecurityOrchestrationPolicies::PolicyStore::FindService
.new(organization: organization, policy_id: policy_id).execute.payload[:policy].name
# => "Block deployments on critical findings"- Verify a second organization cannot reach that policy by id, which is the only cross-organization guard today
other_organization = Organizations::Organization.create!(name: 'Other', path: 'other-org')
result = Security::SecurityOrchestrationPolicies::PolicyStore::FindService
.new(organization: other_organization, policy_id: policy_id).execute
puts result.message # => "Policy was not found"- Turn the instance setting from step 2 back off and verify the services refuse, with the message now naming the organization
Gitlab::CurrentSettings.update!(policy_store_experiment_enabled: false)
result = Security::SecurityOrchestrationPolicies::PolicyStore::ListService
.new(organization: organization).execute
puts result.reason # => experiment_not_active
puts result.message # => "Policy Store experiment is not active for this organization"References
- Related to https://gitlab.com/gitlab-org/gitlab/-/work_items/606971
- Part of https://gitlab.com/groups/gitlab-org/-/epics/22937
- Split out of !249148 (merged) (open), which now targets this and adds only the list endpoint.
- The gem change that lets a policy be owned by an organization alone: !249164 (merged) (merged)
- The ActiveRecord-backed repository, which replaces the in-memory store: https://gitlab.com/gitlab-org/gitlab/-/work_items/606969