An error occurred while fetching the assigned iteration of the selected issue.
Add GraphQL API for policy errors and violations
Why are we doing this work
In Add violation_data to scan_result_policy_violat... (#433390 - closed) we're adding a new column violation_data
to be able to save details about what caused policy violations. In Extend policy bot comment with violation data (#433403 - closed), we're also adding a service to normalize the violations for all policy rule types and we can use it for the GraphQL API.
In this issue, we want to add GraphQL API to query policyViolation
and policyErrors
for a given merge request.
Relevant links
Non-functional requirements
- Documentation:
- Feature flag: we should do these changes behind feature flag
- Performance:
- Testing:
Implementation plan
A rough diff:
diff --git a/ee/app/graphql/ee/types/merge_request_type.rb b/ee/app/graphql/ee/types/merge_request_type.rb
index c00868b23debbf16c6bb18758c06fd48783cfe83..5f42e326ff240641cab35d509dd7fc399143742c 100644
--- a/ee/app/graphql/ee/types/merge_request_type.rb
+++ b/ee/app/graphql/ee/types/merge_request_type.rb
@@ -31,6 +31,18 @@ module MergeRequestType
null: false,
description: 'Information relating to rules that must be satisfied to merge this merge request.'
+ field :policy_violations,
+ type: [::Types::SecurityOrchestration::ScanResultPolicyViolationType],
+ null: true,
+ description: 'Scan Result Policies violations for the merge request',
+ resolver: ::Resolvers::SecurityOrchestration::ScanResultPolicyViolationResolver
+
+ field :policy_errors,
+ type: [::Types::SecurityOrchestration::ScanResultPolicyErrorType],
+ null: true,
+ description: 'Scan Result Policies errors for the merge request',
+ resolver: ::Resolvers::SecurityOrchestration::ScanResultPolicyErrorsResolver
+
field :suggested_reviewers, ::Types::AppliedMl::SuggestedReviewersType,
null: true,
alpha: { milestone: '15.4' },
diff --git a/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_errors_resolver.rb b/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_errors_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..24bc8f5bbbb8f02b35a7c57df78f014c391b8e85
--- /dev/null
+++ b/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_errors_resolver.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module SecurityOrchestration
+ class ScanResultPolicyErrorsResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ alias_method :merge_request, :object
+
+ type Types::SecurityOrchestration::ScanResultPolicyErrorType, null: true
+
+ def resolve(**_args)
+ presenter = Security::SecurityOrchestrationPolicies::ScanResultPolicyViolationsPresenter.new(merge_request)
+ presenter.errors
+ end
+ end
+ end
+end
diff --git a/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_violation_resolver.rb b/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_violation_resolver.rb
new file mode 100644
index 0000000000000000000000000000000000000000..711107b889d547ccd5ebbc7b4eed52a093069690
--- /dev/null
+++ b/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_violation_resolver.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module SecurityOrchestration
+ class ScanResultPolicyViolationResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ alias_method :merge_request, :object
+
+ type Types::SecurityOrchestration::ScanResultPolicyViolationType, null: true
+
+ def resolve(**_args)
+ presenter = Security::SecurityOrchestrationPolicies::ScanResultPolicyViolationsPresenter.new(merge_request)
+ presenter.violations
+ end
+ end
+ end
+end
diff --git a/ee/app/graphql/types/security_orchestration/scan_result_policy_error_type.rb b/ee/app/graphql/types/security_orchestration/scan_result_policy_error_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49bb6608a3e1f92e79616b664d90360aa54c10df
--- /dev/null
+++ b/ee/app/graphql/types/security_orchestration/scan_result_policy_error_type.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Types
+ module SecurityOrchestration
+ # rubocop: disable Graphql/AuthorizeTypes -- In line with other policy types
+ class ScanResultPolicyErrorType < BaseObject
+ graphql_name 'ScanResultPolicyError'
+ description 'Represents the scan result policy error'
+
+ field :name,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Represents the policy name.'
+
+ field :report_type,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Represents the report type.'
+
+ field :code,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Represents the error code.'
+
+ field :suggestion,
+ type: ::GraphQL::Types::String,
+ null: true,
+ description: 'Represents suggestion on how to fix the error.'
+
+ field :scan_result_policy_id,
+ type: GraphQL::Types::Int,
+ null: false,
+ description: 'Policy id.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/ee/app/graphql/types/security_orchestration/scan_result_policy_violation_detail_type.rb b/ee/app/graphql/types/security_orchestration/scan_result_policy_violation_detail_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3b6fa71812808d18ab161fe04eafe3c1226142b0
--- /dev/null
+++ b/ee/app/graphql/types/security_orchestration/scan_result_policy_violation_detail_type.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Types
+ module SecurityOrchestration
+ # rubocop: disable Graphql/AuthorizeTypes -- In line with other policy types
+ class ScanResultPolicyViolationDetailType < BaseObject
+ graphql_name 'ScanResultPolicyViolationDetail'
+ description 'Represents the scan result policy violation detail'
+
+ field :status,
+ type: GraphQL::Types::String,
+ null: true,
+ description: 'Represents whether the violation is newly detected or previously existing.'
+
+ field :scan_type,
+ type: GraphQL::Types::String,
+ null: true,
+ description: 'Represents the scan type.'
+
+ field :severity,
+ type: GraphQL::Types::String,
+ null: true,
+ description: 'Represents the severity.'
+
+ field :path,
+ type: GraphQL::Types::String,
+ null: true,
+ description: 'Represents the path to the finding in the code.'
+
+ field :description,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Represents the description.'
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/ee/app/graphql/types/security_orchestration/scan_result_policy_violation_type.rb b/ee/app/graphql/types/security_orchestration/scan_result_policy_violation_type.rb
new file mode 100644
index 0000000000000000000000000000000000000000..67437592cf8a48fe49e93262b3ab7824078dbdc5
--- /dev/null
+++ b/ee/app/graphql/types/security_orchestration/scan_result_policy_violation_type.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Types
+ module SecurityOrchestration
+ # rubocop: disable Graphql/AuthorizeTypes -- In line with other policy types
+ class ScanResultPolicyViolationType < BaseObject
+ graphql_name 'ScanResultPolicyViolation'
+ description 'Represents the scan result policy violation'
+
+ field :name,
+ type: GraphQL::Types::String,
+ null: false,
+ description: 'Represents the name.'
+
+ field :report_type,
+ type: ::Types::SecurityOrchestration::ApprovalReportTypeEnum,
+ null: false,
+ description: 'Represents the report_type of the approval rule.'
+
+ field :violations,
+ type: [::Types::SecurityOrchestration::ScanResultPolicyViolationDetailType],
+ null: false,
+ description: 'Represents related violations.'
+
+ field :scan_result_policy_id,
+ type: GraphQL::Types::Int,
+ null: false,
+ description: 'Policy id.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
Verification steps
- Create a project
- Create
.gitlab-ci.yml
:include: - template: Jobs/Secret-Detection.gitlab-ci.yml build-job: script: - echo "Compiling the code..." - echo "Compile complete."
- Create a policy:
type: approval_policy name: Sec & Lic description: '' enabled: true rules: - type: scan_finding scanners: [] vulnerabilities_allowed: 0 severity_levels: [] vulnerability_states: - new_needs_triage - new_dismissed - detected - confirmed - dismissed - resolved branch_type: protected - type: license_finding match_on_inclusion_license: true license_types: - BSD 3-Clause "New" or "Revised" License license_states: - newly_detected branch_type: protected - type: any_merge_request branch_type: protected commits: unsigned actions: - type: require_approval approvals_required: 1 role_approvers: - developer
- Create MR with violation:
diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..ee4bf74ac3b632173dafc09e74ecd68c298bdfa1 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +AWS_TOKEN=AKIAZYONPI3G4JNCCWGQ \ No newline at end of file
- Merge the secret so that it becomes a previously-existing vulnerability. The vulnerability should show up in
Secure -> Vulnerability report
. - Create a new MR, adding a new leaked secret. Example:
diff --git a/.env b/.env index ee4bf74ac3b632173dafc09e74ecd68c298bdfa1..d791cd2180779bf094bd017e07b12c1fad506c2c 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -AWS_TOKEN=AKIAZYONPI3G4JNCCWGQ \ No newline at end of file +AWS_TOKEN=AKIAZYONPI3G4JNCCWGQ +AWS_TOKEN2=AKIAZYONPI3G4JNCCWGZ \ No newline at end of file
- Query the data presented in the bot comment via GraphQL API (
/-/graphql-explorer
):
{
project(fullPath: "<project-path>") {
mergeRequest(iid: "<iid>") {
id
title
policyViolations {
policies {
name
reportType
}
newScanFinding {
name
reportType
severity
location
path
}
previousScanFinding {
name
reportType
severity
location
path
}
anyMergeRequest {
name
commits
}
licenseScanning {
license
dependencies
url
}
errors {
error
message
reportType
data
}
comparisonPipelines {
reportType
source
target
}
}
}
}
}