Add EPSS to GitLab database and enable querying from GraphQL API
Introduction
As part of EPSS support, we need to save EPSS data to the GitLab database. This data will initially be queried through the GraphQL API, and later it will be used in the vulnerability report and vulnerability details pages.
We will create a new pm_epss
table and associate epss data with vulnerability models to enable querying through the GraphQL vulnerability type.
The reason we are creating a new pm_epss
table and not populating an existing table with EPSS data is due to the nature of EPSS score updates. EPSS scores are regenerated every day for all CVEs. Updating all CVE advisories is a heavy operation. A later join is seemingly preferable.
GitLab uses Active Record for database access. See Active Record Guide for more information.
This slack discussion (90 day retention) is a great source for understanding implementation ideas.
Ultimately, we should be able to retrieve EPSS scores with the following GraphQL request:
{
project(fullPath: "gitlab-org/gitlab") {
vulnerabilities {
nodes {
identifiers {
externalId
externalType
}
epssScore {
score
cve
}
}
}
}
}
Implementation
Add EPSS to DB
-
Create a rails migration for a new pm_epss
table. For example, see !117326 (merged). Read further on creating migrations, namely creating new tables.-
Add a cve
text field. It should be at least 13 characters long. Although it's length is technically not limited, we can assume it won't be longer than 24 characters. -
Add a score
float field. -
Should we add an index?
-
-
Create an Epss
ApplicationRecord
inee/app/models/package_metadata/epss.rb
(seeee/app/models/package_metadata/advisory.rb
for example)-
Validate cve
to be present and match a CVE ID format. -
Validate score
to be of the expected format (float with two digits after the dot) if possible.
-
-
Add a has_one
association toIdentifier
to associate CVEs with their EPSS scores:has_one :epss, -> { with_external_type('cve') }, primary_key: :external_id, foreign_key: 'cve_id', inverse_of: false
-
Return nil
if there's no associated EPSS score.
-
-
Delegate EPSS scores to Finding
throughVulnerability
(is this needed if we're exposing the EPSS score only in the Vulnerability type in GraphQL?):
Click to expand
diff --git a/ee/app/models/ee/vulnerability.rb b/ee/app/models/ee/vulnerability.rb
index 6b886e62f67d..0d5f7494e388 100644
--- a/ee/app/models/ee/vulnerability.rb
+++ b/ee/app/models/ee/vulnerability.rb
@@ -176,7 +176,7 @@ def with_vulnerability_links
delegate :name, to: :group, prefix: true, allow_nil: true
delegate :solution, :identifiers, :links, :remediations, :file,
- :cve_value, :cwe_value, :other_identifier_values, :location,
+ :cve_value, :cwe_value, :other_identifier_values, :location, :epss_scores,
to: :finding, allow_nil: true
delegate :file, to: :finding, prefix: true, private: true
diff --git a/ee/app/models/vulnerabilities/finding.rb b/ee/app/models/vulnerabilities/finding.rb
index 528e22283f66..4ef91aae4db8 100644
--- a/ee/app/models/vulnerabilities/finding.rb
+++ b/ee/app/models/vulnerabilities/finding.rb
@@ -36,6 +36,7 @@ class Finding < ApplicationRecord
has_many :finding_identifiers, class_name: 'Vulnerabilities::FindingIdentifier', inverse_of: :finding, foreign_key: 'occurrence_id'
has_many :identifiers, through: :finding_identifiers, class_name: 'Vulnerabilities::Identifier'
+ has_many :epss_scores, through: :identifiers
has_many :finding_links, class_name: 'Vulnerabilities::FindingLink', inverse_of: :finding, foreign_key: 'vulnerability_occurrence_id'
Add EPSS to GraphQL
-
Create an epss_type.rb
underee/app/graphql/types
and includefield :cve
andfield :score
, similarly toee/app/graphql/types/vulnerabilities/cvss_type.rb
.-
cve
- non-nullable string. -
score
- nullable float. -
But how do we query EPSS data from a separate table here?
-
-
Add field :epss
of typeEpssType
toee/app/graphql/types/vulnerability_type.rb
.-
This field should be nullable
-
field :epss_score,
Types::EpssType, # Define this and expose all the fields for the EPSS Score
null: true,
description: 'EPSS score for CVE identifiers.'