Improve backend error messaging for orchestration policy validation
<!-- triage-serverless v3 PLEASE DO NOT REMOVE THIS SECTION -->
*This page may contain information related to upcoming products, features and functionality.
It is important to note that the information presented is for informational purposes only, so please do not rely on the information for purchasing or planning purposes.
Just like with all projects, the items mentioned on the page are subject to change or delay, and the development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc.*
<!-- triage-serverless v3 PLEASE DO NOT REMOVE THIS SECTION -->
<!--
Implementation issues are used break-up a large piece of work into small, discrete tasks that can
move independently through the build workflow steps. They're typically used to populate a Feature
Epic. Once created, an implementation issue is usually refined in order to populate and review the
implementation plan and weight.
Example workflow: https://about.gitlab.com/handbook/engineering/development/threat-management/planning/diagram.html#plan
-->
## Why are we doing this work
<!--
A brief explanation of the why, not the what or how. Assume the reader doesn't know the
background and won't have time to dig-up information from comment threads.
-->
Currently, if a Security Orchestration Policy violates the JSON schema, the backend will simply return [`Invalid policy yaml`](https://gitlab.com/gitlab-org/gitlab/-/blob/111ae4c123fa9f51e16ea862a60cea548a5e9623/ee/app/services/security/security_orchestration_policies/process_policy_service.rb#L25). This is unhelpful to the user because it does not tell them what part of the policy needs to be fixed. We should instead return a list of all the policy validation errors which occurred.
Pointed out in: https://gitlab.com/gitlab-org/gitlab/-/issues/339751#note_683097117
## Relevant links
<!--
Information that the developer might need to refer to when implementing the issue.
- [Design Issue](https://gitlab.com/gitlab-org/gitlab/-/issues/<id>)
- [Design 1](https://gitlab.com/gitlab-org/gitlab/-/issues/<id>/designs/<image>.png)
- [Design 2](https://gitlab.com/gitlab-org/gitlab/-/issues/<id>/designs/<image>.png)
- [Similar implementation](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/<id>)
-->
## Non-functional requirements
<!--
Add details for required items and delete others.
-->
- [ ] Documentation:
- [ ] Feature flag:
- [ ] Performance:
- [ ] Testing:
## Implementation plan
<!--
Steps and the parts of the code that will need to get updated. The plan can also
call-out responsibilities for other team members or teams.
e.g.:
- [ ] ~frontend Step 1
- [ ] `@person` Step 1a
- [ ] ~frontend Step 2
-->
Presently, this validation happens in [`orchestration_policy_configuration.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/1e7a2d16b7b67fde966aa3ca0cb510a97dd52a5b/ee/app/models/security/orchestration_policy_configuration.rb#L47) using `schemer.valid?`, which returns a boolean.
We should instead use `schemer.validate` to get a list of validation errors:
```ruby
[26] pry(main)> policy = 'scan_execution_policy:
- name: Run Cluster Image Scanning
description: This policy enforces container scans against a production cluster on a daily basis.
enabled: true
rules:
- type: schedule
agents:
production:
namespaces:
- production
cadence: 0 0 * * *
actions:
- scan: cluster_image_scanning
'
[27] pry(main)> policy_yaml = Gitlab::Config::Loader::Yaml.new(policy).load!
[28] pry(main)> schemer.validate(policy_yaml.deep_stringify_keys).map { |e| JSONSchemer::Errors.pretty(e) }
=> ["property '/scan_execution_policy/0/rules/0/agents' is invalid: error_type=schema"]
```
1. ~backend Create new validator class in `lib/gitlab/security/validators/orchestration_policy_validator.rb`:
```ruby
module GitLab
module Security
module Validators
class OrchestrationPolicyValidator
POLICY_SCHEMA_PATH = 'ee/app/validators/json_schemas/security_orchestration_policy.json'
def initialize(policy)
@errors = JSONSchemer
.schema(Rails.root.join(POLICY_SCHEMA_PATH))
.validate(policy)
end
def valid?
@errors.empty?
end
def errors
@errors.map { |e| JSONSchemer::Errors.pretty(e) }
end
end
end
end
end
```
1. ~backend Add a new `policy_validation` method to `orchestration_policy_configuration.rb` which returns
the `OrchestrationPolicyValidator`:
```ruby
def policy_validation(policy = policy_hash)
GitLab::Security::Validators::OrchestrationPolicyValidator.new(policy)
end
```
1. ~backend Change [`ProcessPolicyService`](https://gitlab.com/gitlab-org/gitlab/-/blob/111ae4c123fa9f51e16ea862a60cea548a5e9623/ee/app/services/security/security_orchestration_policies/process_policy_service.rb#L25) to return a detailed error message when validation errors exist.
```ruby
validator = policy_configuration.policy_validation(policy)
raise format_errors(validator.errors) unless validator.valid?
```
<!--
Workflow and other relevant labels
# ~"group::" ~"Category:" ~"GitLab Ultimate"
Other settings you might want to include when creating the issue.
# /assign @
# /epic &
-->
issue