Skip to content

Commit

Permalink
Implement get cve for packages endpoint and get rid of parsing db (#13)
Browse files Browse the repository at this point in the history
Parsing the db entities into business domain objects does not seem to bring any upside in our use-case.
  • Loading branch information
fwilhe authored Jun 13, 2024
1 parent f64e7a2 commit ed0a201
Show file tree
Hide file tree
Showing 22 changed files with 298 additions and 103 deletions.
11 changes: 11 additions & 0 deletions api-examples/Get CVE by Id.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: Get CVE by Id
type: http
seq: 2
}

get {
url: http://localhost:8080/v1/cves/CVE-2024-1547
body: none
auth: none
}
11 changes: 11 additions & 0 deletions api-examples/Get CVEs by Distro Codename Packages.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: Get CVEs by Distro Codename Packages
type: http
seq: 4
}

get {
url: http://localhost:8080/v1/cves/debian_linux/bookworm/packages/vim,firefox-esr
body: none
auth: none
}
11 changes: 11 additions & 0 deletions api-examples/Get CVEs by Distro Codename.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: Get CVEs by Distro Codename
type: http
seq: 3
}

get {
url: http://localhost:8080/v1/cves/debian_linux/bookworm
body: none
auth: none
}
11 changes: 11 additions & 0 deletions api-examples/Get CVEs by Distro Version Packages.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: Get CVEs by Distro Version Packages
type: http
seq: 5
}

get {
url: http://localhost:8080/v1/cves/debian_linux/version/12/packages/vim,firefox-esr
body: none
auth: none
}
1 change: 1 addition & 0 deletions api-examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Get https://www.usebruno.com to use those api example requests to play with the api.
9 changes: 9 additions & 0 deletions api-examples/bruno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"version": "1",
"name": "glvd",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}
28 changes: 26 additions & 2 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,34 @@ include::{snippets}/getCve/http-response.adoc[]

=== Get a list of CVEs by distro

To query all CVEs for a given distribution, you may use this endpoint:
To query all CVEs for a given distribution by codename, you may use this endpoint:

include::{snippets}/getCveForDistro/curl-request.adoc[]

The expected response looks like this:

include::{snippets}/getCveForDistro/http-response.adoc[]
include::{snippets}/getCveForDistro/http-response.adoc[]

To use a specific distribution version number, use this endpoint:

include::{snippets}/getCveForDistroByVersion/curl-request.adoc[]

The expected response looks like this:

include::{snippets}/getCveForDistroByVersion/http-response.adoc[]

=== Get a list of CVEs for packages by distro

include::{snippets}/getCveForPackages/curl-request.adoc[]

The expected response looks like this:

include::{snippets}/getCveForPackages/http-response.adoc[]

To use a specific distribution version number, use this endpoint:

include::{snippets}/getCveForPackagesByDistroVersion/curl-request.adoc[]

The expected response looks like this:

include::{snippets}/getCveForPackagesByDistroVersion/http-response.adoc[]
36 changes: 29 additions & 7 deletions src/main/java/io/gardenlinux/glvd/GlvdController.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.gardenlinux.glvd;

import io.gardenlinux.glvd.dto.Cve;
import io.gardenlinux.glvd.exceptions.CantParseJSONException;
import io.gardenlinux.glvd.db.CveEntity;
import io.gardenlinux.glvd.db.SourcePackageCve;
import io.gardenlinux.glvd.exceptions.NotFoundException;
import jakarta.annotation.Nonnull;
import org.springframework.http.MediaType;
Expand All @@ -25,14 +25,36 @@ public GlvdController(@Nonnull GlvdService glvdService) {
}

@GetMapping("/{cveId}")
ResponseEntity<Cve> getCveId(@PathVariable("cveId") final String cveId) throws NotFoundException {
ResponseEntity<CveEntity> getCveId(@PathVariable("cveId") final String cveId) throws NotFoundException {
return ResponseEntity.ok().body(glvdService.getCve(cveId));
}

@GetMapping("/{vendor}/{product}/{codename}")
ResponseEntity<List<Cve>> getCveDistro(@PathVariable final String vendor, @PathVariable final String product,
@PathVariable final String codename) throws CantParseJSONException {
return ResponseEntity.ok().body(glvdService.getCveForDistribution(vendor, product, codename));
@GetMapping("/{product}/{codename}")
ResponseEntity<List<SourcePackageCve>> getCveDistro(@PathVariable final String product,
@PathVariable final String codename) {
return ResponseEntity.ok().body(glvdService.getCveForDistribution(product, codename));
}


@GetMapping("/{product}/version/{version}")
ResponseEntity<List<SourcePackageCve>> getCveDistroVersion(@PathVariable final String product,
@PathVariable final String version) {
return ResponseEntity.ok().body(glvdService.getCveForDistributionVersion(product, version));
}


@GetMapping("/{product}/{codename}/packages/{packageList}")
ResponseEntity<List<SourcePackageCve>> getCvePackages(@PathVariable final String product,
@PathVariable final String codename, @PathVariable final String packageList) {
var cveForPackages = glvdService.getCveForPackages(product, codename, packageList);
return ResponseEntity.ok().body(cveForPackages);
}

@GetMapping("/{product}/version/{version}/packages/{packageList}")
ResponseEntity<List<SourcePackageCve>> getCvePackagesVersion(@PathVariable final String product,
@PathVariable final String version, @PathVariable final String packageList) {
var cveForPackages = glvdService.getCveForPackagesVersion(product, version, packageList);
return ResponseEntity.ok().body(cveForPackages);
}

}
42 changes: 23 additions & 19 deletions src/main/java/io/gardenlinux/glvd/GlvdService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
package io.gardenlinux.glvd;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.gardenlinux.glvd.db.CveEntity;
import io.gardenlinux.glvd.db.CveRepository;
import io.gardenlinux.glvd.db.HealthCheckRepository;
import io.gardenlinux.glvd.dto.Cve;
import io.gardenlinux.glvd.db.SourcePackageCve;
import io.gardenlinux.glvd.dto.Readiness;
import io.gardenlinux.glvd.exceptions.CantParseJSONException;
import io.gardenlinux.glvd.exceptions.DbNotConnectedException;
import io.gardenlinux.glvd.exceptions.NotFoundException;
import jakarta.annotation.Nonnull;
Expand All @@ -24,12 +21,9 @@ public class GlvdService {
@Nonnull
private final HealthCheckRepository healthCheckRepository;

private final ObjectMapper objectMapper;

public GlvdService(@Nonnull CveRepository cveRepository, @Nonnull HealthCheckRepository healthCheckRepository) {
this.cveRepository = cveRepository;
this.healthCheckRepository = healthCheckRepository;
this.objectMapper = new ObjectMapper();
}

public Readiness getReadiness() throws DbNotConnectedException {
Expand All @@ -41,23 +35,33 @@ public Readiness getReadiness() throws DbNotConnectedException {
}
}

public Cve getCve(String cveId) throws NotFoundException, CantParseJSONException {
var cveEntity = cveRepository.findById(cveId).orElseThrow(NotFoundException::new);
// Not the most elegant solution. This might be replaced by a VIEW in the database,
// or some other feature in spring data jpa?
private SourcePackageCve parseDbResponse(String input) {
var parts = input.split(",");
var packageName = parts[0];
var cveId = parts[1];
var cvePublishedDate = parts[2];
return new SourcePackageCve(cveId, cvePublishedDate, packageName);
}

return cveEntityDataToDomainEntity(cveEntity);
public CveEntity getCve(String cveId) throws NotFoundException {
return cveRepository.findById(cveId).orElseThrow(NotFoundException::new);
}

public List<Cve> getCveForDistribution(String vendor, String product, String codename) throws CantParseJSONException {
var entities = cveRepository.cvesForDistribution(vendor, product, codename);
public List<SourcePackageCve> getCveForDistribution(String product, String codename) {
return cveRepository.cvesForDistribution(product, codename).stream().map(this::parseDbResponse).toList();
}

return entities.stream().map(this::cveEntityDataToDomainEntity).toList();
public List<SourcePackageCve> getCveForDistributionVersion(String product, String version) {
return cveRepository.cvesForDistributionVersion(product, version).stream().map(this::parseDbResponse).toList();
}

private Cve cveEntityDataToDomainEntity(CveEntity cveEntity) throws CantParseJSONException {
try {
return objectMapper.readValue(cveEntity.getData(), Cve.class);
} catch (JsonProcessingException e) {
throw new CantParseJSONException("Failed to parse JSON object into domain classes:\n====\n" + cveEntity.getData() + "\n====");
}
public List<SourcePackageCve> getCveForPackages(String product, String codename, String packages) {
return cveRepository.cvesForPackageList(product, codename,"{"+packages+"}").stream().map(this::parseDbResponse).toList();
}

public List<SourcePackageCve> getCveForPackagesVersion(String product, String version, String packages) {
return cveRepository.cvesForPackageListVersion(product, version,"{"+packages+"}").stream().map(this::parseDbResponse).toList();
}
}
2 changes: 1 addition & 1 deletion src/main/java/io/gardenlinux/glvd/db/CveEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ public int hashCode() {
return result;
}

}
}
82 changes: 70 additions & 12 deletions src/main/java/io/gardenlinux/glvd/db/CveRepository.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,85 @@
package io.gardenlinux.glvd.db;

import io.gardenlinux.glvd.dto.Cve;
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 CveRepository extends JpaRepository<CveEntity, String> {

@Query(value = """
SELECT
all_cve.*
FROM
all_cve
INNER JOIN deb_cve USING (cve_id)
INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id)
WHERE
dist_cpe.cpe_vendor = ?1 AND
dist_cpe.cpe_product = ?2 and
dist_cpe.deb_codename = ?3
ORDER BY
deb_cve.deb_source AS source_package,
all_cve.cve_id AS cve_id,
all_cve."data" ->> 'published' AS cve_published_date
FROM
all_cve
INNER JOIN deb_cve USING (cve_id)
INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id)
WHERE
dist_cpe.cpe_product = :product AND
dist_cpe.deb_codename = :codename AND
deb_cve.debsec_vulnerable = TRUE
ORDER BY
all_cve.cve_id
""", nativeQuery = true)
List<String> cvesForDistribution(@Param("product") String product, @Param("codename") String codename);

@Query(value = """
SELECT
deb_cve.deb_source AS source_package,
all_cve.cve_id AS cve_id,
all_cve."data" ->> 'published' AS cve_published_date
FROM
all_cve
INNER JOIN deb_cve USING (cve_id)
INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id)
WHERE
dist_cpe.cpe_product = :product AND
dist_cpe.cpe_version = :version AND
deb_cve.debsec_vulnerable = TRUE
ORDER BY
all_cve.cve_id
""", nativeQuery = true)
List<CveEntity> cvesForDistribution(String vendor, String product, String codename);
List<String> cvesForDistributionVersion(@Param("product") String product, @Param("version") String version);

@Query(value = """
SELECT
deb_cve.deb_source AS source_package,
all_cve.cve_id AS cve_id,
all_cve."data" ->> 'published' AS cve_published_date
FROM
all_cve
INNER JOIN deb_cve USING (cve_id)
INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id)
WHERE
dist_cpe.cpe_product = :product AND
dist_cpe.deb_codename = :codename AND
deb_cve.deb_source = ANY(:packages ::TEXT[]) AND
deb_cve.debsec_vulnerable = TRUE
ORDER BY
all_cve.cve_id
""", nativeQuery = true)
List<String> cvesForPackageList(@Param("product") String product, @Param("codename") String codename, @Param("packages") String packages);

@Query(value = """
SELECT
deb_cve.deb_source AS source_package,
all_cve.cve_id AS cve_id,
all_cve."data" ->> 'published' AS cve_published_date
FROM
all_cve
INNER JOIN deb_cve USING (cve_id)
INNER JOIN dist_cpe ON (deb_cve.dist_id = dist_cpe.id)
WHERE
dist_cpe.cpe_product = :product AND
dist_cpe.cpe_version = :version AND
deb_cve.deb_source = ANY(:packages ::TEXT[]) AND
deb_cve.debsec_vulnerable = TRUE
ORDER BY
all_cve.cve_id
""", nativeQuery = true)
List<String> cvesForPackageListVersion(@Param("product") String product, @Param("version") String version, @Param("packages") String packages);

}
64 changes: 64 additions & 0 deletions src/main/java/io/gardenlinux/glvd/db/SourcePackageCve.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.gardenlinux.glvd.db;

import jakarta.annotation.Nonnull;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

import java.util.Objects;

@Entity
public class SourcePackageCve {

@Id
@Column(name = "cve_id", nullable = false)
private String id;

@Column(name = "cve_published_date", nullable = false)
@Nonnull
private String cvePublishedDate;

@Column(name = "source_package", nullable = false)
@Nonnull
private String sourcePackage;

public SourcePackageCve() {
}

public SourcePackageCve(String id, @Nonnull String cvePublishedDate, @Nonnull String sourcePackage) {
this.id = id;
this.cvePublishedDate = cvePublishedDate;
this.sourcePackage = sourcePackage;
}

public String getId() {
return id;
}

@Nonnull
public String getCvePublishedDate() {
return cvePublishedDate;
}

@Nonnull
public String getSourcePackage() {
return sourcePackage;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

SourcePackageCve that = (SourcePackageCve) o;
return Objects.equals(id, that.id) && cvePublishedDate.equals(that.cvePublishedDate) && sourcePackage.equals(that.sourcePackage);
}

@Override
public int hashCode() {
int result = Objects.hashCode(id);
result = 31 * result + cvePublishedDate.hashCode();
result = 31 * result + sourcePackage.hashCode();
return result;
}
}
Loading

0 comments on commit ed0a201

Please sign in to comment.