Skip to content

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 in ee/app/models/package_metadata/epss.rb (see ee/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 to Identifier 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 through Vulnerability (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 under ee/app/graphql/types and include field :cve and field :score, similarly to ee/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 type EpssType to ee/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.'
Edited by Yasha Rise