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.
- On
master, inbundle exec rails console, confirm the default:
Gitlab::PolicyStore.configuration.repository # => Gitlab::PolicyStore::Adapters::InMemoryPolicyRepository- Check out this branch, restart the console, and read the same line. Verify it now
returns
Govern::PolicyStore::ActiveRecordPolicyRepository. - 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- 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- Verify
Govern::Policy.lastreturns the row. Restart the console and read it back throughSecurity::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. - 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
- Related to https://gitlab.com/gitlab-org/gitlab/-/work_items/604367
- Depends on !249574 (merged) (merged)
- Splits out of !248000 (closed) (open)