Draft: Rd summit challenge policy store [ci skip]
What does this MR do and why?
Builds the Policy Store v2 authoring stack on the branch, derived from the deployment-gate PoC in !245588:
gitlab-security-policy-storegem (Gitlab::SecurityPolicyStore) — AR-free store with a read/write seam (Store#load_policies/#list/#find/#create/#delete) over an injected adapter, plus theScopeTranspiler.- Persistence — the
policiestable in the sec schema (gitlab_sec),Security::Policies::V2::Policy, andSecurity::Policies::V2::StoreAdapter. The cross-schema link fromprotected_environment_approval_rulesis a loose foreign key. - Authoring services —
Security::SecurityOrchestrationPolicies::PolicyStore::{Create,Find,List,Destroy}, routed through the store gem. - REST API — Full CRUD for policies:
POST /groups/:id/security/policies(create)GET /groups/:id/security/policies(list)GET /groups/:id/security/policies/:policy_id(show)DELETE /groups/:id/security/policies/:policy_id(delete)- Plus metadata lists:
GET /security/policies/{triggers,actions,rules} - All gated by the Policy Store experiment.
- Deployment gate —
EE::Ci::ProcessBuildServicelists deployment policies through the store (scoped to the project's organization) and evaluates them.
The Rego evaluator (
Gitlab::Glaz.evaluate_rego) is owned by the engine team and is not yet on this branch, so end-to-end gate enforcement is not runnable here — the gate's unit spec stubs the evaluator. Authoring via the REST API is fully runnable.
References
- Derived from the deployment-gate PoC: !245588
- Design:
UNIFIED_POLICY_STORE_GEM_DESIGN.md,SECURITY_POLICY_V2_CREATE_MUTATION_PLAN.md,POLICY_STORE_DEPLOYMENT_GATE_INTEGRATION_PLAN.md
How to verify (REST API)
Backend/console + curl. This replaces the PoC's security_policies:v2:seed rake task with
the REST authoring API.
One-time setup
bundle install
bundle exec rails db:migrateEnable the Policy Store experiment
# rails console — the experiment is required for authoring + the gate
ApplicationSetting.current.update!(policy_store_experiment_enabled: true) # instance
group = Group.find_by_full_path("your-group")
group.namespace_settings.update!(policy_store_experiment_enabled: true) # group
Feature.enable(:security_policies_v2, group) # feature flag
# Ultimate license (security_orchestration_policies) must be present.Create a policy
TOKEN=<personal access token with api scope>
GITLAB_URL=http://gdk.test:3000
curl --request POST \
--header "PRIVATE-TOKEN: $TOKEN" \
--header "Content-Type: application/json" \
--url "$GITLAB_URL/api/v4/groups/your-group/security/policies" \
--data '{"name": "Production deployment gate", "trigger_id": "deployment_requested", "mode": "enforce", "rules": { "rego": "package governance\nimport rego.v1\n\nmatches contains msg if {\n input.environment.name == \"production\"\n msg := \"Production deployment blocked\"\n}" }, "actions": [{ "type": "block", "reason": "Enforced by policy" }]}'
# => 201 Created, returns the policy (id, name, trigger_id, mode, scope_rego)List policies for a group
curl --header "PRIVATE-TOKEN: $TOKEN" "$GITLAB_URL/api/v4/groups/your-group/security/policies"
# => [{ "id": 1, "name": "Production deployment gate", ... }]Get a single policy by ID
curl --header "PRIVATE-TOKEN: $TOKEN" "$GITLAB_URL/api/v4/groups/your-group/security/policies/1"
# => { "id": 1, "name": "Production deployment gate", ... }Delete a policy
curl --request DELETE --header "PRIVATE-TOKEN: $TOKEN" "$GITLAB_URL/api/v4/groups/your-group/security/policies/1"
# => 204 No ContentList authoring metadata
curl --header "PRIVATE-TOKEN: $TOKEN" "$GITLAB_URL/api/v4/security/policies/triggers"
# => [{ "id": "deployment_requested", "name": "Deployment" }]
curl --header "PRIVATE-TOKEN: $TOKEN" "$GITLAB_URL/api/v4/security/policies/actions"
# => block, require_approval
curl --header "PRIVATE-TOKEN: $TOKEN" "$GITLAB_URL/api/v4/security/policies/rules"
# => custom, calendar, environmentVerify persistence
# rails console — the row lands in the sec schema
policy = Security::Policies::V2::Policy.last
policy.slice(:organization_id, :trigger_id, :mode)
policy.rules # { "rego" => "package governance..." }
policy.actions # [{ "type" => "block", ... }]Negative checks: with the experiment disabled (either the instance or group setting), the
endpoints return 404; without the Ultimate license they return 403; an unknown trigger_id
returns 400; requesting a non-existent policy ID returns 404.
Deployment gate (pending the evaluator)
The gate (EE::Ci::ProcessBuildService) lists these policies through the store and evaluates
each rego-authored rule, enforcing block/log actions honoring mode. Because
Gitlab::Glaz.evaluate_rego is not yet on the branch, this path can't be exercised end-to-end
here; the expected behavior (covered by the gate spec) is:
| Policy | deploy-prod job |
|---|---|
enforce + block, rego matches |
failed, reason security_policy_denied |
audit mode |
proceeds |
| no matching policy / experiment off | proceeds |
typed rules (no rego) |
skipped (rego-only for now) |
Deferred
Scope (scope_rego) applicability filtering, typed-rule evaluation, the require_approval
executor, an org-scoped store query, and the external Rego evaluator/engine gem.