Skip to content
Snippets Groups Projects
Commit 6257f84d authored by Evgeniy Pats's avatar Evgeniy Pats
Browse files

Add Coverage Fuzzing to DB & Security Dashboard

This adds database support, graphql and frontend

Related MR https://gitlab.com/gitlab-org/gitlab/-/merge_requests/34648
parent 9cbb10d6
No related branches found
No related tags found
No related merge requests found
Showing
with 317 additions and 17 deletions
Loading
Loading
@@ -14666,7 +14666,7 @@ enum VulnerabilityIssueLinkType {
"""
Represents a vulnerability location. The fields with data will depend on the vulnerability report type
"""
union VulnerabilityLocation = VulnerabilityLocationContainerScanning | VulnerabilityLocationDast | VulnerabilityLocationDependencyScanning | VulnerabilityLocationSast | VulnerabilityLocationSecretDetection
union VulnerabilityLocation = VulnerabilityLocationContainerScanning | VulnerabilityLocationCoverageFuzzing | VulnerabilityLocationDast | VulnerabilityLocationDependencyScanning | VulnerabilityLocationSast | VulnerabilityLocationSecretDetection
 
"""
Represents the location of a vulnerability found by a container security scan
Loading
Loading
@@ -14688,6 +14688,36 @@ type VulnerabilityLocationContainerScanning {
operatingSystem: String
}
 
"""
Represents the location of a vulnerability found by a Coverage Fuzzing scan
"""
type VulnerabilityLocationCoverageFuzzing {
"""
Number of the last relevant line in the vulnerable file
"""
endLine: String
"""
Path to the vulnerable file
"""
file: String
"""
Number of the first relevant line in the vulnerable file
"""
startLine: String
"""
Class containing the vulnerability
"""
vulnerableClass: String
"""
Method containing the vulnerability
"""
vulnerableMethod: String
}
"""
Represents the location of a vulnerability found by a DAST scan
"""
Loading
Loading
Loading
Loading
@@ -43119,6 +43119,11 @@
"name": "VulnerabilityLocationContainerScanning",
"ofType": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationCoverageFuzzing",
"ofType": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationDast",
Loading
Loading
@@ -43196,6 +43201,89 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationCoverageFuzzing",
"description": "Represents the location of a vulnerability found by a Coverage Fuzzing scan",
"fields": [
{
"name": "endLine",
"description": "Number of the last relevant line in the vulnerable file",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "file",
"description": "Path to the vulnerable file",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "startLine",
"description": "Number of the first relevant line in the vulnerable file",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerableClass",
"description": "Class containing the vulnerability",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerableMethod",
"description": "Method containing the vulnerability",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationDast",
Loading
Loading
@@ -2196,6 +2196,18 @@ Represents the location of a vulnerability found by a container security scan
| `image` | String | Name of the vulnerable container image |
| `operatingSystem` | String | Operating system that runs on the vulnerable container image |
 
## VulnerabilityLocationCoverageFuzzing
Represents the location of a vulnerability found by a Coverage Fuzzing scan
| Name | Type | Description |
| --- | ---- | ---------- |
| `endLine` | String | Number of the last relevant line in the vulnerable file |
| `file` | String | Path to the vulnerable file |
| `startLine` | String | Number of the first relevant line in the vulnerable file |
| `vulnerableClass` | String | Class containing the vulnerability |
| `vulnerableMethod` | String | Method containing the vulnerability |
## VulnerabilityLocationDast
 
Represents the location of a vulnerability found by a DAST scan
Loading
Loading
{"__schema":{"types":[{"kind":"UNION","name":"VulnerabilityLocation","possibleTypes":[{"name":"VulnerabilityLocationContainerScanning"},{"name":"VulnerabilityLocationDast"},{"name":"VulnerabilityLocationDependencyScanning"},{"name":"VulnerabilityLocationSast"},{"name":"VulnerabilityLocationSecretDetection"}]}]}}
{"__schema":{"types":[{"kind":"UNION","name":"VulnerabilityLocation","possibleTypes":[{"name":"VulnerabilityLocationContainerScanning"},{"name":"VulnerabilityLocationDast"},{"name":"VulnerabilityLocationDependencyScanning"},{"name":"VulnerabilityLocationSast"},{"name":"VulnerabilityLocationSecretDetection"},{"name":"VulnerabilityLocationCoverageFuzzing"}]}]}}
Loading
Loading
@@ -15,6 +15,9 @@ query($fullPath: ID!, $pipelineIid: ID!) {
dependencyScanning {
vulnerabilitiesCount
}
coverageFuzzing {
vulnerabilitiesCount
}
}
}
}
Loading
Loading
Loading
Loading
@@ -268,7 +268,13 @@ export default {
};
},
},
securityReportTypes: ['dast', 'sast', 'dependencyScanning', 'containerScanning'],
securityReportTypes: [
'dast',
'sast',
'dependencyScanning',
'containerScanning',
'coverage_fuzzing',
],
};
</script>
<template>
Loading
Loading
@@ -333,6 +339,7 @@ export default {
:enabled-reports="mr.enabledReports"
:sast-help-path="mr.sastHelp"
:dast-help-path="mr.dastHelp"
:coverage-fuzzing-help-path="mr.coverageFuzzingHelp"
:container-scanning-help-path="mr.containerScanningHelp"
:dependency-scanning-help-path="mr.dependencyScanningHelp"
:secret-scanning-help-path="mr.secretScanningHelp"
Loading
Loading
Loading
Loading
@@ -13,6 +13,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.sastHelp = data.sast_help_path;
this.containerScanningHelp = data.container_scanning_help_path;
this.dastHelp = data.dast_help_path;
this.coverageFuzzingHelp = data.coverage_fuzzing_help_path;
this.secretScanningHelp = data.secret_scanning_help_path;
this.dependencyScanningHelp = data.dependency_scanning_help_path;
this.vulnerabilityFeedbackPath = data.vulnerability_feedback_path;
Loading
Loading
Loading
Loading
@@ -55,8 +55,8 @@ export default {
crashAddress() {
return this.vulnerability.location?.crash_address;
},
crashState() {
return this.vulnerability.location?.crash_state;
stacktraceSnippet() {
return this.vulnerability.location?.stacktrace_snippet;
},
className() {
return this.vulnerability.location?.class;
Loading
Loading
@@ -107,9 +107,6 @@ export default {
scannerVersion: this.scannerVersion,
});
},
stacktraceSnippet() {
return this.vulnerability.stacktrace_snippet;
},
},
methods: {
hasMoreValues(index, values) {
Loading
Loading
@@ -182,10 +179,6 @@ export default {
<gl-friendly-wrap ref="crashAddress" :text="crashAddress" />
</vulnerability-detail>
 
<vulnerability-detail v-if="crashState" :label="s__('Crash State')">
<code-block ref="crashState" :code="crashState" max-height="225px" />
</vulnerability-detail>
<vulnerability-detail v-if="stacktraceSnippet" :label="s__('Stacktrace snippet')">
<code-block ref="stacktraceSnippet" :code="stacktraceSnippet" max-height="225px" />
</vulnerability-detail>
Loading
Loading
Loading
Loading
@@ -64,6 +64,11 @@ export default {
required: false,
default: '',
},
coverageFuzzingHelpPath: {
type: String,
required: false,
default: '',
},
dependencyScanningHelpPath: {
type: String,
required: false,
Loading
Loading
@@ -136,6 +141,7 @@ export default {
'sast',
'containerScanning',
'dast',
'coverageFuzzing',
'dependencyScanning',
'secretScanning',
'summaryCounts',
Loading
Loading
@@ -173,6 +179,9 @@ export default {
hasDastReports() {
return this.enabledReports.dast;
},
hasCoverageFuzzingReports() {
return this.enabledReports.coverage_fuzzing;
},
hasSastReports() {
return this.enabledReports.sast;
},
Loading
Loading
@@ -237,6 +246,13 @@ export default {
this.setSecretScanningDiffEndpoint(secretScanningDiffEndpoint);
this.fetchSecretScanningDiff();
}
const coverageFuzzingDiffEndpoint = gl?.mrWidgetData?.coverage_fuzzing_comparision_path;
if (coverageFuzzingDiffEndpoint && this.hasCoverageFuzzingReports) {
this.setCoverageFuzzingDiffEndpoint(coverageFuzzingDiffEndpoint);
this.fetchCoverageFuzzingDiff();
}
},
methods: {
...mapActions([
Loading
Loading
@@ -441,6 +457,25 @@ export default {
/>
</template>
 
<template v-if="hasCoverageFuzzingReports">
<summary-row
:summary="groupedSastText"
:status-icon="sastStatusIcon"
:popover-options="coverageFuzzingPopover"
class="js-sast-widget"
data-qa-selector="sast_scan_report"
/>
<grouped-issues-list
v-if="sast.newIssues.length || sast.resolvedIssues.length"
:unresolved-issues="sast.newIssues"
:resolved-issues="sast.resolvedIssues"
:component="$options.componentNames.SecurityIssueBody"
class="report-block-group-list"
data-testid="sast-issues-list"
/>
</template>
<issue-modal
:modal="modal"
:vulnerability-feedback-help-path="vulnerabilityFeedbackHelpPath"
Loading
Loading
Loading
Loading
@@ -85,5 +85,18 @@ export default {
),
};
},
coverageFuzzingPopover() {
return {
title: s__('ciReport|Coverage Fuzzing Title'),
content: sprintf(
s__('ciReport|%{linkStartTag}Learn more about Coverage Fuzzing %{linkEndTag}'),
{
linkStartTag: getLinkStartTag(this.coverageFuzzingHelpPath),
linkEndTag,
},
false,
),
};
},
},
};
Loading
Loading
@@ -159,6 +159,46 @@ export const fetchDependencyScanningDiff = ({ state, dispatch }) => {
export const updateDependencyScanningIssue = ({ commit }, issue) =>
commit(types.UPDATE_DEPENDENCY_SCANNING_ISSUE, issue);
 
/**
* COVERAGE FUZZING
*/
export const setCoverageFuzzingDiffEndpoint = ({ commit }, path) =>
commit(types.SET_COVERAGE_FUZZING_DIFF_ENDPOINT, path);
export const requestCoverageFuzzingDiff = ({ commit }) =>
commit(types.REQUEST_COVERAGE_FUZZING_DIFF);
export const receiveCoverageFuzzingDiffSuccess = ({ commit }, response) =>
commit(types.RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS, response);
export const receiveCoverageFuzzingDiffError = ({ commit }) =>
commit(types.RECEIVE_COVERAGE_FUZZING_DIFF_ERROR);
export const fetchCoverageFuzzingDiff = ({ state, dispatch }) => {
dispatch('requestCoverageFuzzingDiff');
return Promise.all([
pollUntilComplete(state.coverageFuzzing.paths.diffEndpoint),
axios.get(state.vulnerabilityFeedbackPath, {
params: {
category: 'coverage_fuzzing',
},
}),
])
.then(values => {
dispatch('receiveCoverageFuzzingDiffSuccess', {
diff: values[0].data,
enrichData: values[1].data,
});
})
.catch(() => {
dispatch('receiveCoverageFuzzingDiffError');
});
};
export const updateCoverageFuzzingIssue = ({ commit }, issue) =>
commit(types.UPDATE_COVERAGE_FUZZING_ISSUE, issue);
/**
* SECRET SCANNING
*/
Loading
Loading
Loading
Loading
@@ -6,6 +6,7 @@ const updateIssueActionsMap = {
container_scanning: 'updateContainerScanningIssue',
dast: 'updateDastIssue',
secret_scanning: 'updateSecretScanningIssue',
coverage_fuzzing: 'updateCoverageFuzzingIssue',
};
 
export default function configureMediator(store) {
Loading
Loading
Loading
Loading
@@ -35,6 +35,12 @@ export const REQUEST_SECRET_SCANNING_DIFF = 'REQUEST_SECRET_SCANNING_DIFF';
export const RECEIVE_SECRET_SCANNING_DIFF_SUCCESS = 'RECEIVE_SECRET_SCANNING_DIFF_SUCCESS';
export const RECEIVE_SECRET_SCANNING_DIFF_ERROR = 'RECEIVE_SECRET_SCANNING_DIFF_ERROR';
 
// COVERAGE FUZZING
export const SET_COVERAGE_FUZZING_DIFF_ENDPOINT = 'SET_COVERAGE_FUZZING_DIFF_ENDPOINT';
export const REQUEST_COVERAGE_FUZZING_DIFF = 'REQUEST_COVERAGE_FUZZING_DIFF';
export const RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS = 'RECEIVE_COVERAGE_FUZZING_DIFF_SUCCESS';
export const RECEIVE_COVERAGE_FUZZING_DIFF_ERROR = 'RECEIVE_COVERAGE_FUZZING_DIFF_ERROR';
// Dismiss security issue
export const SET_ISSUE_MODAL_DATA = 'SET_ISSUE_MODAL_DATA';
export const REQUEST_DISMISS_VULNERABILITY = 'REQUEST_DISMISS_VULNERABILITY';
Loading
Loading
@@ -63,6 +69,7 @@ export const UPDATE_DEPENDENCY_SCANNING_ISSUE = 'UPDATE_DEPENDENCY_SCANNING_ISSU
export const UPDATE_CONTAINER_SCANNING_ISSUE = 'UPDATE_CONTAINER_SCANNING_ISSUE';
export const UPDATE_DAST_ISSUE = 'UPDATE_DAST_ISSUE';
export const UPDATE_SECRET_SCANNING_ISSUE = 'UPDATE_SECRET_SCANNING_ISSUE';
export const UPDATE_COVERAGE_FUZZING_ISSUE = 'UPDATE_COVERAGE_FUZZING_ISSUE';
 
export const OPEN_DISMISSAL_COMMENT_BOX = 'OPEN_DISMISSAL_COMMENT_BOX ';
export const CLOSE_DISMISSAL_COMMENT_BOX = 'CLOSE_DISMISSAL_COMMENT_BOX';
Loading
Loading
@@ -43,7 +43,22 @@ export default () => ({
hasBaseReport: false,
scans: [],
},
coverageFuzzing: {
paths: {
head: null,
base: null,
diffEndpoint: null,
},
isLoading: false,
hasError: false,
 
newIssues: [],
resolvedIssues: [],
allIssues: [],
baseReportOutofDate: false,
hasBaseReport: false,
},
dependencyScanning: {
paths: {
head: null,
Loading
Loading
Loading
Loading
@@ -18,6 +18,9 @@ export default {
location() {
return this.vulnerability.location || {};
},
stacktraceSnippet() {
return this.vulnerability.stacktrace_snippet || '';
},
scanner() {
return this.vulnerability.scanner || {};
},
Loading
Loading
@@ -154,6 +157,23 @@ export default {
</detail-item>
</ul>
 
<template v-if="location.crash_address || location.stacktrace_snippet || location.crash_type">
<h3>{{ __('Location') }}</h3>
<ul>
<detail-item
v-if="location.crash_address"
:sprintf-message="__('%{labelStart}Crash Address:%{labelEnd} %{crash_address}')"
>{{ location.crash_address }}
</detail-item>
<detail-item
v-if="location.stacktrace_snippet"
:sprintf-message="__('%{labelStart}Crash State:%{labelEnd} %{stacktrace_snippet}')"
>
<code-block :code="location.stacktrace_snippet" max-height="225px" />
</detail-item>
</ul>
</template>
<template v-if="location.file">
<h3>{{ __('Location') }}</h3>
<ul>
Loading
Loading
Loading
Loading
@@ -18,7 +18,7 @@ module EE
before_action :whitelist_query_limiting_ee_show, only: [:show]
before_action :authorize_read_pipeline!, only: [:container_scanning_reports, :dependency_scanning_reports,
:license_scanning_reports,
:sast_reports, :secret_detection_reports, :dast_reports, :metrics_reports]
:sast_reports, :secret_detection_reports, :dast_reports, :metrics_reports, :coverage_fuzzing_reports]
 
feature_category :container_scanning, only: [:container_scanning_reports]
feature_category :dependency_scanning, only: [:dependency_scanning_reports]
Loading
Loading
@@ -83,6 +83,10 @@ module EE
reports_response(merge_request.compare_metrics_reports)
end
 
def coverage_fuzzing_reports
reports_response(merge_request.compare_coverage_fuzzing_reports(current_user))
end
protected
 
# rubocop:disable Gitlab/ModuleWithInstanceVariables
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@
# Security::JobsFinder
#
# Abstract class encapsulating common logic for finding jobs (builds) that are related to the Secure products
# SAST, DAST, Dependency Scanning, Container Scanning and License Management
# SAST, DAST, Dependency Scanning, Container Scanning and License Management, Coverage Fuzzing
#
# Arguments:
# params:
Loading
Loading
@@ -15,7 +15,7 @@ module Security
attr_reader :pipeline
 
def self.allowed_job_types
# Example return: [:sast, :dast, :dependency_scanning, :container_scanning, :license_management]
# Example return: [:sast, :dast, :dependency_scanning, :container_scanning, :license_management, :coverage_fuzzing]
raise NotImplementedError, 'allowed_job_types must be overwritten to return an array of job types'
end
 
Loading
Loading
Loading
Loading
@@ -13,7 +13,7 @@
module Security
class SecurityJobsFinder < JobsFinder
def self.allowed_job_types
[:sast, :dast, :dependency_scanning, :container_scanning, :secret_detection]
[:sast, :dast, :dependency_scanning, :container_scanning, :secret_detection, :coverage_fuzzing]
end
end
end
# frozen_string_literal: true
module Types
module VulnerabilityLocation
# rubocop: disable Graphql/AuthorizeTypes
class CoverageFuzzingType < BaseObject
graphql_name 'VulnerabilityLocationCoverageFuzzing'
description 'Represents the location of a vulnerability found by a Coverage Fuzzing scan'
field :vulnerable_class, GraphQL::STRING_TYPE, null: true,
description: 'Class containing the vulnerability',
hash_key: :class
field :end_line, GraphQL::STRING_TYPE, null: true,
description: 'Number of the last relevant line in the vulnerable file'
field :file, GraphQL::STRING_TYPE, null: true,
description: 'Path to the vulnerable file'
field :vulnerable_method, GraphQL::STRING_TYPE, null: true,
description: 'Method containing the vulnerability',
hash_key: :method
field :start_line, GraphQL::STRING_TYPE, null: true,
description: 'Number of the first relevant line in the vulnerable file'
end
end
end
Loading
Loading
@@ -11,7 +11,8 @@ module Types
VulnerabilityLocation::DependencyScanningType,
VulnerabilityLocation::DastType,
VulnerabilityLocation::SastType,
VulnerabilityLocation::SecretDetectionType
VulnerabilityLocation::SecretDetectionType,
VulnerabilityLocation::CoverageFuzzingType
 
def self.resolve_type(object, context)
case object[:report_type]
Loading
Loading
@@ -25,6 +26,8 @@ module Types
VulnerabilityLocation::SastType
when 'secret_detection'
VulnerabilityLocation::SecretDetectionType
when 'coverage_fuzzing'
VulnerabilityLocation::CoverageFuzzingType
else
raise UnexpectedReportType, "Report type must be one of #{::Vulnerabilities::Occurrence::REPORT_TYPES.keys}"
end
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment