diff --git a/README.md b/README.md index f68b28a..5c4e1b2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ONGuard -The ONGuard (OSV - NVD Guard) service integrates OSV and NVD in order to retrieve CVE vulnerabilities from +The ONGuard (OSV Guard) service integrates OSV in order to retrieve CVE vulnerabilities from the given set of package urls (purls). Upon receiving a collection of `purls`: @@ -42,17 +42,15 @@ the OSV Service will return with a collection of vulnerabilities. ``` We need to expand each of these vulnerability IDs and for that the service will call retrieve the OSV data -that contains useful remediation information, aliases (including the CVE), summary and title but we also need to -retrieve the CVSS metrics and this data is retrieved from the NVD service. +that contains useful remediation information, aliases (including the CVE), summary, title and metrics. That means the service has to perform 2 requests: - OSV /vulns/{vulnId} -- NVD /rest/json/cves/2.0?cveId={cveId} -As you can imagine. That implies (2 * number of vulnerabilities) + 1 requests for each request. Where in a normal-sized -Java project with 150 dependencies (direct and transitive) will be 201 HTTP requests. +As you can imagine. That implies (number of vulnerabilities) + 1 requests for each request. Where in a normal-sized +Java project with 150 dependencies (direct and transitive) will be 101 HTTP requests. -For that we have added a Redis cache in order to save the aggregated data and also to cache the requests to OSV. +For that we have added a Redis cache in order to cache the individual requests to OSV. The final result of the aggregated data will look like this: @@ -111,13 +109,6 @@ The OpenAPI Schema can be retrieved in the management endpoint at http://localho ## Running the application -### Required parameters - -The NVD database has a rate limit that is more permissive when using an api Key. You can get one by filling in [this form](https://nvd.nist.gov/developers/request-an-api-key) - -Once you apply and receive it you can configure it with the following configuration parameter: `api.nvd.key=` - - ### Running the application locally The application depends on Redis and uses the JSON capability. You can either connect to an existing instance or use the `TestContainers` framework to spin up one for you. @@ -127,7 +118,7 @@ The application depends on Redis and uses the JSON capability. You can either co In this case, as it is the default configuration, you only need to provide the apiKey. ```shell script -./mvnw compile quarkus:dev -Dapi.nvd.key= +./mvnw compile quarkus:dev ``` * Note: If you're having issues with Podman and TestContainers you can check the [Quarkus Blog](https://quarkus.io/blog/quarkus-devservices-testcontainers-podman/) and the [Quarkus Podman guide](https://quarkus.io/guides/podman) @@ -143,7 +134,7 @@ podman run -d --rm -p 6379:6379 -p 8001:8001 --name redis-stack redis/redis-stac You can run your application in dev mode that enables live coding using: ```shell script -./mvnw compile quarkus:dev -Dapi.nvd.key= -Dquarkus.redis.hosts=redis://localhost:6379/ +./mvnw compile quarkus:dev -Dquarkus.redis.hosts=redis://localhost:6379/ ``` ## Packaging and running the application diff --git a/deploy/onguard.yaml b/deploy/onguard.yaml index a1051d4..98889c4 100644 --- a/deploy/onguard.yaml +++ b/deploy/onguard.yaml @@ -30,11 +30,6 @@ spec: memory: "128Mi" cpu: "500m" env: - - name: API_NVD_APIKEY - valueFrom: - secretKeyRef: - name: exhort-onguard-secret - key: api-nvd-apikey - name: DB_REDIS_HOST valueFrom: secretKeyRef: @@ -75,81 +70,3 @@ spec: targetPort: 9000 selector: app: onguard ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: onguard-ingester - labels: - app: onguard-ingester -spec: - replicas: 1 - selector: - matchLabels: - app: onguard-ingester - template: - metadata: - labels: - app: onguard-ingester - spec: - containers: - - name: onguard-ingester - image: onguard:latest - imagePullPolicy: IfNotPresent - ports: - - name: management - containerPort: 9000 - protocol: TCP - resources: - limits: - memory: "128Mi" - cpu: "500m" - env: - - name: DB_REDIS_HOST - valueFrom: - secretKeyRef: - name: exhort-onguard-secret - key: db.host - - name: DB_REDIS_PORT - valueFrom: - secretKeyRef: - name: exhort-onguard-secret - key: db.port - - name: API_NVD_APIKEY - valueFrom: - secretKeyRef: - name: exhort-onguard-secret - key: api-nvd-apikey - - name: QUARKUS_PROFILE - value: ingester,prod - - name: ONGUARD_PASSWORD - valueFrom: - secretKeyRef: - name: exhort-onguard-secret - key: admin.pwd - livenessProbe: - httpGet: - path: /q/health/live - port: 9000 - initialDelaySeconds: 1 - readinessProbe: - httpGet: - path: /q/health/ready - port: 9000 - initialDelaySeconds: 5 - periodSeconds: 20 ---- -apiVersion: v1 -kind: Service -metadata: - name: onguard-ingester - labels: - app: onguard-ingester -spec: - ports: - - name: management - port: 9000 - protocol: TCP - targetPort: 9000 - selector: - app: onguard-ingester diff --git a/deploy/openshift/template.yaml b/deploy/openshift/template.yaml index d89c6ad..e41e30a 100644 --- a/deploy/openshift/template.yaml +++ b/deploy/openshift/template.yaml @@ -87,124 +87,6 @@ objects: containerPort: '${{MANAGEMENT_PORT}}' protocol: TCP env: - - name: API_NVD_APIKEY - valueFrom: - secretKeyRef: - name: exhort-secret - key: api-nvd-apikey - - name: DB_REDIS_HOST - valueFrom: - secretKeyRef: - name: '${ELASTICACHE_SECRET}' - key: db.endpoint - - name: DB_REDIS_PORT - valueFrom: - secretKeyRef: - name: '${ELASTICACHE_SECRET}' - key: db.port - - name: QUARKUS_HTTP_PORT - value: '8080' - - name: QUARKUS_MANAGEMENT_PORT - value: '9000' - - name: QUARKUS_REDIS_MAX_POOL_SIZE - value: '20' - - name: QUARKUS_REDIS_MAX_POOL_WAITING - value: '100' - securityContext: - runAsNonRoot: true - resources: - limits: - cpu: ${CPU_LIMIT} - memory: ${MEMORY_LIMIT} - requests: - cpu: ${CPU_REQUEST} - memory: ${MEMORY_REQUEST} - imagePullPolicy: Always - restartPolicy: Always - serviceAccountName: '${SERVICE_ACCOUNT_NAME}' - - kind: Service - apiVersion: v1 - metadata: - name: '${ING_SERVICE_NAME}' - labels: - app-name: '${ING_APP_NAME}' - spec: - ports: - - name: http - protocol: TCP - appProtocol: http - port: '${{SERVICE_PORT}}' - targetPort: http - - name: management - protocol: TCP - appProtocol: http - port: '${{MANAGEMENT_PORT}}' - targetPort: management - selector: - app: '${ING_APP_NAME}' - - kind: Deployment - apiVersion: apps/v1 - metadata: - name: '${ING_APP_NAME}' - spec: - replicas: '${{ING_REPLICAS}}' - selector: - matchLabels: - app: '${ING_APP_NAME}' - template: - metadata: - labels: - app: '${ING_APP_NAME}' - spec: - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - '${ING_APP_NAME}' - topologyKey: kubernetes.io/hostname - weight: 90 - - podAffinityTerm: - labelSelector: - matchExpressions: - - key: app - operator: In - values: - - '${ING_APP_NAME}' - topologyKey: topology.kubernetes.io/zone - weight: 100 - containers: - - name: app - image: '${IMAGE}:${IMAGE_TAG}' - livenessProbe: - httpGet: - path: /q/health/live - port: '${{MANAGEMENT_PORT}}' - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /q/health/ready - port: '${{MANAGEMENT_PORT}}' - initialDelaySeconds: 5 - periodSeconds: 10 - ports: - - name: http - containerPort: '${{SERVICE_PORT}}' - protocol: TCP - - name: management - containerPort: '${{MANAGEMENT_PORT}}' - protocol: TCP - env: - - name: API_NVD_APIKEY - valueFrom: - secretKeyRef: - name: exhort-secret - key: api-nvd-apikey - name: DB_REDIS_HOST valueFrom: secretKeyRef: @@ -219,13 +101,6 @@ objects: value: '8080' - name: QUARKUS_MANAGEMENT_PORT value: '9000' - - name: QUARKUS_PROFILE - value: ingester,prod - - name: ONGUARD_PASSWORD - valueFrom: - secretKeyRef: - name: exhort-secret - key: admin.pwd - name: QUARKUS_REDIS_MAX_POOL_SIZE value: '20' - name: QUARKUS_REDIS_MAX_POOL_WAITING @@ -248,21 +123,11 @@ parameters: description: Application name value: onguard required: true - - name: ING_APP_NAME - displayName: Ingester Application name - description: Ingester Application name - value: onguard-ingester - required: true - name: REPLICAS displayName: Replicas description: Number of desired pods value: '1' required: true - - name: ING_REPLICAS - displayName: Ingester Replicas - description: Number of desired pods for the ingester service - value: '1' - required: true - name: IMAGE displayName: Container image name description: Container image name @@ -287,11 +152,6 @@ parameters: description: Service name value: onguard required: true - - name: ING_SERVICE_NAME - displayName: Ingester Service Name - description: Ingester Service Name - value: onguard-ingester - required: true - name: SERVICE_PORT displayName: Service port description: Service port diff --git a/devfile.yaml b/devfile.yaml index 625d1c7..b2d918e 100644 --- a/devfile.yaml +++ b/devfile.yaml @@ -6,13 +6,12 @@ metadata: supportUrl: https://github.com/RHEcosystemAppEng/onguard/issues website: https://github.com/RHEcosystemAppEng/onguard displayName: ONGUard - description: ONGuard Service that aggregates data from OSV and NVD + description: ONGuard Service that aggregates data from OSV tags: - Exhort - RHTPA - Java - Quarkus - - NVD - OSV projectType: Quarkus language: Java diff --git a/pom.xml b/pom.xml index a428d34..a2622ac 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 com.redhat.ecosystemappeng.onguard onguard @@ -90,8 +92,8 @@ quarkus-smallrye-health - io.quarkus - quarkus-micrometer-registry-prometheus + io.quarkus + quarkus-micrometer-registry-prometheus io.quarkus @@ -139,9 +141,9 @@ org.wiremock wiremock-standalone - ${wiremock.version} + ${wiremock.version} test - + diff --git a/src/main/docker/Dockerfile.multi-stage b/src/main/docker/Dockerfile.multi-stage index 6205da3..9b6df4e 100644 --- a/src/main/docker/Dockerfile.multi-stage +++ b/src/main/docker/Dockerfile.multi-stage @@ -19,7 +19,7 @@ LABEL io.k8s.description="Red Hat Trusted Profile Analyzer - ONGuard Service" LABEL io.k8s.display-name="RHTPA ONGuard service" LABEL io.openshift.tags="rhtpa exhort onguard" LABEL summary="The RHTPA ONGuard Service exposes an API for retrieving vulnerability data \ -from OSV and NVD databases" +from OSV database" WORKDIR /work/ COPY --from=build /code/target/*-runner /work/application diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/Alias.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/Alias.java deleted file mode 100644 index 6448cf5..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/Alias.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model; - -import io.quarkus.runtime.annotations.RegisterForReflection; - -@RegisterForReflection -public record Alias(String id, String cveId) { - - public static String getKey(String id) { - return "alias:" + id; - } - - public static String getKey(Alias alias) { - if(alias == null) { - return null; - } - return getKey(alias.id); - } -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/CveAliasResponse.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/CveAliasResponse.java deleted file mode 100644 index 7a52b39..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/CveAliasResponse.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model; - -import java.util.List; - -public record CveAliasResponse(String cveId, List aliases, String error) { - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/Ingestion.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/Ingestion.java deleted file mode 100644 index 0491ad1..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/Ingestion.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model; - -import java.time.LocalDateTime; - -import io.quarkus.runtime.annotations.RegisterForReflection; - -@RegisterForReflection -public record Ingestion(LocalDateTime started, LocalDateTime completed, Integer index, Integer pageSize, Integer total, Status status, LocalDateTime since) { - - public static enum Status { - COMPLETED, - PROCESSING, - COMPLETED_WITH_ERRORS - } - - @Override - public String toString() { - return "Ingestion [started: " + started + ", index: " + index + ", total: " + total + ", status: " + status + ", completed: " + completed + ", since: " + since + "]"; - } - - public static class Builder { - LocalDateTime started; - LocalDateTime completed; - Integer index; - Integer pageSize; - Integer total; - Status status; - LocalDateTime since; - - private Builder() { - } - - private Builder(Ingestion other) { - this.started = other.started; - this.completed = other.completed; - this.index = other.index; - this.pageSize = other.pageSize; - this.status = other.status; - this.total = other.total; - this.since = other.since; - } - - public Builder started(LocalDateTime started) { - this.started = started; - return this; - } - - public Builder completed(LocalDateTime completed) { - this.completed = completed; - return this; - } - - public Builder index(Integer index) { - this.index = index; - return this; - } - - public Builder pageSize(Integer pageSize) { - this.pageSize = pageSize; - return this; - } - - public Builder status(Status status) { - this.status = status; - return this; - } - - public Builder total(Integer total) { - this.total = total; - return this; - } - - public Builder since(LocalDateTime since) { - this.since = since; - return this; - } - - public Ingestion build() { - if (index == null) { - index = 0; - } - return new Ingestion(started, completed, index, pageSize, total, status, since); - } - } - - public static Ingestion.Builder builder(Ingestion other) { - return new Builder(other); - } - - public static Ingestion.Builder builder() { - return new Builder(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/Vulnerability.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/Vulnerability.java deleted file mode 100644 index b7b7e17..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/Vulnerability.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model; - -import static com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability.CVE_PATTERN; - -import java.util.Date; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.redhat.ecosystemappeng.onguard.model.osv.Affected; -import com.redhat.ecosystemappeng.onguard.model.osv.Severity; - -public record Vulnerability( - String cveId, - Date created, - Date lastModified, - String summary, - String description, - List affected, - @JsonProperty("severity") List severities) { - - public boolean hasData() { - return created != null && severities != null; - } - - @Override - public final int hashCode() { - return cveId.hashCode(); - } - - @Override - public final boolean equals(Object other) { - if(other == null) { - return false; - } - if(other instanceof Vulnerability) { - return cveId.equals(((Vulnerability)other).cveId); - } - return false; - } - - public static class Builder { - List aliases; - String cveId; - Date created; - Date lastModified; - String summary; - String description; - List affected; - List severities; - - private Builder() { - } - - private Builder(Vulnerability other) { - this.cveId = other.cveId; - this.created = other.created; - this.lastModified = other.lastModified; - this.summary = other.summary; - this.description = other.description; - this.affected = other.affected; - this.severities = other.severities; - } - - public Builder cveId(String cveId) { - this.cveId = cveId; - return this; - } - - public Builder created(Date created) { - this.created = created; - return this; - } - - public Builder lastModified(Date lastModified) { - this.lastModified = lastModified; - return this; - } - - public Builder summary(String summary) { - this.summary = summary; - return this; - } - - public Builder description(String description) { - this.description = description; - return this; - } - - public Builder affected(List affected) { - this.affected = affected; - return this; - } - - public Builder severities(List severities) { - this.severities = severities; - return this; - } - - public String getCveId() { - return cveId; - } - - public Vulnerability build() { - if(cveId == null && aliases != null) { - var alias = aliases.stream().filter(a -> CVE_PATTERN.matcher(a).matches()).findAny(); - if(alias.isPresent()) { - cveId = alias.get(); - } - } - return new Vulnerability(cveId, created, lastModified, summary, description, affected, severities); - } - } - - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(Vulnerability other) { - return new Builder(other); - } -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/VulnerabilityAlias.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/VulnerabilityAlias.java deleted file mode 100644 index 1662666..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/VulnerabilityAlias.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model; - -import io.quarkus.runtime.annotations.RegisterForReflection; - -@RegisterForReflection -public record VulnerabilityAlias(String alias, Vulnerability vulnerability) { - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdCve.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdCve.java deleted file mode 100644 index 3cddbb1..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdCve.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model.nvd; - -import java.util.Date; - -public record NvdCve(String id, Date publisihed, Date lastModified) { - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdResponse.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdResponse.java deleted file mode 100644 index aceb75a..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model.nvd; - -import java.util.Date; -import java.util.List; - -public record NvdResponse(Integer resultsPerPage, Integer startIndex, Integer totalResults, String format, String version, Date timestamp, List vulnerabilities) { - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdVulnerability.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdVulnerability.java deleted file mode 100644 index 16075cd..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/nvd/NvdVulnerability.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.model.nvd; - -public record NvdVulnerability(NvdCve cve) { - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/model/osv/OsvVulnerability.java b/src/main/java/com/redhat/ecosystemappeng/onguard/model/osv/OsvVulnerability.java index 09db321..e3cd9a4 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/model/osv/OsvVulnerability.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/model/osv/OsvVulnerability.java @@ -29,6 +29,10 @@ public record OsvVulnerability(String id, String summary, String details, List affected, @JsonProperty("severity") List severities) { public static final Pattern CVE_PATTERN = Pattern.compile("CVE-\\d{4}-\\d{4,7}", Pattern.CASE_INSENSITIVE); + + public static OsvVulnerability createEmpty(String vulnId) { + return new OsvVulnerability(vulnId, null, null, null, null, null, null, null); + } public OsvVulnerability { List newAliases = new ArrayList<>(); diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/IngestionRepository.java b/src/main/java/com/redhat/ecosystemappeng/onguard/repository/IngestionRepository.java deleted file mode 100644 index b8dbe4e..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/IngestionRepository.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.repository; - -import com.redhat.ecosystemappeng.onguard.model.Ingestion; - -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; - -public interface IngestionRepository { - - Uni getSync(); - - Uni saveSync(Ingestion ingestion); - - Uni updateSync(Ingestion ingestion); - - Uni deleteAll(); - - Multi exportAliases(); - - Multi exportVulnerabilities(); - - Uni exportIngestion(); - - Uni importScript(byte[] data); - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/VulnerabilityRepository.java b/src/main/java/com/redhat/ecosystemappeng/onguard/repository/VulnerabilityRepository.java index 1e010b7..2abbb22 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/VulnerabilityRepository.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/repository/VulnerabilityRepository.java @@ -18,23 +18,16 @@ package com.redhat.ecosystemappeng.onguard.repository; import java.util.List; +import java.util.Map; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; -import com.redhat.ecosystemappeng.onguard.model.VulnerabilityAlias; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; import io.smallrye.mutiny.Uni; public interface VulnerabilityRepository { - Uni get(String cveId); + Uni get(String cveId); - Uni save(Vulnerability vulnerability); + Uni> list(List vulnerabilities); - Uni getByAlias(String alias); - - Uni> listByAliases(List aliases); - - Uni setAliases(List aliases, String cveId); - - Uni removeAll(); } diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/IngestionRedisRepository.java b/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/IngestionRedisRepository.java deleted file mode 100644 index a30fcaa..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/IngestionRedisRepository.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.repository.redis; - -import java.util.Arrays; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.ecosystemappeng.onguard.model.Alias; -import com.redhat.ecosystemappeng.onguard.model.Ingestion; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; -import com.redhat.ecosystemappeng.onguard.repository.IngestionRepository; - -import io.quarkus.redis.datasource.ReactiveRedisDataSource; -import io.quarkus.redis.datasource.json.ReactiveJsonCommands; -import io.quarkus.redis.datasource.keys.KeyScanArgs; -import io.quarkus.redis.datasource.keys.ReactiveKeyCommands; -import io.quarkus.redis.datasource.list.ReactiveListCommands; -import io.quarkus.redis.datasource.value.ReactiveValueCommands; -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -@ApplicationScoped -public class IngestionRedisRepository implements IngestionRepository { - - private static final String SYNCS = "ingestions:updates"; - private static final int MAX_HISTORY = 100; - - private final ReactiveListCommands syncCommands; - private final ReactiveKeyCommands keyCommands; - private final ReactiveValueCommands valueCommands; - private final ReactiveJsonCommands jsonCommands; - private final ReactiveRedisDataSource ds; - - public IngestionRedisRepository(ReactiveRedisDataSource ds) { - this.ds = ds; - this.syncCommands = ds.list(String.class, Ingestion.class); - this.keyCommands = ds.key(); - this.jsonCommands = ds.json(String.class); - this.valueCommands = ds.value(Alias.class); - } - - @Inject - ObjectMapper mapper; - - @Override - public Uni getSync() { - return syncCommands.lindex(SYNCS, 0); - } - - @Override - public Uni saveSync(Ingestion ingestion) { - return syncCommands.llen(SYNCS).chain(len -> { - Uni res = Uni.createFrom().item(ingestion); - if (len == MAX_HISTORY) { - res = syncCommands.rpop(SYNCS); - } - return res.chain(i -> syncCommands.lpush(SYNCS, ingestion)).replaceWith(ingestion); - }); - } - - @Override - public Uni updateSync(Ingestion ingestion) { - return syncCommands.lset(SYNCS, 0, ingestion).replaceWith(ingestion); - } - - @Override - public Uni deleteAll() { - return syncCommands.getDataSource().flushall(); - } - - @Override - public Multi exportVulnerabilities() { - return keyCommands.scan(new KeyScanArgs().match("cves:*").count(2000)).toMulti().onItem() - .transformToUniAndMerge(this::toVulnerability).onItem().transform(v -> { - try { - return String.format("JSON.SET %s $ '%s'\n", v.cveId(), mapper.writeValueAsString(v)); - } catch (JsonProcessingException e) { - return "-- " + e.getMessage(); - } - }); - } - - @Override - public Multi exportAliases() { - return keyCommands.scan(new KeyScanArgs().match("alias:*").count(2000)).toMulti().onItem() - .transformToUniAndMerge(this::toAlias) - .onItem() - .transform(a -> String.format("SET %s '%s'\n", a.id(), a.cveId())); - } - - @Override - public Uni exportIngestion() { - return getSync().onItem().transform(i -> { - try { - return String.format("LPUSH %s '%s'", SYNCS, mapper.writeValueAsString(i)); - } catch (JsonProcessingException e) { - return "-- " + e.getMessage(); - } - }); - } - - @Override - public Uni importScript(byte[] data) { - return Uni.createFrom().item(new String(data)).onItem().transformToMulti(d -> Multi.createFrom().items(d.lines())) - .onItem().invoke(line -> { - if (line.startsWith("--")) { - return; - } - var args = line.split(" "); - if (args.length > 1) { - ds.execute(args[0], Arrays.copyOfRange(args, 1, args.length)); - } else { - ds.execute(args[0]); - } - }).onItem().ignoreAsUni(); - - } - - private Uni toVulnerability(String key) { - return jsonCommands - .jsonGet(key, Vulnerability.class); - } - - private Uni toAlias(String key) { - return valueCommands.get(key); - } - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepository.java b/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepository.java index 4780c14..aadc36d 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepository.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepository.java @@ -17,25 +17,19 @@ */ package com.redhat.ecosystemappeng.onguard.repository.redis; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import org.jboss.logging.Logger; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.ecosystemappeng.onguard.model.Alias; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; -import com.redhat.ecosystemappeng.onguard.model.VulnerabilityAlias; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; import com.redhat.ecosystemappeng.onguard.repository.VulnerabilityRepository; import io.quarkus.redis.datasource.ReactiveRedisDataSource; -import io.quarkus.redis.datasource.json.ReactiveJsonCommands; import io.quarkus.redis.datasource.value.ReactiveValueCommands; import io.smallrye.mutiny.Uni; import jakarta.enterprise.context.ApplicationScoped; @@ -45,119 +39,58 @@ public class VulnerabilityRedisRepository implements VulnerabilityRepository { private static final Logger LOGGER = Logger.getLogger(VulnerabilityRedisRepository.class); + private static final String KEY_PREFIX = "vuln:"; @Inject ObjectMapper mapper; - private final ReactiveValueCommands aliasCommands; - private final ReactiveJsonCommands vulnCommands; + private final ReactiveValueCommands vulnCommands; public VulnerabilityRedisRepository(ReactiveRedisDataSource ds) { - this.vulnCommands = ds.json(String.class); - this.aliasCommands = ds.value(Alias.class); + this.vulnCommands = ds.value(OsvVulnerability.class); } @Override - public Uni get(String cveId) { - if (cveId == null) { + public Uni get(String vulnId) { + if (vulnId == null) { return Uni.createFrom().nullItem(); } - return vulnCommands.jsonGet(cvesKey(cveId), Vulnerability.class).onItem().ifNull() - .continueWith(Vulnerability.builder().cveId(cveId).build()); - } - private String cvesKey(String cveId) { - return "cves:" + cveId; + return vulnCommands.get(toKey(vulnId)).onItem().ifNull().continueWith(OsvVulnerability.createEmpty(vulnId)); } - @Override - public Uni save(Vulnerability vulnerability) { - return vulnCommands.jsonSet(cvesKey(vulnerability.cveId()), "$", vulnerability).chain(() -> { - var alias = new Alias(vulnerability.cveId(), vulnerability.cveId()); - return aliasCommands.set(Alias.getKey(alias.id()), alias); - }); + private String toKey(String vulnId) { + return KEY_PREFIX + vulnId; } - public Uni setAliases(List aliases, String cveId) { - if (aliases != null && !aliases.isEmpty() && cveId != null) { - return aliasCommands - .mset(aliases.stream().collect(Collectors.toMap(v -> Alias.getKey(v), v -> new Alias(v, cveId)))); - } - return Uni.createFrom().voidItem(); + private String fromKey(String key) { + return key.substring(KEY_PREFIX.length()); } - private Uni> list(List cves) { - // Arguments do not match the signature... - if (cves == null || cves.isEmpty()) { - return Uni.createFrom().item(Collections.emptyList()); + public Uni> list(List vulns) { + if (vulns == null || vulns.isEmpty()) { + return Uni.createFrom().item(Collections.emptyMap()); } - var first = cvesKey(cves.get(0)); - var varargs = new ArrayList<>(cves.subList(1, cves.size()).stream().map(this::cvesKey).toList()); - varargs.add("$"); - return vulnCommands.jsonMget(first, varargs.toArray(new String[0])).onItem().transform(found -> { - var results = new ArrayList(); - for (int i = 0; i < cves.size(); i++) { - Vulnerability vuln = null; - if (found.get(i) != null) { - try { - vuln = mapper.readValue(found.get(i).getJsonObject(0).encode(), Vulnerability.class); - } catch (JsonProcessingException e) { - LOGGER.warnf("Unable to parse vulnerability with id %s", cves.get(i), e); - } - } - if (vuln == null) { - vuln = Vulnerability.builder().cveId(cves.get(i)).build(); - } - results.add(vuln); - } - return results; - }); - - } - - @Override - public Uni getByAlias(String aliasId) { - return aliasCommands.get(Alias.getKey(aliasId)) - .onItem().ifNull().continueWith(new Alias(aliasId, null)) - .chain(alias -> get(alias.cveId())) - .onItem().transform(vuln -> new VulnerabilityAlias(aliasId, vuln)) - .onItem().ifNull().continueWith(new VulnerabilityAlias(aliasId, null)); - } - - @Override - public Uni> listByAliases(List aliasIds) { - return aliasCommands.mget(aliasIds.stream().map(Alias::getKey).toList().toArray(new String[0])) - .chain(aliases -> transformAliasToVulnerabilities(aliases, aliasIds)); - } - - private Uni> transformAliasToVulnerabilities(Map aliases, - List aliasIds) { - return list(aliases.values().stream().filter(Objects::nonNull).map(Alias::cveId).toList()) + String[] keys = vulns.stream().map(this::toKey).toArray(String[]::new); + return vulnCommands.mget(keys) .onItem() - .transform(vulns -> vulns.stream() - .distinct() - .collect(Collectors.toMap(Vulnerability::cveId, v -> v))) - .onItem() - .transform(vulns -> { - var cveIdCount = new HashSet<>(); - var results = new ArrayList(); - - aliasIds.stream().forEach(aliasId -> { - var alias = aliases.get(Alias.getKey(aliasId)); - if (alias == null || alias.cveId() == null) { - results.add(new VulnerabilityAlias(aliasId, null)); - } else if (!cveIdCount.contains(alias.cveId())) { - cveIdCount.add(alias.cveId()); - results.add(new VulnerabilityAlias(aliasId, vulns.get(alias.cveId()))); + .transform(m -> { + Map result = new HashMap<>(); + for(var e : m.entrySet()) { + var vulnId = fromKey(e.getKey()); + var vuln = e.getValue(); + if(vuln == null) { + result.put(vulnId, OsvVulnerability.createEmpty(vulnId)); + } else { + var key = vuln.id() == null ? vulnId : vuln.id(); + if(!result.containsKey(key)) { + result.put(key, vuln); + } } - }); - return results; - }); - } + } - @Override - public Uni removeAll() { - return Uni.createFrom().voidItem().onItem().invoke(() -> aliasCommands.getDataSource().flushall()); + return result; + }); } } diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/rest/IngestionEndpoint.java b/src/main/java/com/redhat/ecosystemappeng/onguard/rest/IngestionEndpoint.java deleted file mode 100644 index 93e9a1b..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/rest/IngestionEndpoint.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.rest; - -import static jakarta.ws.rs.core.Response.Status.ACCEPTED; -import static jakarta.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR; -import static jakarta.ws.rs.core.Response.Status.NOT_FOUND; -import static jakarta.ws.rs.core.Response.Status.OK; - -import org.jboss.logging.Logger; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.ecosystemappeng.onguard.service.IngestionService; - -import io.quarkus.vertx.http.ManagementInterface; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.event.Observes; -import jakarta.inject.Inject; -import jakarta.ws.rs.core.MediaType; - -@ApplicationScoped -public class IngestionEndpoint { - - private static final Logger LOGGER = Logger.getLogger(IngestionEndpoint.class); - - @Inject - IngestionService svc; - - @Inject - ObjectMapper mapper; - - public void registerManagementRoutes(@Observes ManagementInterface mi) { - mi.router().get("/admin/status").handler(ctx -> { - svc.getStatus().subscribe().with(i -> { - if(i == null) { - ctx.response().setStatusCode(NOT_FOUND.getStatusCode()); - return; - } - try { - ctx.response().setStatusCode(OK.getStatusCode()).putHeader("Content-Type", MediaType.APPLICATION_JSON) - .end(mapper.writeValueAsString(i)); - } catch (JsonProcessingException e) { - ctx.response().setStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()).end(e.getMessage()); - } - }); - }); - - mi.router().get("/admin/export/cves").handler(ctx -> { - ctx.response().putHeader("Content-Type", MediaType.TEXT_PLAIN); - StringBuffer buffer = new StringBuffer(); - svc.exportVulnerabilities().subscribe().with(v -> { - if (v != null) { - buffer.append(v); - } - }, f -> { - LOGGER.error("Unable to export cves data", f); - ctx.response().setStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()).send(f.getMessage()); - }, () -> { - ctx.response().send(buffer.toString()); - }); - }); - - mi.router().get("/admin/export/aliases").handler(ctx -> { - ctx.response().putHeader("Content-Type", MediaType.TEXT_PLAIN); - StringBuffer buffer = new StringBuffer(); - svc.exportAliases().subscribe().with(a -> buffer.append(a), f -> { - LOGGER.error("Unable to export aliases data", f); - ctx.response().setStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()).send(f.getMessage()); - }, () -> { - ctx.response().send(buffer.toString()); - }); - }); - - mi.router().get("/admin/export/ingestions").handler(ctx -> { - ctx.response().putHeader("Content-Type", MediaType.TEXT_PLAIN); - svc.exportIngestion().subscribe().with(a -> ctx.response().end(a), f -> { - LOGGER.error("Unable to export ingestions data", f); - ctx.response().setStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()).send(f.getMessage()); - }); - }); - - mi.router().post("/admin/import").handler(ctx -> { - ctx.request().bodyHandler(h -> svc.importScript(h.getBytes()).subscribe().with( - v -> ctx.response().setStatusCode(ACCEPTED.getStatusCode()), - f -> ctx.response().setStatusCode(INTERNAL_SERVER_ERROR.getStatusCode()))); - - }); - } - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/rest/PurlEndpoint.java b/src/main/java/com/redhat/ecosystemappeng/onguard/rest/PurlEndpoint.java index 9a570c1..4f7642a 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/rest/PurlEndpoint.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/rest/PurlEndpoint.java @@ -22,14 +22,13 @@ import java.util.Map; import com.redhat.ecosystemappeng.onguard.model.PurlsRequest; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; import com.redhat.ecosystemappeng.onguard.service.VulnerabilityService; import io.smallrye.mutiny.Uni; import jakarta.inject.Inject; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.QueryParam; @Path("/purls") public class PurlEndpoint { @@ -38,10 +37,10 @@ public class PurlEndpoint { VulnerabilityService svc; @POST - public Uni>> find(PurlsRequest request, @QueryParam("reload") boolean reload) { + public Uni>> find(PurlsRequest request) { if(request == null || request.purls() == null || request.purls().isEmpty()) { return Uni.createFrom().item(Collections.emptyMap()); } - return svc.findByPurls(request.purls(), reload); + return svc.findByPurls(request.purls()); } } diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/rest/VulnerabilityEndpoint.java b/src/main/java/com/redhat/ecosystemappeng/onguard/rest/VulnerabilityEndpoint.java index 0fc3ef7..3842d42 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/rest/VulnerabilityEndpoint.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/rest/VulnerabilityEndpoint.java @@ -18,15 +18,15 @@ package com.redhat.ecosystemappeng.onguard.rest; import java.util.List; +import java.util.Map; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; import com.redhat.ecosystemappeng.onguard.service.VulnerabilityService; -import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; import jakarta.inject.Inject; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.QueryParam; @Path("/vulnerabilities") public class VulnerabilityEndpoint { @@ -35,11 +35,8 @@ public class VulnerabilityEndpoint { VulnerabilityService svc; @POST - public Multi find(List vulnerabilities, @QueryParam("reload") boolean reload) { - if(vulnerabilities == null || vulnerabilities.isEmpty()) { - return Multi.createFrom().empty(); - } - return svc.find(vulnerabilities, reload); + public Uni> find(List vulnerabilities) { + return svc.find(vulnerabilities); } } diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/IngestionService.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/IngestionService.java deleted file mode 100644 index 5da6184..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/IngestionService.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jboss.logging.Logger; -import org.jboss.resteasy.reactive.ClientWebApplicationException; - -import com.redhat.ecosystemappeng.onguard.model.Ingestion; -import com.redhat.ecosystemappeng.onguard.model.nvd.NvdResponse; -import com.redhat.ecosystemappeng.onguard.model.nvd.NvdVulnerability; -import com.redhat.ecosystemappeng.onguard.repository.IngestionRepository; -import com.redhat.ecosystemappeng.onguard.repository.VulnerabilityRepository; -import com.redhat.ecosystemappeng.onguard.service.nvd.NvdService; -import com.redhat.ecosystemappeng.onguard.service.osv.OsvService; - -import io.quarkus.runtime.Startup; -import io.quarkus.scheduler.Scheduled; -import io.quarkus.scheduler.Scheduled.ConcurrentExecution; -import io.quarkus.scheduler.Scheduled.SkipPredicate; -import io.quarkus.scheduler.ScheduledExecution; -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import jakarta.ws.rs.core.Response; - -@ApplicationScoped -public class IngestionService { - - private static final String INGESTER_PROFILE = "ingester"; - private static final Logger LOGGER = Logger.getLogger(IngestionService.class); - - @Inject - IngestionRepository repository; - - @Inject - NvdService nvdSvc; - - @Inject - VulnerabilityRepository vulnRepository; - - @Inject - OsvService osvService; - - @ConfigProperty(name = "onguard.ingester.pageSize", defaultValue = "1000") - Integer pageSize; - - @Inject - ExecutorService executor; - - @Inject - SkipSchedulerPredicate skipScheduler; - - @Startup - void purgeUncompletedSyncs() { - if (!skipScheduler.test(null)) { - repository.getSync().onItem().ifNotNull().transformToUni(i -> { - if (Ingestion.Status.PROCESSING.equals(i.status())) { - return repository.updateSync(Ingestion.builder(i).status(Ingestion.Status.COMPLETED_WITH_ERRORS).build()); - } - return Uni.createFrom().item(i); - }).subscribeAsCompletionStage().getNow(null); - } - return; - } - - @Scheduled(every = "{onguard.ingester.schedule.every:1h}", skipExecutionIf = SkipSchedulerPredicate.class, delay = 10, delayUnit = TimeUnit.SECONDS, concurrentExecution = ConcurrentExecution.SKIP) - Uni sync() { - LOGGER.info("Loading vulnerabilities from NVD"); - return repository.getSync() - .onItem().ifNull().continueWith(Ingestion.builder().build()).chain(this::ingestData); - } - - public Uni getStatus() { - return repository.getSync(); - } - - public Uni deleteAll() { - return repository.deleteAll(); - } - - private Uni ingestData(Ingestion previous) { - var sync = Ingestion.builder().started(LocalDateTime.now()).status(Ingestion.Status.PROCESSING) - .pageSize(pageSize); - if (Ingestion.Status.COMPLETED.equals(previous.status())) { - sync.since(previous.started()).index(0); - } else if (Ingestion.Status.COMPLETED_WITH_ERRORS.equals(previous.status())) { - sync.since(previous.since()).index(previous.index()); - } - - return repository.saveSync(sync.build()).chain(ingestion -> Multi.createBy().repeating().uni( - () -> new AtomicInteger(ingestion.index()), - state -> readNvdData(ingestion, state.getAndAdd(pageSize))) - .until(r -> r.totalResults() == 0 || r.vulnerabilities() == null || r.vulnerabilities().isEmpty()) - .onItem().call(this::processPage) - .onItem().call(resp -> this.updateSyncProgress(resp, ingestion)) - .onItem().ignore() - .onCompletion().call(() -> { - var completed = Ingestion.builder(ingestion).status(Ingestion.Status.COMPLETED).completed(LocalDateTime.now()) - .index(ingestion.total()).build(); - return repository.updateSync(completed); - }) - .onFailure().call(e -> { - LOGGER.error("Unable to load vulnerabilities", e); - var errIng = Ingestion.builder(ingestion).status(Ingestion.Status.COMPLETED_WITH_ERRORS).build(); - return repository.updateSync(errIng); - }).toUni()); - } - - private Uni readNvdData(Ingestion ingestion, int index) { - return nvdSvc.bulkLoad(index, pageSize, ingestion.since()) - .onFailure().retry().withBackOff(Duration.ofSeconds(10)).expireIn(Duration.ofMinutes(5).toMillis()) - .onFailure().call(e -> { - LOGGER.error("Unable to read page from NVD", e); - var errBulk = Ingestion.builder(ingestion).completed(LocalDateTime.now()) - .status(Ingestion.Status.COMPLETED_WITH_ERRORS) - .index(index).build(); - return repository.updateSync(errBulk); - }); - } - - private Uni processPage(NvdResponse response) { - Multi vulnMulti = Uni.createFrom().item(response).onItem().transform(NvdResponse::vulnerabilities) - .onItem().disjoint(); - return vulnMulti.onItem() - .transform(this::toCve) - .onItem() - .transformToUniAndMerge(this::ingestVulnerability) - .toUni(); - } - - private Uni updateSyncProgress(NvdResponse response, Ingestion i) { - var updated = Ingestion.builder(i).status(Ingestion.Status.PROCESSING).total(response.totalResults()) - .index(response.startIndex()).completed(null).pageSize(pageSize).build(); - - return repository.updateSync(updated).replaceWith(response); - } - - private String toCve(NvdVulnerability nvdVuln) { - return nvdVuln.cve().id(); - } - - private Uni ingestVulnerability(String cve) { - LOGGER.debugf("Ingest Vulnerability %s", cve); - return osvService.get(cve) - .onFailure(ClientWebApplicationException.class).recoverWithItem(error -> { - var e = (ClientWebApplicationException) error; - if (e.getResponse() != null && e.getResponse().getStatus() == Response.Status.NOT_FOUND.getStatusCode()) { - LOGGER.debugf("Not found vulnerability: %s in OSV. Ignoring", cve); - return null; - } - throw e; - }) - .onItem().ifNotNull() - .transformToUni(o -> vulnRepository.save(o)) - .onFailure() - .retry() - .withBackOff(Duration.ofSeconds(10)) - .expireIn(Duration.ofMinutes(5).toMillis()); - } - - @Singleton - public static class SkipSchedulerPredicate implements SkipPredicate { - - @ConfigProperty(name = "quarkus.profile") - String profile; - - public boolean test(ScheduledExecution execution) { - return profile != null && !profile.contains(INGESTER_PROFILE); - } - } - - public Multi exportVulnerabilities() { - return repository.exportVulnerabilities(); - } - - public Multi exportAliases() { - return repository.exportAliases(); - } - - public Uni exportIngestion() { - return repository.exportIngestion(); - } - - public Uni importScript(byte[] data) { - return repository.importScript(data); - } - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityService.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityService.java index 001b65e..ea2d6b0 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityService.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityService.java @@ -20,17 +20,14 @@ import java.util.List; import java.util.Map; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; -import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; public interface VulnerabilityService { - Multi find(List vulnIds, boolean reload); + Uni> find(List vulnIds); - Uni>> findByPurls(List purls, boolean reload); - - Uni cleanUp(); + Uni>> findByPurls(List purls); } diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceImpl.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceImpl.java index 26bc4a8..4b4b9d1 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceImpl.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceImpl.java @@ -17,49 +17,44 @@ */ package com.redhat.ecosystemappeng.onguard.service; -import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; -import java.util.stream.Collectors; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.context.ManagedExecutor; -import org.eclipse.microprofile.faulttolerance.Fallback; -import org.jboss.logging.Logger; -import org.jboss.resteasy.reactive.ClientWebApplicationException; +import org.eclipse.microprofile.rest.client.inject.RestClient; import com.fasterxml.jackson.databind.ObjectMapper; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; -import com.redhat.ecosystemappeng.onguard.model.VulnerabilityAlias; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; +import com.redhat.ecosystemappeng.onguard.model.osv.PackageRef; import com.redhat.ecosystemappeng.onguard.model.osv.Partition; +import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequest; +import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequestItem; +import com.redhat.ecosystemappeng.onguard.model.osv.QueryResult; +import com.redhat.ecosystemappeng.onguard.model.osv.QueryResultItem; +import com.redhat.ecosystemappeng.onguard.model.osv.VulnerabilityRef; import com.redhat.ecosystemappeng.onguard.repository.VulnerabilityRepository; -import com.redhat.ecosystemappeng.onguard.service.nvd.NvdService; -import com.redhat.ecosystemappeng.onguard.service.osv.OsvService; +import com.redhat.ecosystemappeng.onguard.service.osv.OsvApi; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; import io.smallrye.mutiny.tuples.Tuple2; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import jakarta.ws.rs.core.Response; @ApplicationScoped public class VulnerabilityServiceImpl implements VulnerabilityService { - private static final Logger LOGGER = Logger.getLogger(VulnerabilityServiceImpl.class); private static final int MAX_OSV_BATCH = 1000; - @Inject - VulnerabilityRepository repository; - - @Inject - OsvService osvService; + @RestClient + OsvApi osvApi; @Inject - NvdService nvdService; + VulnerabilityRepository repository; @Inject ExecutorService executorService; @@ -74,95 +69,44 @@ public class VulnerabilityServiceImpl implements VulnerabilityService { ManagedExecutor executor; @Override - public Multi find(List aliases, boolean reload) { - if (aliases.isEmpty()) { - return Multi.createFrom().empty(); - } - return repository.listByAliases(aliases) - .onItem().ifNotNull().transformToMulti(list -> Multi.createFrom().iterable(list)) - .onItem().transformToUniAndMerge(v -> load(v, reload)); + public Uni> find(List aliases) { + return repository.list(aliases); } @Override - public Uni>> findByPurls(List purls, boolean reload) { + public Uni>> findByPurls(List purls) { return Multi.createFrom().items(new Partition(purls, MAX_OSV_BATCH).stream()) - .onItem().transform(p -> processBatch(p, reload)).onItem() + .onItem().transform(p -> processBatch(p)).onItem() .transformToUniAndMerge(e -> e) .toUni() .onItem().ifNull().continueWith(Collections.emptyMap()); } - Uni>> processBatchFallback(List purls, boolean reload) { - Uni.createFrom() - .voidItem() - .onItem() - .delayIt().by(Duration.ofSeconds(delay)) - .chain(() -> processBatch(purls, reload)) - .runSubscriptionOn(executor).subscribeAsCompletionStage(); - return Uni.createFrom().item(purls).onItem() - .transform(i -> i.stream().collect(Collectors.toMap(purl -> purl, purl -> Collections.emptyList()))); - } - - @Fallback(fallbackMethod = "processBatchFallback") - Uni>> processBatch(List purls, boolean reload) { + private Uni>> processBatch(List purls) { var keys = Multi.createFrom().items(purls.stream()); - var values = osvService.query(purls) + var values = query(purls) .onItem() - .transform(vulns -> find(vulns, reload)); + .transformToUniAndMerge(vulns -> find(vulns)); - return Multi.createBy().combining().streams(keys, values).asTuple().onItem() - .transformToUniAndMerge(t -> t.getItem2().collect().asList().map(items -> Tuple2.of(t.getItem1(), items))) - .collect().asMap(t -> t.getItem1(), t -> t.getItem2()); + return Multi.createBy().combining().streams(keys, values).asTuple() + .collect().asMap(t -> t.getItem1(), t -> new ArrayList<>(t.getItem2().values())); } - private Uni load(VulnerabilityAlias vulnAlias, boolean reload) { - if (!reload && vulnAlias.vulnerability() != null) { - return Uni.createFrom().item(vulnAlias.vulnerability()); - } - return osvService.get(vulnAlias.alias()) - .onItem().ifNotNull() - .transformToUni(v -> { - if(vulnAlias.alias().equals(v.cveId()) || v.severities() != null) { - return Uni.createFrom().item(v); + private Multi> query(List purls) { + List queries = purls.stream().map(purl -> new QueryRequestItem(new PackageRef(purl))).toList(); + return osvApi.queryBatch(new QueryRequest(queries)) + .onFailure().recoverWithItem(t -> new QueryResult(purls.stream().map(purl -> new QueryResultItem(Collections.emptyList())).toList())) + .map(QueryResult::results) + .onItem().ifNull().continueWith(Collections.emptyList()) + .onItem().transformToMulti(i -> Multi.createFrom().items(i.stream())) + .onItem().transform(item -> { + if (item.vulns() == null) { + List vulns = Collections.emptyList(); + return vulns; } - if(v.cveId() == null) { - return Uni.createFrom().nullItem(); - } - return osvService.get(v.cveId()); - }) - .onItem().ifNotNull() - .transformToUni(updated -> updateExistingVuln(updated)) - .onFailure(ClientWebApplicationException.class).recoverWithItem(error -> { - var e = (ClientWebApplicationException) error; - if (e.getResponse() != null && e.getResponse().getStatus() == Response.Status.NOT_FOUND.getStatusCode()) { - LOGGER.infof("Not found %s in OSV", vulnAlias.alias()); - } else { - LOGGER.errorf("Error retrieving OSV vulnerability for %s", vulnAlias.alias(), e); - Uni.createFrom().voidItem() - .onItem().delayIt().by(Duration.ofSeconds(delay)) - .chain(i -> load(vulnAlias, reload)) - .runSubscriptionOn(executor).subscribeAsCompletionStage(); - } - return null; - }); - } - - private Uni updateExistingVuln(Vulnerability v) { - var builder = Vulnerability.builder(v); - return repository.get(v.cveId()).onItem().transform(existing -> { - if (existing != null && existing.hasData()) { - builder.created(existing.created()).lastModified(new Date()); - } else { - builder.created(new Date()); - } - return builder.build(); - }).call(repository::save); - } - - @Override - public Uni cleanUp() { - return Uni.createFrom().voidItem().onItem().invoke(() -> repository.removeAll()); + return item.vulns(); + }).onItem().transform(refs -> refs.stream().map(VulnerabilityRef::id).toList()); } } diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdApi.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdApi.java deleted file mode 100644 index 6e1ee6c..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdApi.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service.nvd; - -import java.time.temporal.ChronoUnit; - -import org.eclipse.microprofile.faulttolerance.CircuitBreaker; -import org.eclipse.microprofile.faulttolerance.Retry; -import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import org.jboss.resteasy.reactive.RestQuery; - -import com.redhat.ecosystemappeng.onguard.model.nvd.NvdResponse; - -import io.smallrye.mutiny.Uni; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -@Path("/rest/json/cves/2.0/") -@RegisterRestClient(configKey = "nvd-api") -@CircuitBreaker(delay = NvdApi.NVD_API_WINDOW_SECS, delayUnit = ChronoUnit.SECONDS) -public interface NvdApi { - - static final long NVD_API_WINDOW_SECS = 30; - - @GET - @ClientHeaderParam(name = "apiKey", value = "${api.nvd.apikey}") - @Produces(MediaType.APPLICATION_JSON) - @Retry(delay = NVD_API_WINDOW_SECS, delayUnit = ChronoUnit.SECONDS) - Uni list( - @RestQuery("startIndex") Integer startIndex, - @RestQuery("resultsPerPage") Integer resultsPerPage, - @RestQuery("lastModStartDate") String lastModStartDate, - @RestQuery("lastModEndDate") String lastModEndDate); -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdService.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdService.java deleted file mode 100644 index e17d795..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdService.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service.nvd; - -import java.time.LocalDateTime; - -import com.redhat.ecosystemappeng.onguard.model.nvd.NvdResponse; - -import io.smallrye.mutiny.Uni; - -public interface NvdService { - - Uni bulkLoad(Integer index, Integer pageSize, LocalDateTime since); - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdServiceImpl.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdServiceImpl.java deleted file mode 100644 index a6d0734..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/nvd/NvdServiceImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service.nvd; - -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; - -import org.eclipse.microprofile.rest.client.inject.RestClient; - -import com.redhat.ecosystemappeng.onguard.model.nvd.NvdResponse; - -import io.smallrye.mutiny.Uni; -import jakarta.enterprise.context.ApplicationScoped; - -@ApplicationScoped -public class NvdServiceImpl implements NvdService { - - @RestClient - NvdApi nvdApi; - - @Override - public Uni bulkLoad(Integer index, Integer pageSize, LocalDateTime since) { - if (since == null) { - return nvdApi.list(index, pageSize, null, null); - } - return nvdApi.list(index, pageSize, - DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(since.atZone(ZoneId.systemDefault())), - DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(LocalDateTime.now().atZone(ZoneId.systemDefault()))); - } - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvApi.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvApi.java index 39c7ede..7d75c31 100644 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvApi.java +++ b/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvApi.java @@ -19,24 +19,17 @@ import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequest; import com.redhat.ecosystemappeng.onguard.model.osv.QueryResult; import io.quarkus.cache.CacheResult; import io.smallrye.mutiny.Uni; -import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; @Path("/v1") @RegisterRestClient(configKey = "osv-api") public interface OsvApi { - - @GET - @Path("/vulns/{vulnId}") - Uni getVuln(@PathParam("vulnId") String vulnId); @POST @Path("/querybatch") diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvService.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvService.java deleted file mode 100644 index 169e8a5..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvService.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service.osv; - -import java.util.List; - -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; - -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; - -public interface OsvService { - - Multi> query(List purls); - - Uni get(String alias); - -} diff --git a/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvServiceImpl.java b/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvServiceImpl.java deleted file mode 100644 index 4c068f4..0000000 --- a/src/main/java/com/redhat/ecosystemappeng/onguard/service/osv/OsvServiceImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service.osv; - -import java.util.Collections; -import java.util.List; - -import org.eclipse.microprofile.rest.client.inject.RestClient; - -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; -import com.redhat.ecosystemappeng.onguard.model.osv.PackageRef; -import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequest; -import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequestItem; -import com.redhat.ecosystemappeng.onguard.model.osv.QueryResult; -import com.redhat.ecosystemappeng.onguard.model.osv.VulnerabilityRef; -import com.redhat.ecosystemappeng.onguard.repository.VulnerabilityRepository; - -import io.smallrye.mutiny.Multi; -import io.smallrye.mutiny.Uni; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.inject.Inject; - -@ApplicationScoped -public class OsvServiceImpl implements OsvService { - - @RestClient - OsvApi osvApi; - - @Inject - VulnerabilityRepository repository; - - @Override - public Multi> query(List purls) { - List queries = purls.stream().map(purl -> new QueryRequestItem(new PackageRef(purl))).toList(); - return osvApi.queryBatch(new QueryRequest(queries)) - .map(QueryResult::results) - .onItem().ifNull().continueWith(Collections.emptyList()) - .onItem().transformToMulti(i -> Multi.createFrom().items(i.stream())) - .onItem().transform(item -> { - if (item.vulns() == null) { - List vulns = Collections.emptyList(); - return vulns; - } - return item.vulns(); - }).onItem().transform(refs -> refs.stream().map(VulnerabilityRef::id).toList()); - } - - @Override - public Uni get(String alias) { - return osvApi.getVuln(alias) - .onItem().ifNotNull() - .transformToUni(osvVuln -> { - var cveId = osvVuln.id(); - var vuln = Uni.createFrom() - .item(Vulnerability.builder().cveId(cveId).summary(osvVuln.summary()).description(osvVuln.details()) - .affected(osvVuln.affected()).severities(osvVuln.severities()).build()); - if (osvVuln.aliases() != null && cveId != null) { - return repository.setAliases(osvVuln.aliases(), cveId).replaceWith(vuln); - } - return vuln; - }); - } - -} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0948559..0b0d228 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,7 +5,6 @@ # quarkus.log.level=DEBUG quarkus.rest-client.osv-api.url=https://api.osv.dev -quarkus.rest-client.nvd-api.url=https://services.nvd.nist.gov quarkus.management.enabled=true diff --git a/src/test/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepositoryTest.java b/src/test/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepositoryTest.java index f56e537..5ab674f 100644 --- a/src/test/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepositoryTest.java +++ b/src/test/java/com/redhat/ecosystemappeng/onguard/repository/redis/VulnerabilityRedisRepositoryTest.java @@ -18,8 +18,10 @@ package com.redhat.ecosystemappeng.onguard.repository.redis; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.List; @@ -37,67 +39,74 @@ public class VulnerabilityRedisRepositoryTest { @Test void testGet_NotFound() { - var cveId = "not_found_cve"; + var cveId = "CVE-0000-00000"; var vuln = repository.get(cveId).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() .assertCompleted().getItem(); assertNotNull(vuln); - assertEquals(cveId, vuln.cveId()); + assertEquals(cveId, vuln.id()); } @Test void testGet_Found() { - var cveId = "CVE-2022-24683"; - var vuln = repository.get(cveId).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() + var aliasId = "GHSA-9p26-698r-w4hx"; + var cveId = "CVE-2024-23650"; + var vuln = repository.get(aliasId).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() .assertCompleted().getItem(); assertNotNull(vuln); - assertEquals(cveId, vuln.cveId()); + assertEquals(cveId, vuln.id()); + assertTrue(vuln.aliases().contains(aliasId)); assertNotNull(vuln.severities()); } @Test void testGetByAlias() { - var cveId = "CVE-2022-24683"; - var alias = "GHSA-wmrx-57hm-mw7r"; - var vuln = repository.getByAlias(alias).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() + var cveId = "CVE-2022-31026"; + var alias = "GHSA-5g4r-2qhx-vqfm"; + var vuln = repository.get(cveId).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() .assertCompleted().getItem(); assertNotNull(vuln); - assertNotNull(vuln.alias()); - assertNotNull(vuln.vulnerability()); - assertEquals(cveId, vuln.vulnerability().cveId()); - assertEquals(alias, vuln.alias()); - assertNotNull(vuln.vulnerability().severities()); + assertTrue(vuln.aliases().contains(alias)); + assertEquals(cveId, vuln.id()); + assertNotNull(vuln.severities()); } @Test void testGetByAlias_NotFound() { var aliasId = "not_found_alias"; - var alias = repository.getByAlias(aliasId).subscribe().withSubscriber(UniAssertSubscriber.create()) + var vuln = repository.get(aliasId).subscribe().withSubscriber(UniAssertSubscriber.create()) .awaitItem().assertCompleted().getItem(); - assertNotNull(alias); - assertEquals(aliasId, alias.alias()); - assertNull(alias.vulnerability()); + assertNotNull(vuln); + assertNull(vuln.id()); } @Test void testListByAliases() { - // CVE-2022-30324 and GHSA-526x-rm7j-v389 are aliases of the same CVE - var aliases = List.of("GHSA-wmrx-57hm-mw7r", "not_found", "GHSA-526x-rm7j-v389", "CVE-2022-30324"); - var found = repository.listByAliases(aliases).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() - .assertCompleted().getItem(); + // CVE-2024-23650 and GHSA-9p26-698r-w4hx are aliases of the same CVE + var aliases = List.of("GHSA-9p26-698r-w4hx", "CVE-0000-00000", "CVE-2022-31026", "CVE-2024-23650"); + var found = repository.list(aliases).subscribe().withSubscriber(UniAssertSubscriber.create()).awaitItem() + .assertCompleted().getItem(); assertEquals(3, found.size()); - for (int i = 0; i < 3; i++) { - assertNotNull(found.get(i)); + for (int i = 1; i < 4; i++) { + assertNotNull(found.get(aliases.get(i))); } - assertNotNull(found.get(0).vulnerability().severities()); - assertNotNull(found.get(2).vulnerability().severities()); - assertNull(found.get(1).vulnerability()); - assertNotNull("CVE-2022-24683", found.get(0).vulnerability().cveId()); - assertEquals("CVE-2022-30324", found.get(2).vulnerability().cveId()); - assertNull(found.get(1).vulnerability()); + var cveWithAlias = found.get(aliases.get(3)); + assertNotNull(cveWithAlias.severities()); + assertEquals(cveWithAlias.id(), aliases.get(3)); + assertTrue(cveWithAlias.aliases().contains(aliases.get(0))); + + var cve = found.get(aliases.get(2)); + assertNotNull(cve.severities()); + assertEquals(cve.id(), aliases.get(2)); + assertFalse(cve.aliases().isEmpty()); + + var notFound = found.get(aliases.get(1)); + assertNull(notFound.severities()); + assertEquals(notFound.id(), aliases.get(1)); + assertTrue(notFound.aliases().isEmpty()); } } diff --git a/src/test/java/com/redhat/ecosystemappeng/onguard/service/IngestionServiceTest.java b/src/test/java/com/redhat/ecosystemappeng/onguard/service/IngestionServiceTest.java deleted file mode 100644 index 7a23572..0000000 --- a/src/test/java/com/redhat/ecosystemappeng/onguard/service/IngestionServiceTest.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.assertArg; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.time.LocalDateTime; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import com.redhat.ecosystemappeng.onguard.model.Ingestion; -import com.redhat.ecosystemappeng.onguard.model.Ingestion.Status; -import com.redhat.ecosystemappeng.onguard.repository.IngestionRepository; -import com.redhat.ecosystemappeng.onguard.repository.VulnerabilityRepository; -import com.redhat.ecosystemappeng.onguard.service.IngestionService.SkipSchedulerPredicate; -import com.redhat.ecosystemappeng.onguard.service.IngestionServiceTest.SchedulerProfile; -import com.redhat.ecosystemappeng.onguard.service.nvd.NvdService; -import com.redhat.ecosystemappeng.onguard.service.osv.OsvService; - -import io.quarkus.test.InjectMock; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; -import io.quarkus.test.junit.mockito.MockitoConfig; -import io.smallrye.mutiny.Uni; -import jakarta.inject.Inject; - -@QuarkusTest -@TestProfile(SchedulerProfile.class) -public class IngestionServiceTest { - - @InjectMock - IngestionRepository repository; - - @Inject - IngestionService svc; - - @InjectMock - NvdService nvdSvc; - - @InjectMock - OsvService osvSvc; - - @InjectMock - VulnerabilityRepository vulnRepo; - - @InjectMock - @MockitoConfig(convertScopes = true) - SkipSchedulerPredicate skipScheduler; - - @Test - void testPurgeUncompletedSyncs_noPreviousMigration() { - when(repository.getSync()).thenReturn(Uni.createFrom().nullItem()); - when(skipScheduler.test(any())).thenReturn(Boolean.FALSE); - - svc.purgeUncompletedSyncs(); - - verify(repository, times(0)).updateSync(any(Ingestion.class)); - } - - @Test - void testPurgeUncompletedSyncs_CompletedMigration() { - when(repository.getSync()).thenReturn(Uni.createFrom() - .item(Ingestion.builder().started(LocalDateTime.now()).status(Status.COMPLETED).build())); - when(skipScheduler.test(any())).thenReturn(Boolean.FALSE); - - svc.purgeUncompletedSyncs(); - - verify(repository, times(0)).updateSync(any(Ingestion.class)); - } - - @Test - void testPurgeUncompletedSyncs_CompletedWithErrors() { - when(repository.getSync()).thenReturn(Uni.createFrom() - .item(Ingestion.builder().started(LocalDateTime.now()).status(Status.PROCESSING).build())); - when(skipScheduler.test(null)).thenReturn(Boolean.FALSE); - - svc.purgeUncompletedSyncs(); - - verify(repository, times(1)).updateSync(assertArg(i -> Status.COMPLETED_WITH_ERRORS.equals(i.status()))); - } - - public static class SchedulerProfile implements QuarkusTestProfile { - - @Override - public Map getConfigOverrides() { - return Map.of("onguard.ingester.schedule.every", "10s"); - } - - } - -} diff --git a/src/test/java/com/redhat/ecosystemappeng/onguard/service/NvdApiTest.java b/src/test/java/com/redhat/ecosystemappeng/onguard/service/NvdApiTest.java deleted file mode 100644 index 4ab2d5c..0000000 --- a/src/test/java/com/redhat/ecosystemappeng/onguard/service/NvdApiTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2024 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.redhat.ecosystemappeng.onguard.service; - -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; -import static com.redhat.ecosystemappeng.onguard.test.WireMockExtensions.NVD_API_PATH; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.eclipse.microprofile.rest.client.inject.RestClient; -import org.junit.jupiter.api.Test; - -import com.github.tomakehurst.wiremock.WireMockServer; -import com.redhat.ecosystemappeng.onguard.service.nvd.NvdApi; -import com.redhat.ecosystemappeng.onguard.test.InjectWireMock; -import com.redhat.ecosystemappeng.onguard.test.WireMockExtensions; - -import io.quarkus.test.common.QuarkusTestResource; -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.RestAssured; -import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; - -@QuarkusTest -@QuarkusTestResource(WireMockExtensions.class) -class NvdApiTest { - - @RestClient - NvdApi nvdApi; - - @InjectWireMock - WireMockServer server; - - static { - RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); - } - - @Test - void testList() { - var response = nvdApi.list(0, 200, "start_date", "end_date").subscribe() - .withSubscriber(UniAssertSubscriber.create()).awaitItem().assertCompleted().getItem(); - assertEquals(1, response.totalResults()); - server.verify(1, getRequestedFor(urlPathEqualTo(NVD_API_PATH)) - .withQueryParam("startIndex", equalTo("0"))); - } - - @Test - void testListWithRetry() { - var response = nvdApi.list(10, 200, "start_date", "end_date").subscribe() - .withSubscriber(UniAssertSubscriber.create()).awaitItem().assertCompleted().getItem(); - - assertEquals(1, response.totalResults()); - server.verify(2, getRequestedFor(urlPathEqualTo(NVD_API_PATH)) - .withQueryParam("startIndex", equalTo("10"))); - } - -} diff --git a/src/test/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceTest.java b/src/test/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceTest.java index 90947c2..e89585f 100644 --- a/src/test/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceTest.java +++ b/src/test/java/com/redhat/ecosystemappeng/onguard/service/VulnerabilityServiceTest.java @@ -17,27 +17,16 @@ */ package com.redhat.ecosystemappeng.onguard.service; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.serviceUnavailable; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathTemplate; import static com.redhat.ecosystemappeng.onguard.test.WireMockExtensions.OSV_QUERY_API_PATH; -import static com.redhat.ecosystemappeng.onguard.test.WireMockExtensions.OSV_VULN_API_PATH; import static com.redhat.ecosystemappeng.onguard.test.WireMockExtensions.PURL_WITH_ERROR; import static com.redhat.ecosystemappeng.onguard.test.WireMockExtensions.PURL_WITH_VULNS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Collections; @@ -48,8 +37,7 @@ import org.junit.jupiter.api.Test; import com.github.tomakehurst.wiremock.WireMockServer; -import com.redhat.ecosystemappeng.onguard.model.Vulnerability; -import com.redhat.ecosystemappeng.onguard.model.VulnerabilityAlias; +import com.redhat.ecosystemappeng.onguard.model.osv.OsvVulnerability; import com.redhat.ecosystemappeng.onguard.repository.VulnerabilityRepository; import com.redhat.ecosystemappeng.onguard.test.InjectWireMock; import com.redhat.ecosystemappeng.onguard.test.WireMockExtensions; @@ -59,10 +47,8 @@ import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.smallrye.mutiny.Uni; -import io.smallrye.mutiny.helpers.test.AssertSubscriber; import io.smallrye.mutiny.helpers.test.UniAssertSubscriber; import jakarta.inject.Inject; -import jakarta.ws.rs.core.MediaType; @QuarkusTest @QuarkusTestResource(WireMockExtensions.class) @@ -88,11 +74,11 @@ public void reset() { @Test void testFindByPurls_Empty() { - var result = vulnerabilityService.findByPurls(null, false).subscribe().withSubscriber(UniAssertSubscriber.create()) + var result = vulnerabilityService.findByPurls(null).subscribe().withSubscriber(UniAssertSubscriber.create()) .awaitItem().assertCompleted().getItem(); assertTrue(result.isEmpty()); - result = vulnerabilityService.findByPurls(Collections.emptyList(), false).subscribe() + result = vulnerabilityService.findByPurls(Collections.emptyList()).subscribe() .withSubscriber(UniAssertSubscriber.create()).awaitItem().assertCompleted().getItem(); assertTrue(result.isEmpty()); @@ -103,7 +89,7 @@ void testFindByPurls_Empty() { void testFindByPurls_NoVulns() { var purl = "pkg:maven/org.mvnpm.at.vaadin/select@24.0.5?type=jar"; var purls = List.of(purl); - var results = vulnerabilityService.findByPurls(purls, false).subscribe() + var results = vulnerabilityService.findByPurls(purls).subscribe() .withSubscriber(UniAssertSubscriber.create()).awaitItem().assertCompleted().getItem(); assertFalse(results.isEmpty()); @@ -119,11 +105,11 @@ void testFindByPurls_NoVulns() { void testFindByPurls_Success() { var purls = List.of(PURL_WITH_VULNS); var alias = "GHSA-hr8g-6v94-x4m9"; - var vulnerability = new Vulnerability("cve-001", null, null, null, null, null, null); - when(repository.listByAliases(anyList())) - .thenReturn(Uni.createFrom().item(List.of(new VulnerabilityAlias(alias, vulnerability)))); + var vulnerability = new OsvVulnerability(alias, null, null, null, null, null, null, null); + when(repository.list(anyList())) + .thenReturn(Uni.createFrom().item(Map.of(alias, vulnerability))); - var results = vulnerabilityService.findByPurls(purls, false).subscribe() + var results = vulnerabilityService.findByPurls(purls).subscribe() .withSubscriber(UniAssertSubscriber.create()).awaitItem().assertCompleted().getItem(); assertFalse(results.isEmpty()); @@ -141,52 +127,12 @@ void testFindByPurls_Success() { void testFindByPurls_Error() throws InterruptedException { var purls = List.of(PURL_WITH_ERROR); - vulnerabilityService.findByPurls(purls, false) + vulnerabilityService.findByPurls(purls) .subscribe() .withSubscriber(UniAssertSubscriber.create()) .awaitItem() .assertItem(Map.of(PURL_WITH_ERROR, Collections.emptyList())); server.verify(1, postRequestedFor(urlEqualTo(OSV_QUERY_API_PATH))); - server.resetRequests(); - - Thread.sleep(1500L); - - server.verify(1, postRequestedFor(urlEqualTo(OSV_QUERY_API_PATH))); - } - - @Test - void testGet_OsvRecoverAfteRetry() throws InterruptedException { - - var cveId = "CVE-2023-36095"; - - when(repository.listByAliases(anyList())) - .thenReturn(Uni.createFrom().item(List.of(new VulnerabilityAlias(cveId, null)))); - when(repository.save(any(Vulnerability.class))).thenReturn(Uni.createFrom().voidItem()); - when(repository.setAliases(anyList(), anyString())).thenReturn(Uni.createFrom().voidItem()); - when(repository.get(eq(cveId))).thenReturn(Uni.createFrom().nullItem()); - - server.stubFor(get(urlPathTemplate(OSV_VULN_API_PATH)) - .withPathParam("id", equalTo(cveId)) - .willReturn(serviceUnavailable())); - - vulnerabilityService.find(List.of(cveId), true) - .subscribe() - .withSubscriber(AssertSubscriber.create()) - .awaitCompletion().assertHasNotReceivedAnyItem(); - - server.verify(1, getRequestedFor(urlPathTemplate(OSV_VULN_API_PATH)).withPathParam("id", equalTo(cveId))); - - server.stubFor(get(urlPathTemplate(OSV_VULN_API_PATH)) - .withPathParam("id", equalTo(cveId)) - .willReturn(ok().withBodyFile("osv-data/" + cveId + ".json").withHeader(WireMockExtensions.CONTENT_TYPE, - MediaType.APPLICATION_JSON))); - - // Wait for the retry to make a successful request - Thread.sleep(1500); - - server.verify(2, getRequestedFor(urlPathTemplate(OSV_VULN_API_PATH)) - .withPathParam("id", equalTo(cveId))); - verify(repository).setAliases(List.of("GHSA-gwqq-6vq7-5j86", "PYSEC-2023-138"), cveId); } } diff --git a/src/test/java/com/redhat/ecosystemappeng/onguard/test/WireMockExtensions.java b/src/test/java/com/redhat/ecosystemappeng/onguard/test/WireMockExtensions.java index abb643c..93f0419 100644 --- a/src/test/java/com/redhat/ecosystemappeng/onguard/test/WireMockExtensions.java +++ b/src/test/java/com/redhat/ecosystemappeng/onguard/test/WireMockExtensions.java @@ -17,13 +17,10 @@ */ package com.redhat.ecosystemappeng.onguard.test; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; -import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.ok; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.serverError; -import static com.github.tomakehurst.wiremock.client.WireMock.serviceUnavailable; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; import static org.junit.jupiter.api.Assertions.fail; @@ -35,7 +32,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.stubbing.Scenario; import com.redhat.ecosystemappeng.onguard.model.osv.PackageRef; import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequest; import com.redhat.ecosystemappeng.onguard.model.osv.QueryRequestItem; @@ -46,14 +42,7 @@ public class WireMockExtensions implements QuarkusTestResourceLifecycleManager { public static final String CONTENT_TYPE = "Content-Type"; - public static final String API_KEY_PARAM = "apiKey"; - public static final String CVE_PARAM = "cveId"; - public static final String NVD_API_KEY = "nvd-api-123"; - public static final String VALID_CVE = "CVE-2022-24684"; - public static final String ERROR_503_CVE = "FAIL_WITH_503"; - public static final String NOT_FOUND = "not_found"; - - public static final String NVD_API_PATH = "/rest/json/cves/2.0"; + public static final String OSV_QUERY_API_PATH = "/v1/querybatch"; public static final String OSV_VULN_API_PATH = "/v1/vulns/{id}"; @@ -66,51 +55,13 @@ public class WireMockExtensions implements QuarkusTestResourceLifecycleManager { public Map start() { server.start(); - stubNvd(); stubOsv(); Map props = new HashMap<>(); - props.put("quarkus.rest-client.nvd-api.url", server.baseUrl()); props.put("quarkus.rest-client.osv-api.url", server.baseUrl()); - props.put("api.nvd.apikey", NVD_API_KEY); return props; } - private void stubNvd() { - - server.stubFor(get(urlPathEqualTo(NVD_API_PATH)) - .withHeader(API_KEY_PARAM, equalTo(NVD_API_KEY)) - .withQueryParam("startIndex", equalTo("0")) - .withQueryParam("resultsPerPage", equalTo("200")) - .withQueryParam("lastModStartDate", equalTo("start_date")) - .withQueryParam("lastModEndDate", equalTo("end_date")) - .willReturn(ok().withBodyFile("nvd-data/" + VALID_CVE + ".json") - .withHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON))); - - server.stubFor(get(urlPathEqualTo(NVD_API_PATH)) - .inScenario("List Retry") - .whenScenarioStateIs(Scenario.STARTED) - .withHeader(API_KEY_PARAM, equalTo(NVD_API_KEY)) - .withQueryParam("startIndex", equalTo("10")) - .withQueryParam("resultsPerPage", equalTo("200")) - .withQueryParam("lastModStartDate", equalTo("start_date")) - .withQueryParam("lastModEndDate", equalTo("end_date")) - .willReturn(serviceUnavailable()) - .willSetStateTo("Next")); - - server.stubFor(get(urlPathEqualTo(NVD_API_PATH)) - .inScenario("List Retry") - .whenScenarioStateIs("Next") - .withHeader(API_KEY_PARAM, equalTo(NVD_API_KEY)) - .withQueryParam("startIndex", equalTo("10")) - .withQueryParam("resultsPerPage", equalTo("200")) - .withQueryParam("lastModStartDate", equalTo("start_date")) - .withQueryParam("lastModEndDate", equalTo("end_date")) - .willReturn(ok().withBodyFile("nvd-data/" + VALID_CVE + ".json") - .withHeader(CONTENT_TYPE, MediaType.APPLICATION_JSON))); - - } - private void stubOsv() { var mapper = new ObjectMapper(); var reqWithVulns = new QueryRequest(List.of(new QueryRequestItem(new PackageRef(PURL_WITH_VULNS)))); diff --git a/src/test/resources/__files/nvd-data/CVE-2022-24684.json b/src/test/resources/__files/nvd-data/CVE-2022-24684.json deleted file mode 100644 index e5a35fb..0000000 --- a/src/test/resources/__files/nvd-data/CVE-2022-24684.json +++ /dev/null @@ -1,171 +0,0 @@ -{ - "resultsPerPage": 1, - "startIndex": 0, - "totalResults": 1, - "format": "NVD_CVE", - "version": "2.0", - "timestamp": "2024-01-11T15:51:15.660", - "vulnerabilities": [ - { - "cve": { - "id": "CVE-2022-24684", - "sourceIdentifier": "cve@mitre.org", - "published": "2022-02-15T15:15:12.703", - "lastModified": "2023-08-08T14:22:24.967", - "vulnStatus": "Modified", - "descriptions": [ - { - "lang": "en", - "value": "HashiCorp Nomad and Nomad Enterprise 0.9.0 through 1.0.16, 1.1.11, and 1.2.5 allow operators with job-submit capabilities to use the spread stanza to panic server agents. Fixed in 1.0.18, 1.1.12, and 1.2.6." - }, - { - "lang": "es", - "value": "HashiCorp Nomad y Nomad Enterprise versiones 0.9.0 hasta 1.0.16, 1.1.11 y 1.2.5 permiten a los operadores con capacidades de envío de trabajos utilizar la estrofa de propagación para hacer entrar en pánico a los agentes del servidor. Corregido en las versiones 1.0.18, 1.1.12 y 1.2.6.\n" - } - ], - "metrics": { - "cvssMetricV31": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "cvssData": { - "version": "3.1", - "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H", - "attackVector": "NETWORK", - "attackComplexity": "LOW", - "privilegesRequired": "LOW", - "userInteraction": "NONE", - "scope": "UNCHANGED", - "confidentialityImpact": "NONE", - "integrityImpact": "NONE", - "availabilityImpact": "HIGH", - "baseScore": 6.5, - "baseSeverity": "MEDIUM" - }, - "exploitabilityScore": 2.8, - "impactScore": 3.6 - } - ], - "cvssMetricV2": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "cvssData": { - "version": "2.0", - "vectorString": "AV:N/AC:L/Au:S/C:N/I:N/A:P", - "accessVector": "NETWORK", - "accessComplexity": "LOW", - "authentication": "SINGLE", - "confidentialityImpact": "NONE", - "integrityImpact": "NONE", - "availabilityImpact": "PARTIAL", - "baseScore": 4.0 - }, - "baseSeverity": "MEDIUM", - "exploitabilityScore": 8.0, - "impactScore": 2.9, - "acInsufInfo": false, - "obtainAllPrivilege": false, - "obtainUserPrivilege": false, - "obtainOtherPrivilege": false, - "userInteractionRequired": false - } - ] - }, - "weaknesses": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "description": [ - { - "lang": "en", - "value": "NVD-CWE-noinfo" - } - ] - } - ], - "configurations": [ - { - "nodes": [ - { - "operator": "OR", - "negate": false, - "cpeMatch": [ - { - "vulnerable": true, - "criteria": "cpe:2.3:a:hashicorp:nomad:*:*:*:*:-:*:*:*", - "versionStartIncluding": "0.9.0", - "versionEndExcluding": "1.0.18", - "matchCriteriaId": "201BE91D-2266-405D-91AB-186AAB87F48C" - }, - { - "vulnerable": true, - "criteria": "cpe:2.3:a:hashicorp:nomad:*:*:*:*:enterprise:*:*:*", - "versionStartIncluding": "0.9.0", - "versionEndExcluding": "1.0.18", - "matchCriteriaId": "AB25A5DD-9BA6-45C3-8404-BC54FC18C4DA" - }, - { - "vulnerable": true, - "criteria": "cpe:2.3:a:hashicorp:nomad:*:*:*:*:-:*:*:*", - "versionStartIncluding": "1.1.0", - "versionEndExcluding": "1.1.12", - "matchCriteriaId": "2B14CB96-73AF-453A-B18E-60639352ABAB" - }, - { - "vulnerable": true, - "criteria": "cpe:2.3:a:hashicorp:nomad:*:*:*:*:enterprise:*:*:*", - "versionStartIncluding": "1.1.0", - "versionEndExcluding": "1.1.12", - "matchCriteriaId": "49D886F9-9206-43EB-A1CE-4C660EC99603" - }, - { - "vulnerable": true, - "criteria": "cpe:2.3:a:hashicorp:nomad:*:*:*:*:-:*:*:*", - "versionStartIncluding": "1.2.0", - "versionEndExcluding": "1.2.6", - "matchCriteriaId": "B0DD0341-5D45-4277-931C-41D06CD10489" - }, - { - "vulnerable": true, - "criteria": "cpe:2.3:a:hashicorp:nomad:*:*:*:*:enterprise:*:*:*", - "versionStartIncluding": "1.2.0", - "versionEndExcluding": "1.2.6", - "matchCriteriaId": "E735FB0A-7112-40FE-BC83-6A2A93E4F6CE" - } - ] - } - ] - } - ], - "references": [ - { - "url": "https://discuss.hashicorp.com", - "source": "cve@mitre.org", - "tags": [ - "Vendor Advisory" - ] - }, - { - "url": "https://discuss.hashicorp.com/t/hcsec-2022-04-nomad-spread-job-stanza-may-trigger-panic-in-servers/", - "source": "cve@mitre.org" - }, - { - "url": "https://discuss.hashicorp.com/t/hcsec-2022-04-nomad-spread-job-stanza-may-trigger-panic-in-servers/35562", - "source": "cve@mitre.org", - "tags": [ - "Vendor Advisory" - ] - }, - { - "url": "https://security.netapp.com/advisory/ntap-20220318-0008/", - "source": "cve@mitre.org", - "tags": [ - "Third Party Advisory" - ] - } - ] - } - } - ] -} \ No newline at end of file diff --git a/src/test/resources/__files/nvd-data/CVE-2023-36095.json b/src/test/resources/__files/nvd-data/CVE-2023-36095.json deleted file mode 100644 index 401b568..0000000 --- a/src/test/resources/__files/nvd-data/CVE-2023-36095.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "resultsPerPage": 1, - "startIndex": 0, - "totalResults": 1, - "format": "NVD_CVE", - "version": "2.0", - "timestamp": "2024-03-27T16:01:47.087", - "vulnerabilities": [ - { - "cve": { - "id": "CVE-2023-36095", - "sourceIdentifier": "cve@mitre.org", - "published": "2023-08-05T03:15:13.580", - "lastModified": "2023-08-14T18:15:10.927", - "vulnStatus": "Modified", - "descriptions": [ - { - "lang": "en", - "value": "An issue in Harrison Chase langchain v.0.0.194 allows an attacker to execute arbitrary code via the python exec calls in the PALChain, affected functions include from_math_prompt and from_colored_object_prompt." - } - ], - "metrics": { - "cvssMetricV31": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "cvssData": { - "version": "3.1", - "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", - "attackVector": "NETWORK", - "attackComplexity": "LOW", - "privilegesRequired": "NONE", - "userInteraction": "NONE", - "scope": "UNCHANGED", - "confidentialityImpact": "HIGH", - "integrityImpact": "HIGH", - "availabilityImpact": "HIGH", - "baseScore": 9.8, - "baseSeverity": "CRITICAL" - }, - "exploitabilityScore": 3.9, - "impactScore": 5.9 - } - ] - }, - "weaknesses": [ - { - "source": "nvd@nist.gov", - "type": "Primary", - "description": [ - { - "lang": "en", - "value": "CWE-94" - } - ] - } - ], - "configurations": [ - { - "nodes": [ - { - "operator": "OR", - "negate": false, - "cpeMatch": [ - { - "vulnerable": true, - "criteria": "cpe:2.3:a:langchain:langchain:0.0.194:*:*:*:*:*:*:*", - "matchCriteriaId": "7186FBDD-894A-41C2-A682-D7B2B95107C9" - } - ] - } - ] - } - ], - "references": [ - { - "url": "http://langchain.com", - "source": "cve@mitre.org", - "tags": [ - "Product" - ] - }, - { - "url": "https://github.com/hwchase17/langchain", - "source": "cve@mitre.org", - "tags": [ - "Product" - ] - }, - { - "url": "https://github.com/langchain-ai/langchain/issues/5872", - "source": "cve@mitre.org", - "tags": [ - "Exploit", - "Issue Tracking", - "Vendor Advisory" - ] - } - ] - } - } - ] -} \ No newline at end of file diff --git a/src/test/resources/__files/osv-data/CVE-2023-36095.json b/src/test/resources/__files/osv-data/CVE-2023-36095.json deleted file mode 100644 index 49805a4..0000000 --- a/src/test/resources/__files/osv-data/CVE-2023-36095.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "id": "CVE-2023-36095", - "details": "An issue in Harrison Chase langchain v.0.0.194 allows an attacker to execute arbitrary code via the python exec calls in the PALChain, affected functions include from_math_prompt and from_colored_object_prompt.", - "aliases": [ - "GHSA-gwqq-6vq7-5j86", - "PYSEC-2023-138" - ], - "modified": "2023-11-29T10:08:30.646216Z", - "published": "2023-08-05T03:15:13Z", - "references": [ - { - "type": "REPORT", - "url": "https://github.com/langchain-ai/langchain/issues/5872" - }, - { - "type": "WEB", - "url": "http://langchain.com" - }, - { - "type": "WEB", - "url": "https://github.com/hwchase17/langchain" - } - ], - "affected": [ - { - "ranges": [ - { - "type": "GIT", - "repo": "https://github.com/hwchase17/langchain", - "events": [ - { - "introduced": "0" - }, - { - "last_affected": "893d20f735af0387870c9a7c160ef51f34d053ad" - } - ] - }, - { - "type": "GIT", - "repo": "https://github.com/langchain-ai/langchain", - "events": [ - { - "introduced": "0" - }, - { - "last_affected": "893d20f735af0387870c9a7c160ef51f34d053ad" - } - ] - } - ], - "versions": [ - "v0.0.100", - "v0.0.101", - "v0.0.102", - "v0.0.103", - "v0.0.104", - "v0.0.105", - "v0.0.106", - "v0.0.107", - "v0.0.108", - "v0.0.109", - "v0.0.110", - "v0.0.111", - "v0.0.112", - "v0.0.113", - "v0.0.114", - "v0.0.115", - "v0.0.116", - "v0.0.117", - "v0.0.118", - "v0.0.119", - "v0.0.120", - "v0.0.121", - "v0.0.122", - "v0.0.123", - "v0.0.124", - "v0.0.125", - "v0.0.126", - "v0.0.127", - "v0.0.128", - "v0.0.129", - "v0.0.130", - "v0.0.131", - "v0.0.132", - "v0.0.133", - "v0.0.134", - "v0.0.135", - "v0.0.136", - "v0.0.137", - "v0.0.138", - "v0.0.139", - "v0.0.140", - "v0.0.141", - "v0.0.142", - "v0.0.143", - "v0.0.144", - "v0.0.145", - "v0.0.146", - "v0.0.147", - "v0.0.148", - "v0.0.149", - "v0.0.150", - "v0.0.151", - "v0.0.152", - "v0.0.153", - "v0.0.154", - "v0.0.155", - "v0.0.156", - "v0.0.157", - "v0.0.158", - "v0.0.159", - "v0.0.160", - "v0.0.161", - "v0.0.162", - "v0.0.163", - "v0.0.164", - "v0.0.165", - "v0.0.166", - "v0.0.167", - "v0.0.168", - "v0.0.169", - "v0.0.170", - "v0.0.171", - "v0.0.172", - "v0.0.173", - "v0.0.174", - "v0.0.175", - "v0.0.176", - "v0.0.177", - "v0.0.178", - "v0.0.179", - "v0.0.180", - "v0.0.181", - "v0.0.182", - "v0.0.183", - "v0.0.184", - "v0.0.185", - "v0.0.186", - "v0.0.187", - "v0.0.188", - "v0.0.189", - "v0.0.190", - "v0.0.191", - "v0.0.192", - "v0.0.193", - "v0.0.194", - "v0.0.64", - "v0.0.65", - "v0.0.66", - "v0.0.67", - "v0.0.68", - "v0.0.69", - "v0.0.70", - "v0.0.71", - "v0.0.72", - "v0.0.73", - "v0.0.74", - "v0.0.75", - "v0.0.76", - "v0.0.77", - "v0.0.78", - "v0.0.79", - "v0.0.80", - "v0.0.81", - "v0.0.82", - "v0.0.83", - "v0.0.84", - "v0.0.85", - "v0.0.86", - "v0.0.87", - "v0.0.88", - "v0.0.89", - "v0.0.90", - "v0.0.91", - "v0.0.92", - "v0.0.93", - "v0.0.94", - "v0.0.95", - "v0.0.96", - "v0.0.97", - "v0.0.98", - "v0.0.99" - ], - "database_specific": { - "source": "https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2023-36095.json" - } - } - ], - "schema_version": "1.6.0", - "severity": [ - { - "type": "CVSS_V3", - "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" - } - ] -} \ No newline at end of file diff --git a/src/test/resources/vulnerabilities.redis b/src/test/resources/vulnerabilities.redis index 6be412e..e4c36e3 100644 --- a/src/test/resources/vulnerabilities.redis +++ b/src/test/resources/vulnerabilities.redis @@ -1,6 +1,3 @@ -JSON.SET cves:CVE-2022-24683 $ '{"affected": [{"package": {"ecosystem": "Go","name": "github.com/hashicorp/nomad","purl": "pkg:golang/github.com/hashicorp/nomad"},"ranges": [{"events": [{"introduced": "0.9.2"},{"fixed": "1.0.18"}],"type": "SEMVER"}]},{"package": {"ecosystem": "Go","name": "github.com/hashicorp/nomad","purl": "pkg:golang/github.com/hashicorp/nomad"},"ranges": [{"events": [{"introduced": "1.1.0"},{"fixed": "1.1.12"}],"type": "SEMVER"}]},{"package": {"ecosystem": "Go","name": "github.com/hashicorp/nomad","purl": "pkg:golang/github.com/hashicorp/nomad"},"ranges": [{"events": [{"introduced": "1.2.0"},{"fixed": "1.2.6"}],"type": "SEMVER"}]}],"created": "2024-01-12T08:42:52.166+00:00","cveId": "CVE-2022-24683","description": "Nomad is an easy-to-use, flexible, and performant workload orchestrator that can deploy a mix of microservice, batch, containerized, and non-containerized applications. HashiCorp Nomad and Nomad Enterprise 0.9.2 through 1.0.17, 1.1.11, and 1.2.5 allow operators with read-fs and alloc-exec (or job-submit) capabilities to read arbitrary files on the host filesystem as root. There are currently no known workarounds. Users are recommended to upgrade as soon as possible to avoid this issue.","severity": [{"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","type": "CVSS_V3"}],"summary": "Arbitrary file reads in HashiCorp Nomad"}' -JSON.SET cves:CVE-2022-30324 $ '{"affected": [{"package": {"ecosystem": "Go","name": "github.com/hashicorp/nomad","purl": "pkg:golang/github.com/hashicorp/nomad"},"ranges": [{"events": [{"introduced": "0.2.0"},{"fixed": "1.1.14"}],"type": "SEMVER"}]},{"package": {"ecosystem": "Go","name": "github.com/hashicorp/nomad","purl": "pkg:golang/github.com/hashicorp/nomad"},"ranges": [{"events": [{"introduced": "1.2.0"},{"fixed": "1.2.8"}],"type": "SEMVER"}]},{"package": {"ecosystem": "Go","name": "github.com/hashicorp/nomad","purl": "pkg:golang/github.com/hashicorp/nomad"},"ranges": [{"events": [{"introduced": "1.3.0"},{"fixed": "1.3.1"}],"type": "SEMVER"}]}],"created": "2024-01-12T08:42:52.166+00:00","cveId": "CVE-2022-30324","description": "HashiCorp Nomad and Nomad Enterprise version 0.2.0 up to 1.3.0 were impacted by go-getter vulnerabilities enabling privilege escalation through the artifact stanza in submitted jobs onto the client agent host. Fixed in 1.1.14, 1.2.8, and 1.3.1.","severity": [{"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","type": "CVSS_V3"}],"summary": "Privilege escalation in Hashicorp Nomad"}' - -SET alias:GHSA-wmrx-57hm-mw7r '{"id": "GHSA-wmrx-57hm-mw7r", "cveId": "CVE-2022-24683"}' -SET alias:GHSA-526x-rm7j-v389 '{"id": "GHSA-526x-rm7j-v389", "cveId": "CVE-2022-30324"}' -SET alias:CVE-2022-30324 '{"id": "CVE-2022-30324", "cveId": "CVE-2022-30324"}' \ No newline at end of file +SET vuln:CVE-2022-31026 '{ "id" : "CVE-2022-31026", "details" : "Trilogy is a client library for MySQL. When authenticating, a malicious server could return a specially crafted authentication packet, causing the client to read and return up to 12 bytes of data from an uninitialized variable in stack memory. Users of the trilogy gem should upgrade to version 2.1.1 This issue can be avoided by only connecting to trusted servers.", "aliases" : [ "GHSA-5g4r-2qhx-vqfm" ], "modified" : "2023-11-29T09:40:48.924299Z", "published" : "2022-06-09T13:15:08Z", "withdrawn" : "2024-05-15T05:32:08.432999Z", "references" : [ { "type" : "ADVISORY", "url" : "https://github.com/github/trilogy/security/advisories/GHSA-5g4r-2qhx-vqfm" }, { "type" : "FIX", "url" : "https://github.com/github/trilogy/commit/6bed62789eaf119902b0fe247d2a91d56c31a962" } ], "affected" : [ { "ranges" : [ { "type" : "GIT", "repo" : "https://github.com/github/trilogy", "events" : [ { "introduced" : "0" }, { "fixed" : "6bed62789eaf119902b0fe247d2a91d56c31a962" } ] } ], "versions" : [ "2.0.0", "v2.1.0" ], "database_specific" : { "source" : "https://storage.googleapis.com/cve-osv-conversion/osv-output/CVE-2022-31026.json" } } ], "schema_version" : "1.6.0", "severity" : [ { "type" : "CVSS_V3", "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N" } ]}' +SET vuln:GHSA-9p26-698r-w4hx '{ "id" : "GHSA-9p26-698r-w4hx", "summary" : "BuildKit vulnerable to possible panic when incorrect parameters sent from frontend", "details" : "### ImpactA malicious BuildKit client or frontend could craft a request that could lead to BuildKit daemon crashing with a panic.### PatchesThe issue has been fixed in v0.12.5### WorkaroundsAvoid using BuildKit frontends from untrusted sources. A frontend image is usually specified as the `#syntax` line on your Dockerfile, or with `--frontend` flag when using `buildctl build` command. ### References", "aliases" : [ "CVE-2024-23650", "GO-2024-2492" ], "modified" : "2024-03-04T18:43:34Z", "published" : "2024-01-31T22:43:54Z", "database_specific" : { "nvd_published_at" : "2024-01-31T22:15:53Z", "cwe_ids" : [ "CWE-754" ], "severity" : "MODERATE", "github_reviewed" : true, "github_reviewed_at" : "2024-01-31T22:43:54Z" }, "references" : [ { "type" : "WEB", "url" : "https://github.com/moby/buildkit/security/advisories/GHSA-9p26-698r-w4hx" }, { "type" : "ADVISORY", "url" : "https://nvd.nist.gov/vuln/detail/CVE-2024-23650" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/pull/4601" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/481d9c45f473c58537f39694a38d7995cc656987" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/7718bd5c3dc8fc5cd246a30cc41766e7a53c043c" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/83edaef59d545b93e2750f1f85675a3764593fee" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/96663dd35bf3787d7efb1ee7fd9ac7fe533582ae" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/e1924dc32da35bfb0bfdbb9d0fc7bca25e552330" }, { "type" : "PACKAGE", "url" : "https://github.com/moby/buildkit" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/releases/tag/v0.12.5" } ], "affected" : [ { "package" : { "name" : "github.com/moby/buildkit", "ecosystem" : "Go", "purl" : "pkg:golang/github.com/moby/buildkit" }, "ranges" : [ { "type" : "SEMVER", "events" : [ { "introduced" : "0" }, { "fixed" : "0.12.5" } ] } ], "database_specific" : { "source" : "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/01/GHSA-9p26-698r-w4hx/GHSA-9p26-698r-w4hx.json" } } ], "schema_version" : "1.6.0", "severity" : [ { "type" : "CVSS_V3", "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" } ]}' +SET vuln:CVE-2024-23650 '{ "id" : "CVE-2024-23650", "summary" : "BuildKit vulnerable to possible panic when incorrect parameters sent from frontend", "details" : "### ImpactA malicious BuildKit client or frontend could craft a request that could lead to BuildKit daemon crashing with a panic.### PatchesThe issue has been fixed in v0.12.5### WorkaroundsAvoid using BuildKit frontends from untrusted sources. A frontend image is usually specified as the `#syntax` line on your Dockerfile, or with `--frontend` flag when using `buildctl build` command. ### References", "aliases" : [ "GHSA-9p26-698r-w4hx", "GO-2024-2492" ], "modified" : "2024-03-04T18:43:34Z", "published" : "2024-01-31T22:43:54Z", "database_specific" : { "nvd_published_at" : "2024-01-31T22:15:53Z", "cwe_ids" : [ "CWE-754" ], "severity" : "MODERATE", "github_reviewed" : true, "github_reviewed_at" : "2024-01-31T22:43:54Z" }, "references" : [ { "type" : "WEB", "url" : "https://github.com/moby/buildkit/security/advisories/GHSA-9p26-698r-w4hx" }, { "type" : "ADVISORY", "url" : "https://nvd.nist.gov/vuln/detail/CVE-2024-23650" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/pull/4601" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/481d9c45f473c58537f39694a38d7995cc656987" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/7718bd5c3dc8fc5cd246a30cc41766e7a53c043c" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/83edaef59d545b93e2750f1f85675a3764593fee" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/96663dd35bf3787d7efb1ee7fd9ac7fe533582ae" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/commit/e1924dc32da35bfb0bfdbb9d0fc7bca25e552330" }, { "type" : "PACKAGE", "url" : "https://github.com/moby/buildkit" }, { "type" : "WEB", "url" : "https://github.com/moby/buildkit/releases/tag/v0.12.5" } ], "affected" : [ { "package" : { "name" : "github.com/moby/buildkit", "ecosystem" : "Go", "purl" : "pkg:golang/github.com/moby/buildkit" }, "ranges" : [ { "type" : "SEMVER", "events" : [ { "introduced" : "0" }, { "fixed" : "0.12.5" } ] } ], "database_specific" : { "source" : "https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2024/01/GHSA-9p26-698r-w4hx/GHSA-9p26-698r-w4hx.json" } } ], "schema_version" : "1.6.0", "severity" : [ { "type" : "CVSS_V3", "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L" } ]}' \ No newline at end of file