diff --git a/.gitignore b/.gitignore index 5f24223..32cb064 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ out/ ### Unbase OCI ### *.oci -glvd.sql + +# DB Dump for DevDb container image +/glvd.sql diff --git a/DevDb.Containerfile b/DevDb.Containerfile new file mode 100644 index 0000000..0b6dfdd --- /dev/null +++ b/DevDb.Containerfile @@ -0,0 +1,3 @@ +FROM ghcr.io/gardenlinux/glvd-postgres:latest + +COPY glvd.sql /docker-entrypoint-initdb.d/glvd.sql diff --git a/README.md b/README.md index 1207eb2..b3c0d72 100644 --- a/README.md +++ b/README.md @@ -32,26 +32,32 @@ To build the app, ensure the test database is running: ## Running the Application Locally -1. Start the application database: +1. Get a dump of the Database (this needs the GitHub `gh` cli and `jq`) + +```bash +./download-db-dump.sh +``` + +2. Start the application database: ```bash ./start-db-for-app.sh ``` -2. Build and run the Spring Boot app: +3. Build and run the Spring Boot app: ```bash ./gradlew bootRun ``` -3. After startup, check readiness: +4. After startup, check readiness: ``` curl http://localhost:8080/readiness # Should return status code 200 ``` -4. Open http://localhost:8080 in your web browser to use the UI +5. Open http://localhost:8080 in your web browser to use the UI ## Example Requests diff --git a/download-db-dump.sh b/download-db-dump.sh new file mode 100755 index 0000000..2d1b7c4 --- /dev/null +++ b/download-db-dump.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +LATEST_RUN_ID=$(gh run list --repo gardenlinux/glvd-data-ingestion --branch main --workflow 02-ingest-dump-snapshot.yaml --json databaseId --limit 1 | jq -r '.[0].databaseId') +gh run download $LATEST_RUN_ID -n glvd.sql --repo gardenlinux/glvd-data-ingestion diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index 8a5a8c6..4d8dbc6 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -71,6 +71,25 @@ Example response: include::{snippets}/getCveForDistro/http-response.adoc[] +=== List CVEs by Image + +Retrieve all CVEs for a given Garden Linux image and version. +This applies a filter for the packages in the specified image. + +Supported images are currently: + +- `ali-gardener_prod` +- `aws-gardener_prod` +- `azure-gardener_prod` +- `gcp-gardener_prod` +- `openstack-gardener_prod` + +include::{snippets}/getCveForImage/curl-request.adoc[] + +Example response: + +include::{snippets}/getCveForImage/http-response.adoc[] + === List CVEs for Packages by Distribution Retrieve all CVEs for a list of packages in a specified distribution. diff --git a/src/main/java/io/gardenlinux/glvd/GlvdController.java b/src/main/java/io/gardenlinux/glvd/GlvdController.java index db48e05..de0ae75 100644 --- a/src/main/java/io/gardenlinux/glvd/GlvdController.java +++ b/src/main/java/io/gardenlinux/glvd/GlvdController.java @@ -35,6 +35,20 @@ gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSi ); } + @GetMapping("/cves/{gardenlinuxVersion}/image/{gardenlinuxImage}") + ResponseEntity> getCveImage( + @PathVariable final String gardenlinuxVersion, + @PathVariable final String gardenlinuxImage, + @RequestParam(defaultValue = "cveId") final String sortBy, + @RequestParam(defaultValue = "ASC") final String sortOrder, + @RequestParam(required = false) final String pageNumber, + @RequestParam(required = false) final String pageSize + ) { + return ResponseEntity.ok().body(glvdService.getCveForImage( + gardenlinuxImage, gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize)) + ); + } + @GetMapping("/cves/{gardenlinuxVersion}/packages/{packageList}") ResponseEntity> getCvePackages( @PathVariable final String gardenlinuxVersion, diff --git a/src/main/java/io/gardenlinux/glvd/GlvdService.java b/src/main/java/io/gardenlinux/glvd/GlvdService.java index d4bc801..3fa7431 100644 --- a/src/main/java/io/gardenlinux/glvd/GlvdService.java +++ b/src/main/java/io/gardenlinux/glvd/GlvdService.java @@ -33,6 +33,9 @@ public class GlvdService { @Nonnull private final SourcePackageRepository sourcePackageRepository; + @Nonnull + private final ImageSourcePackageCveRepository imageSourcePackageCveRepository; + @Nonnull private final CveDetailsRepository cveDetailsRepository; @@ -62,9 +65,10 @@ public class GlvdService { Logger logger = LoggerFactory.getLogger(GlvdService.class); - public GlvdService(@Nonnull SourcePackageCveRepository sourcePackageCveRepository, @Nonnull SourcePackageRepository sourcePackageRepository, @Nonnull CveDetailsRepository cveDetailsRepository, @Nonnull CveContextRepository cveContextRepository, @Nonnull DistCpeRepository distCpeRepository, @Nonnull NvdExclusiveCveRepository nvdExclusiveCveRepository, @Nonnull DebSrcRepository debSrcRepository, @Nonnull KernelCveRepository kernelCveRepository, @Nonnull KernelCveDetailsRepository kernelCveDetailsRepository, @Nonnull NvdCveRepository nvdCveRepository, @Nonnull TriageRepository triageRepository) { + public GlvdService(@Nonnull SourcePackageCveRepository sourcePackageCveRepository, @Nonnull SourcePackageRepository sourcePackageRepository, @Nonnull ImageSourcePackageCveRepository imageSourcePackageCveRepository, @Nonnull CveDetailsRepository cveDetailsRepository, @Nonnull CveContextRepository cveContextRepository, @Nonnull DistCpeRepository distCpeRepository, @Nonnull NvdExclusiveCveRepository nvdExclusiveCveRepository, @Nonnull DebSrcRepository debSrcRepository, @Nonnull KernelCveRepository kernelCveRepository, @Nonnull KernelCveDetailsRepository kernelCveDetailsRepository, @Nonnull NvdCveRepository nvdCveRepository, @Nonnull TriageRepository triageRepository) { this.sourcePackageCveRepository = sourcePackageCveRepository; this.sourcePackageRepository = sourcePackageRepository; + this.imageSourcePackageCveRepository = imageSourcePackageCveRepository; this.cveDetailsRepository = cveDetailsRepository; this.cveContextRepository = cveContextRepository; this.distCpeRepository = distCpeRepository; @@ -108,6 +112,22 @@ private Pageable determinePageAndSortFeatures2(SortAndPageOptions sortAndPageOpt return Pageable.unpaged(); } + public List getCveForImage(String image, String gardenlinuxVersion, SortAndPageOptions sortAndPageOptions) { + var cvesExcludingKernel = imageSourcePackageCveRepository.findByGardenlinuxVersionAndGardenlinuxImageName( + gardenlinuxVersion, image, determinePageAndSortFeatures(sortAndPageOptions)) + .stream() + .filter(CvesByStatusRejectedxx()) + .toList(); + + var kernelCves = kernelCveRepository.findByGardenlinuxVersion(gardenlinuxVersion) + .stream() + .map(kernelCve -> new ImageSourcePackageCve(kernelCve.getCveId(), kernelCve.getSourcePackageName(), kernelCve.getSourcePackageVersion(), kernelCve.getGardenlinuxVersion(), "", "", "", kernelCve.isVulnerable(), kernelCve.getCvePublishedDate(), kernelCve.getCveLastModifiedDate(), kernelCve.getCveLastIngestedDate(), kernelCve.getVulnStatus(), kernelCve.getBaseScore(), kernelCve.getVectorString(), kernelCve.getBaseScoreV40(), kernelCve.getBaseScoreV31(), kernelCve.getBaseScoreV30(), kernelCve.getBaseScoreV2(), kernelCve.getVectorStringV40(), kernelCve.getVectorStringV31(), kernelCve.getVectorStringV30(), kernelCve.getVectorStringV2())) + .filter(CvesByStatusRejectedxx()) + .toList(); + + return Stream.concat(cvesExcludingKernel.stream(), kernelCves.stream()).toList(); + } + public List getCveForDistribution(String gardenlinuxVersion, SortAndPageOptions sortAndPageOptions) { var cvesExcludingKernel = sourcePackageCveRepository.findByGardenlinuxVersion( gardenlinuxVersion, determinePageAndSortFeatures(sortAndPageOptions)) @@ -128,6 +148,11 @@ private static Predicate CvesByStatusRejected() { return cve -> !cve.getVulnStatus().equalsIgnoreCase("Rejected"); } + // fixme + private static Predicate CvesByStatusRejectedxx() { + return cve -> !cve.getVulnStatus().equalsIgnoreCase("Rejected"); + } + public List kernelCvesForGardenLinuxVersion(String gardenlinuxVersion) { return kernelCveRepository.findByGardenlinuxVersion(gardenlinuxVersion); } diff --git a/src/main/java/io/gardenlinux/glvd/UiController.java b/src/main/java/io/gardenlinux/glvd/UiController.java index 8f4b777..75d9625 100644 --- a/src/main/java/io/gardenlinux/glvd/UiController.java +++ b/src/main/java/io/gardenlinux/glvd/UiController.java @@ -1,5 +1,6 @@ package io.gardenlinux.glvd; +import io.gardenlinux.glvd.db.ImageSourcePackageCve; import io.gardenlinux.glvd.db.SourcePackageCve; import io.gardenlinux.glvd.exceptions.CveNotKnownException; import jakarta.annotation.Nonnull; @@ -39,6 +40,33 @@ gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSi return "getPackagesForDistro"; } + @GetMapping("/getCveForImage") + public String getCveForImage( + @RequestParam(name = "gardenlinuxVersion", required = true) String gardenlinuxVersion, + @RequestParam(name = "imageName", required = true) String imageName, + @RequestParam(defaultValue = "baseScore") final String sortBy, + @RequestParam(defaultValue = "DESC") final String sortOrder, + @RequestParam(required = false) final String pageNumber, + @RequestParam(required = false) final String pageSize, + @RequestParam(required = false, defaultValue = "true") final boolean onlyVulnerable, + Model model + ) { + var sourcePackageCves = glvdService.getCveForImage( + imageName, gardenlinuxVersion, new SortAndPageOptions(sortBy, sortOrder, pageNumber, pageSize) + ) + .stream() + .filter(ImageSourcePackageCve::isVulnerable) + .filter(sourcePackageCve -> !sourcePackageCve.getVulnStatus().equalsIgnoreCase("Rejected")) + .toList(); + var contexts = glvdService.getCveContextsForDist(glvdService.distVersionToId(gardenlinuxVersion)); + model.addAttribute("sourcePackageCves", sourcePackageCves); + model.addAttribute("gardenlinuxVersion", gardenlinuxVersion); + model.addAttribute("imageName", imageName); + model.addAttribute("onlyVulnerable", onlyVulnerable); + model.addAttribute("cveContexts", contexts); + return "getCveForImage"; + } + @GetMapping("/getCveForDistribution") public String getCveForDistribution( @RequestParam(name = "gardenlinuxVersion", required = true) String gardenlinuxVersion, diff --git a/src/main/java/io/gardenlinux/glvd/db/ImageSourcePackageCve.java b/src/main/java/io/gardenlinux/glvd/db/ImageSourcePackageCve.java new file mode 100644 index 0000000..6d1f498 --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/db/ImageSourcePackageCve.java @@ -0,0 +1,194 @@ +package io.gardenlinux.glvd.db; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity +@Table(name = "imagesourcepackagecve") +public class ImageSourcePackageCve { + + @Id + @Column(name = "cve_id", nullable = false) + private String cveId; + + @Column(name = "source_package_name", nullable = false) + private String sourcePackageName; + + @Column(name = "source_package_version", nullable = false) + private String sourcePackageVersion; + + @Column(name = "gardenlinux_version", nullable = false) + private String gardenlinuxVersion; + + @Column(name = "gardenlinux_image_name", nullable = false) + private String gardenlinuxImageName; + + @Column(name = "gardenlinux_image_version", nullable = false) + private String gardenlinuxImageVersion; + + @Column(name = "gardenlinux_image_commit_id", nullable = false) + private String gardenlinuxImageCommitId; + + @Column(name = "is_vulnerable", nullable = false) + private boolean isVulnerable; + + @Column(name = "cve_published_date", nullable = false) + private String cvePublishedDate; + + @Column(name = "cve_last_modified_date", nullable = false) + private String cveLastModifiedDate; + + @Column(name = "cve_last_ingested_date", nullable = false) + private String cveLastIngestedDate; + + @Column(name = "vuln_status", nullable = false) + private String vulnStatus; + + @Column(name = "base_score", nullable = true) + private Float baseScore; + + @Column(name = "vector_string", nullable = true) + private String vectorString; + + @Column(name = "base_score_v40", nullable = true) + private Float baseScoreV40; + + @Column(name = "base_score_v31", nullable = true) + private Float baseScoreV31; + + @Column(name = "base_score_v30", nullable = true) + private Float baseScoreV30; + + @Column(name = "base_score_v2", nullable = true) + private Float baseScoreV2; + + @Column(name = "vector_string_v40", nullable = true) + private String vectorStringV40; + + @Column(name = "vector_string_v31", nullable = true) + private String vectorStringV31; + + @Column(name = "vector_string_v30", nullable = true) + private String vectorStringV30; + + @Column(name = "vector_string_v2", nullable = true) + private String vectorStringV2; + + public ImageSourcePackageCve() { + } + + public ImageSourcePackageCve(String cveId, String sourcePackageName, String sourcePackageVersion, String gardenlinuxVersion, String gardenlinuxImageName, String gardenlinuxImageVersion, String gardenlinuxImageCommitId, boolean isVulnerable, String cvePublishedDate, String cveLastModifiedDate, String cveLastIngestedDate, String vulnStatus, Float baseScore, String vectorString, Float baseScoreV40, Float baseScoreV31, Float baseScoreV30, Float baseScoreV2, String vectorStringV40, String vectorStringV31, String vectorStringV30, String vectorStringV2) { + this.cveId = cveId; + this.sourcePackageName = sourcePackageName; + this.sourcePackageVersion = sourcePackageVersion; + this.gardenlinuxVersion = gardenlinuxVersion; + this.gardenlinuxImageName = gardenlinuxImageName; + this.gardenlinuxImageVersion = gardenlinuxImageVersion; + this.gardenlinuxImageCommitId = gardenlinuxImageCommitId; + this.isVulnerable = isVulnerable; + this.cvePublishedDate = cvePublishedDate; + this.cveLastModifiedDate = cveLastModifiedDate; + this.cveLastIngestedDate = cveLastIngestedDate; + this.vulnStatus = vulnStatus; + this.baseScore = baseScore; + this.vectorString = vectorString; + this.baseScoreV40 = baseScoreV40; + this.baseScoreV31 = baseScoreV31; + this.baseScoreV30 = baseScoreV30; + this.baseScoreV2 = baseScoreV2; + this.vectorStringV40 = vectorStringV40; + this.vectorStringV31 = vectorStringV31; + this.vectorStringV30 = vectorStringV30; + this.vectorStringV2 = vectorStringV2; + } + + public String getCveId() { + return cveId; + } + + public String getSourcePackageName() { + return sourcePackageName; + } + + public String getSourcePackageVersion() { + return sourcePackageVersion; + } + + public String getGardenlinuxVersion() { + return gardenlinuxVersion; + } + + public String getGardenlinuxImageName() { + return gardenlinuxImageName; + } + + public String getGardenlinuxImageVersion() { + return gardenlinuxImageVersion; + } + + public String getGardenlinuxImageCommitId() { + return gardenlinuxImageCommitId; + } + + public boolean isVulnerable() { + return isVulnerable; + } + + public String getCvePublishedDate() { + return cvePublishedDate; + } + + public String getCveLastModifiedDate() { + return cveLastModifiedDate; + } + + public String getCveLastIngestedDate() { + return cveLastIngestedDate; + } + + public String getVulnStatus() { + return vulnStatus; + } + + public Float getBaseScore() { + return baseScore; + } + + public String getVectorString() { + return vectorString; + } + + public Float getBaseScoreV40() { + return baseScoreV40; + } + + public Float getBaseScoreV31() { + return baseScoreV31; + } + + public Float getBaseScoreV30() { + return baseScoreV30; + } + + public Float getBaseScoreV2() { + return baseScoreV2; + } + + public String getVectorStringV40() { + return vectorStringV40; + } + + public String getVectorStringV31() { + return vectorStringV31; + } + + public String getVectorStringV30() { + return vectorStringV30; + } + + public String getVectorStringV2() { + return vectorStringV2; + } +} \ No newline at end of file diff --git a/src/main/java/io/gardenlinux/glvd/db/ImageSourcePackageCveRepository.java b/src/main/java/io/gardenlinux/glvd/db/ImageSourcePackageCveRepository.java new file mode 100644 index 0000000..6f9a869 --- /dev/null +++ b/src/main/java/io/gardenlinux/glvd/db/ImageSourcePackageCveRepository.java @@ -0,0 +1,53 @@ +package io.gardenlinux.glvd.db; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ImageSourcePackageCveRepository extends JpaRepository { + + List findBySourcePackageName( + @Param("source_package_name") String source_package_name, + Pageable pageable + ); + + List findBySourcePackageNameAndSourcePackageVersion( + @Param("source_package_name") String source_package_name, + @Param("source_package_version") String source_package_version, + Pageable pageable + ); + + List findByCveIdAndGardenlinuxVersion( + @Param("cve_id") String cve_id, + @Param("gardenlinux_version") String gardenlinux_version, + Pageable pageable + ); + + List findByGardenlinuxVersion( + @Param("gardenlinux_version") String gardenlinux_version, + Pageable pageable + ); + + List findByGardenlinuxVersionAndGardenlinuxImageName( + @Param("gardenlinux_version") String gardenlinux_version, + @Param("gardenlinux_image_name") String gardenlinux_image_name, + Pageable pageable + ); + + // would be nice if we did not need a native query here + // is this (the in-array search for packages) possible in any other way with spring data jpa? + // fixme: does not support sorting, cf https://github.com/spring-projects/spring-data-jpa/issues/2504#issuecomment-1527743003 + // pagination seems to work ok + @Query(value = """ + SELECT * FROM imagesourcepackagecve + WHERE source_package_name = ANY(:source_package_names ::TEXT[]) AND gardenlinux_version = :gardenlinux_version + """, nativeQuery = true) + List findBySourcePackageNameInAndGardenlinuxVersion( + @Param("source_package_names") String source_package_names, + @Param("gardenlinux_version") String gardenlinux_version, + Pageable pageable + ); +} diff --git a/src/main/java/io/gardenlinux/glvd/db/SourcePackageCve.java b/src/main/java/io/gardenlinux/glvd/db/SourcePackageCve.java index 5b3e3d4..f6d006f 100644 --- a/src/main/java/io/gardenlinux/glvd/db/SourcePackageCve.java +++ b/src/main/java/io/gardenlinux/glvd/db/SourcePackageCve.java @@ -6,7 +6,7 @@ import jakarta.persistence.Table; @Entity -@Table(name = "sourcepackagecve") +@Table(name = "sourcepackagecve_anyimage") public class SourcePackageCve { @Id diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html index 6676fe1..593224a 100644 --- a/src/main/resources/static/index.html +++ b/src/main/resources/static/index.html @@ -93,6 +93,7 @@ document.addEventListener("DOMContentLoaded", function () { populateGardenlinuxVersions("gardenlinuxVersion"); + populateGardenlinuxVersions("imageGardenlinuxVersion"); populateGardenlinuxVersions("packagesGardenlinuxVersion"); populateGardenlinuxVersions("distroGardenlinuxVersion"); populateGardenlinuxVersions("releaseNotesGardenlinuxVersion"); @@ -100,7 +101,6 @@ }); -

Welcome to the Garden Linux Vulnerability Database

GLVD (Garden Linux Vulnerability Database) is an application for tracking security issues in @@ -125,10 +125,30 @@

Welcome to the Garden Linux Vulnerability Database

+ + +
+ + + + + + +
+
- +