Wire the policy store facade to its ActiveRecord backend

What does this MR do and why?

ee/config/initializers/policy_store.rb points Gitlab::PolicyStore's facade at Govern::PolicyStore::ActiveRecordPolicyRepository, replacing the gem's in-memory default. From here on, the services under ee/app/services/security/security_orchestration_policies/policy_store/ persist to govern_policies instead of to per-process memory.

The initializer runs inside to_prepare rather than as a plain initializer because the Configuration singleton survives a development reload while the repository class does not. It ships without a Gitlab.ee? guard because config/application.rb only adds ee/config/initializers to the load path under Gitlab.ee, so FOSS never loads the file.

Wiring the adapter in unconditionally is safe: every caller in BaseService returns early unless organization.policy_store_experiment_active?, which requires the security_policies_v2 feature flag, the policy_store_experiment_enabled application setting, and the security_orchestration_policies licensed feature together.

This is split out from !249574 (merged), which adds the adapter itself, to keep the revert surface to these two files.

How to set up and validate locally

Needs an Ultimate licence, because security_orchestration_policies is Ultimate-only.

  1. On master, in bundle exec rails console, confirm the default:
Gitlab::PolicyStore.configuration.repository # => Gitlab::PolicyStore::Adapters::InMemoryPolicyRepository
  1. Check out this branch, restart the console, and read the same line. Verify it now returns Govern::PolicyStore::ActiveRecordPolicyRepository.
  2. Open the experiment gate and assert it took effect with the predicate the production code actually calls:
Feature.enable(:security_policies_v2)
ApplicationSetting.current.update!(policy_store_experiment_enabled: true)
organization = Organizations::Organization.default_organization
organization.policy_store_experiment_active? # => true
  1. Create a policy through the service layer, so the write goes through this wiring:
result = Security::SecurityOrchestrationPolicies::PolicyStore::CreateService.new(
  organization: organization,
  params: { name: 'stacked-mr-check', trigger_type: 'deployment_requested' }
).execute
result.success? ? result.payload[:policy] : result.message
  1. Verify Govern::Policy.last returns the row. Restart the console and read it back through Security::SecurityOrchestrationPolicies::PolicyStore::FindService.new(organization: organization, policy_id: Govern::Policy.last.id).execute. Verify it succeeds with the same policy, since surviving a restart is what the in-memory adapter would not have given you.
  2. Turn the gate back off and repeat step 4:
ApplicationSetting.current.update!(policy_store_experiment_enabled: false)

Verify result.message equals Security::SecurityOrchestrationPolicies::PolicyStore::BaseService::EXPERIMENT_NOT_ACTIVE_MESSAGE, because the wiring being unconditional must not mean the feature is reachable. 7. Clean up: Govern::Policy.last.destroy!.

References

Edited by Marcos Rocha

Merge request reports

Loading
Loading