diff --git a/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java b/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java index b6aac7965..51752c586 100644 --- a/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java +++ b/src/main/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessor.java @@ -51,6 +51,7 @@ import org.dependencytrack.persistence.jdbi.NotificationSubjectDao; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyEvaluator; +import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyOperation; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyRating; import org.dependencytrack.proto.notification.v1.Group; import org.dependencytrack.proto.vulnanalysis.v1.ScanKey; @@ -205,8 +206,21 @@ private void processScannerResult(final QueryManager qm, final Component compone LOGGER.debug("Identified policy matches for %d/%d vulnerabilities (scanKey: %s)" .formatted(matchedPoliciesByVulnUuid.size(), syncedVulns.size(), prettyPrint(scanKey))); + // Log the matched policies with operation mode LOG + final List loggablePolicies = matchedPoliciesByVulnUuid.entrySet().stream() + .filter(policy -> policy.getValue().getOperationMode() == VulnerabilityPolicyOperation.LOG) + .map(policy -> policy.getValue().getName()).toList(); + if (!loggablePolicies.isEmpty()) { + LOGGER.info("List of matched vulnerability policies with mode LOG : " + loggablePolicies); + } + + final Map actionablePolicies = matchedPoliciesByVulnUuid.entrySet().stream() + .filter(policy -> policy.getValue().getOperationMode() == VulnerabilityPolicyOperation.APPLY) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // Perform analysis for only actionable policies. final List newVulnUuids = synchronizeFindingsAndAnalyses(qm, component, syncedVulns, - scannerResult.getScanner(), matchedPoliciesByVulnUuid); + scannerResult.getScanner(), actionablePolicies); LOGGER.debug("Identified %d new vulnerabilities for %s with %s (scanKey: %s)" .formatted(newVulnUuids.size(), scanKey.getComponentUuid(), scannerResult.getScanner(), prettyPrint(scanKey))); @@ -446,7 +460,6 @@ private List maybeApplyPolicyAnalyses(QueryManager qm, final Dao .collect(Collectors.toMap(Analysis::getVulnUuid, Function.identity())); final var analysesToCreateOrUpdate = new ArrayList(); - final var projectAuditChangeNotifications = new ArrayList(); final var analysisCommentsByVulnId = new MultivaluedHashMap(); for (final Map.Entry vulnUuidAndPolicy : policiesByVulnUuid.entrySet()) { diff --git a/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java b/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java index a3fea93d2..ce3747161 100644 --- a/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java +++ b/src/main/java/org/dependencytrack/model/VulnerabilityPolicy.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyAnalysis; +import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyOperation; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyRating; import javax.jdo.annotations.Column; @@ -96,6 +97,10 @@ public class VulnerabilityPolicy implements Serializable { @Persistent(defaultFetchGroup = "true") private List ratings; + @Column(name = "OPERATION_MODE", allowsNull = "false") + @Persistent(defaultFetchGroup = "true") + private VulnerabilityPolicyOperation operationMode; + public long getId() { return id; } @@ -183,5 +188,13 @@ public List getRatings() { public void setRatings(List ratings) { this.ratings = ratings; } + + public VulnerabilityPolicyOperation getOperationMode() { + return operationMode; + } + + public void setOperationMode(VulnerabilityPolicyOperation operationMode) { + this.operationMode = operationMode; + } } diff --git a/src/main/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDao.java b/src/main/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDao.java index 4f57aaa24..4fcbb7653 100644 --- a/src/main/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDao.java +++ b/src/main/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDao.java @@ -59,8 +59,9 @@ public interface VulnerabilityPolicyDao extends SqlObject { WHERE ("VALID_FROM" IS NULL OR "VALID_FROM" <= NOW()) AND ("VALID_UNTIL" IS NULL OR "VALID_UNTIL" >= NOW()) + AND ("OPERATION_MODE" != 'DISABLED') """) - List getAllValid(); + List getAllEnabledAndValid(); @SqlQuery(""" SELECT @@ -74,6 +75,7 @@ public interface VulnerabilityPolicyDao extends SqlObject { "UPDATED" AS "updated", "VALID_FROM" AS "validFrom", "VALID_UNTIL" AS "validUntil", + "OPERATION_MODE" AS "operationMode", COUNT(*) OVER() AS "totalCount" FROM "VULNERABILITY_POLICY" @@ -84,7 +86,7 @@ public interface VulnerabilityPolicyDao extends SqlObject { ${offsetAndLimit!} """) @UseRowReducer(PaginatedVulnerabilityPolicyRowReducer.class) - PaginatedResult getPageWithNameLike(@DefineOrdering(allowedColumns = {"id", "author", "created", "name", "updated", "validFrom", "validUntil"}, alsoBy = "id") Ordering ordering, + PaginatedResult getPageWithNameLike(@DefineOrdering(allowedColumns = {"id", "author", "created", "name", "updated", "validFrom", "validUntil", "operationMode"}, alsoBy = "id") Ordering ordering, @DefinePagination Pagination pagination, @Bind String name); @SqlQuery(""" @@ -94,9 +96,9 @@ PaginatedResult getPageWithNameLike(@DefineOrdering(allowedColumns = {"id", "aut @SqlUpdate(""" INSERT INTO "VULNERABILITY_POLICY" - ("ANALYSIS", "AUTHOR", "CONDITIONS", "CREATED", "DESCRIPTION", "NAME", "RATINGS", "VALID_FROM", "VALID_UNTIL") + ("ANALYSIS", "AUTHOR", "CONDITIONS", "CREATED", "DESCRIPTION", "NAME", "RATINGS", "VALID_FROM", "VALID_UNTIL", "OPERATION_MODE") VALUES - ((:analysis)::JSONB, :author, :conditions, NOW(), :description, :name, (:ratings)::JSONB, :validFrom, :validUntil) + ((:analysis)::JSONB, :author, :conditions, NOW(), :description, :name, (:ratings)::JSONB, :validFrom, :validUntil, :operationMode) RETURNING * """) @GetGeneratedKeys("*") @@ -117,7 +119,8 @@ PaginatedResult getPageWithNameLike(@DefineOrdering(allowedColumns = {"id", "aut "RATINGS" = (:ratings)::JSONB, "UPDATED" = NOW(), "VALID_FROM" = :validFrom, - "VALID_UNTIL" = :validUntil + "VALID_UNTIL" = :validUntil, + "OPERATION_MODE" = :operationMode WHERE "NAME" = :name AND ( -- Using IS DISTINCT FROM instead of != for nullable columns @@ -129,6 +132,7 @@ PaginatedResult getPageWithNameLike(@DefineOrdering(allowedColumns = {"id", "aut OR "RATINGS" IS DISTINCT FROM (:ratings)::JSONB OR "VALID_FROM" IS DISTINCT FROM :validFrom OR "VALID_UNTIL" IS DISTINCT FROM :validUntil + OR "OPERATION_MODE" IS DISTINCT FROM :operationMode ) RETURNING * """) diff --git a/src/main/java/org/dependencytrack/policy/vulnerability/DatabaseVulnerabilityPolicyProvider.java b/src/main/java/org/dependencytrack/policy/vulnerability/DatabaseVulnerabilityPolicyProvider.java index ca394778a..d3e186859 100644 --- a/src/main/java/org/dependencytrack/policy/vulnerability/DatabaseVulnerabilityPolicyProvider.java +++ b/src/main/java/org/dependencytrack/policy/vulnerability/DatabaseVulnerabilityPolicyProvider.java @@ -34,7 +34,7 @@ public class DatabaseVulnerabilityPolicyProvider implements VulnerabilityPolicyP @Override public List getApplicablePolicies(final Project project) { try (final var qm = new QueryManager()) { - return jdbi(qm).withExtension(VulnerabilityPolicyDao.class, VulnerabilityPolicyDao::getAllValid); + return jdbi(qm).withExtension(VulnerabilityPolicyDao.class, VulnerabilityPolicyDao::getAllEnabledAndValid); } } diff --git a/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicy.java b/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicy.java index 4ea1564b8..8531a0d82 100644 --- a/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicy.java +++ b/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicy.java @@ -47,6 +47,8 @@ public class VulnerabilityPolicy implements Serializable { private List ratings; + private VulnerabilityPolicyOperation operationMode; + public void setName(final String name) { this.name = name; } @@ -128,4 +130,12 @@ public ZonedDateTime getValidUntil() { public List getConditions() { return conditions; } + + public VulnerabilityPolicyOperation getOperationMode() { + return operationMode; + } + + public void setOperationMode(VulnerabilityPolicyOperation operationMode) { + this.operationMode = operationMode; + } } diff --git a/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicyOperation.java b/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicyOperation.java new file mode 100644 index 000000000..c25db2aa0 --- /dev/null +++ b/src/main/java/org/dependencytrack/policy/vulnerability/VulnerabilityPolicyOperation.java @@ -0,0 +1,26 @@ +/* + * This file is part of Dependency-Track. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) OWASP Foundation. All Rights Reserved. + */ +package org.dependencytrack.policy.vulnerability; + +public enum VulnerabilityPolicyOperation { + + DISABLED, + LOG, + APPLY +} diff --git a/src/main/resources/migration/changelog-v5.5.0.xml b/src/main/resources/migration/changelog-v5.5.0.xml index ef0d8d083..fe1b1b699 100644 --- a/src/main/resources/migration/changelog-v5.5.0.xml +++ b/src/main/resources/migration/changelog-v5.5.0.xml @@ -104,4 +104,10 @@ + + + + + + \ No newline at end of file diff --git a/src/main/resources/schema/vulnerability-policy-v1.schema.json b/src/main/resources/schema/vulnerability-policy-v1.schema.json index 8019adeeb..2065e726b 100644 --- a/src/main/resources/schema/vulnerability-policy-v1.schema.json +++ b/src/main/resources/schema/vulnerability-policy-v1.schema.json @@ -80,6 +80,10 @@ "items": { "$ref": "#/$defs/rating" }, "minItems": 0, "maxItems": 3 + }, + "operationMode": { + "description": "Mode of operation for the vulnerability policy.", + "enum": ["DISABLED", "APPLY", "LOG"] } }, "$defs": { @@ -107,6 +111,6 @@ "required": ["method", "severity"] } }, - "required": ["apiVersion", "type", "name", "conditions", "analysis"], + "required": ["apiVersion", "type", "name", "conditions", "analysis", "operationMode"], "additionalProperties": false } diff --git a/src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessorTest.java b/src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessorTest.java index 06616446b..3605d8e50 100644 --- a/src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessorTest.java +++ b/src/test/java/org/dependencytrack/event/kafka/processor/VulnerabilityScanResultProcessorTest.java @@ -55,6 +55,7 @@ import org.dependencytrack.policy.vulnerability.DatabaseVulnerabilityPolicyProvider; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyAnalysis; +import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyOperation; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyRating; import org.dependencytrack.proto.notification.v1.NewVulnerabilitySubject; import org.dependencytrack.proto.notification.v1.NewVulnerableDependencySubject; @@ -645,6 +646,7 @@ public void analysisThroughPolicyNewAnalysisTest() { policy.setConditions(List.of("has(component.name)", "project.version != \"\"")); policy.setAnalysis(policyAnalysis); policy.setRatings(List.of(policyRating)); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); final var componentUuid = component.getUuid(); @@ -747,6 +749,7 @@ public void analysisThroughPolicyNewAnalysisSuppressionTest() { policy.setAuthor("Jane Doe"); policy.setConditions(List.of("has(component.name)", "project.version != \"\"")); policy.setAnalysis(policyAnalysis); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); final var componentUuid = component.getUuid(); @@ -850,6 +853,7 @@ public void analysisThroughPolicyExistingDifferentAnalysisTest() { policy.setConditions(List.of("has(component.name)", "project.version != \"\"")); policy.setAnalysis(policyAnalysis); policy.setRatings(List.of(policyRating)); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); final var componentUuid = component.getUuid(); @@ -970,6 +974,7 @@ public void analysisThroughPolicyExistingEqualAnalysisTest() { policy.setConditions(List.of("has(component.name)", "project.version != \"\"")); policy.setAnalysis(policyAnalysis); policy.setRatings(List.of(policyRating)); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); final var componentUuid = component.getUuid(); @@ -1058,6 +1063,7 @@ public void analysisThroughPolicyWithAliasesTest() { policy.setName("Foo"); policy.setConditions(List.of("vuln.aliases.exists(alias, alias.id == \"GHSA-100\" || alias.id == \"GHSA-200\")")); policy.setAnalysis(policyAnalysis); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); // Report three vulnerabilities for the component: @@ -1140,6 +1146,7 @@ public void analysisThroughPolicyResetOnNoMatchTest() { policy.setName("Foo"); policy.setConditions(List.of("component.name == \"some-other-name\"")); policy.setAnalysis(policyAnalysis); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); // Create vulnerability with existing analysis that was previously applied by the above policy, @@ -1273,6 +1280,7 @@ public void analysisThroughPolicyWithPoliciesNotYetValidOrNotValidAnymoreTest() notYetValidPolicy.setValidFrom(ZonedDateTime.ofInstant(Instant.now().plusSeconds(180), ZoneOffset.UTC)); notYetValidPolicy.setConditions(List.of("true")); notYetValidPolicy.setAnalysis(notYetValidPolicyAnalysis); + notYetValidPolicy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(notYetValidPolicy)); final var notValidAnymorePolicyAnalysis = new VulnerabilityPolicyAnalysis(); @@ -1345,6 +1353,7 @@ public void analysisThroughPolicyWithAnalysisUpdateNotOnStateOrSuppressionTest() policy.setName("Foo"); policy.setConditions(List.of("true")); policy.setAnalysis(policyAnalysis); + policy.setOperationMode(VulnerabilityPolicyOperation.APPLY); jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(policy)); final var componentUuid = component.getUuid(); @@ -1368,6 +1377,55 @@ public void analysisThroughPolicyWithAnalysisUpdateNotOnStateOrSuppressionTest() record -> assertThat(record.topic()).isEqualTo(KafkaTopics.NOTIFICATION_PROJECT_AUDIT_CHANGE.name())); } + @Test + public void analysisThroughPolicyWithPoliciesLoggableTest() { + final var project = new Project(); + project.setName("acme-app"); + project.setVersion("1.0.0"); + qm.persist(project); + + final var component = new Component(); + component.setName("acme-lib"); + component.setVersion("1.1.0"); + component.setProject(project); + qm.persist(component); + + final var policyAnalysis = new VulnerabilityPolicyAnalysis(); + policyAnalysis.setState(VulnerabilityPolicyAnalysis.State.FALSE_POSITIVE); + policyAnalysis.setJustification(VulnerabilityPolicyAnalysis.Justification.CODE_NOT_REACHABLE); + policyAnalysis.setVendorResponse(VulnerabilityPolicyAnalysis.Response.WILL_NOT_FIX); + policyAnalysis.setSuppress(true); + final var loggablePolicy = new VulnerabilityPolicy(); + loggablePolicy.setName("NotActionable"); + loggablePolicy.setConditions(List.of("true")); + loggablePolicy.setAnalysis(policyAnalysis); + loggablePolicy.setOperationMode(VulnerabilityPolicyOperation.LOG); + jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.create(loggablePolicy)); + + final var vuln = new Vulnerability(); + vuln.setVulnId("CVE-100"); + vuln.setSource(Vulnerability.Source.NVD); + vuln.setSeverity(Severity.CRITICAL); + qm.persist(vuln); + + final var componentUuid = component.getUuid(); + final var scanToken = UUID.randomUUID().toString(); + final var scanKey = ScanKey.newBuilder().setScanToken(scanToken).setComponentUuid(componentUuid.toString()).build(); + final var scanResult = ScanResult.newBuilder() + .setKey(scanKey) + .addScannerResults(ScannerResult.newBuilder() + .setScanner(SCANNER_INTERNAL) + .setStatus(SCAN_STATUS_SUCCESSFUL) + .setBom(Bom.newBuilder().addAllVulnerabilities(List.of( + createVuln(vuln.getVulnId(), vuln.getSource()) + )))) + .build(); + processor.process(aConsumerRecord(scanKey, scanResult).build()); + + qm.getPersistenceManager().evictAll(); + assertThat(qm.getAnalysis(component, vuln)).isNull(); + } + private org.cyclonedx.proto.v1_4.Vulnerability createVuln(final String id, final String source) { return org.cyclonedx.proto.v1_4.Vulnerability.newBuilder() .setId(id) diff --git a/src/test/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDaoTest.java b/src/test/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDaoTest.java index 37a33efea..b732aa62b 100644 --- a/src/test/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDaoTest.java +++ b/src/test/java/org/dependencytrack/persistence/jdbi/VulnerabilityPolicyDaoTest.java @@ -19,7 +19,6 @@ package org.dependencytrack.persistence.jdbi; import alpine.persistence.PaginatedResult; -import org.apache.commons.lang3.function.TriConsumer; import org.dependencytrack.PersistenceCapableTest; import org.dependencytrack.model.Analysis; import org.dependencytrack.model.AnalysisComment; @@ -31,6 +30,7 @@ import org.dependencytrack.model.Vulnerability; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyAnalysis; +import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyOperation; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyRating; import org.jdbi.v3.core.Handle; import org.junit.After; @@ -98,6 +98,7 @@ private static VulnerabilityPolicy getVulnerabilityPolicyInstance() throws Parse rating.setScore(6.3); rating.setVector("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"); vulnPolicy.setRatings(List.of(rating)); + vulnPolicy.setOperationMode(VulnerabilityPolicyOperation.LOG); return vulnPolicy; } @@ -133,45 +134,43 @@ public void testVulnerabilityPolicyIsCreated() throws Exception { assertThat(rating1.getSeverity()).isEqualTo(VulnerabilityPolicyRating.Severity.HIGH); assertThat(rating1.getVector()).isEqualTo("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"); }); + assertThat(vulnerabilityPolicy.getOperationMode()).isEqualTo(VulnerabilityPolicyOperation.LOG); }); } @Test - public void testGetAllValid() { - final TriConsumer createPolicy = (name, validFrom, validUntil) -> { - final var analysis = new VulnerabilityPolicyAnalysis(); - analysis.setState(VulnerabilityPolicyAnalysis.State.FALSE_POSITIVE); - final var policy = new org.dependencytrack.policy.vulnerability.VulnerabilityPolicy(); - policy.setName(name); - policy.setValidFrom(validFrom); - policy.setValidUntil(validUntil); - policy.setConditions(List.of("true")); - policy.setAnalysis(analysis); - vulnPolicyDao.create(policy); - }; + public void testGetAllEnabledAndValid() { // Without validFrom and validUntil - createPolicy.accept("Foo-001", null, null); + createPolicy("Foo-001", null, null, VulnerabilityPolicyOperation.APPLY); // validFrom in the future - createPolicy.accept("Foo-002", ZonedDateTime.ofInstant(Instant.now().plusSeconds(180), ZoneOffset.UTC), null); + createPolicy("Foo-002", ZonedDateTime.ofInstant(Instant.now().plusSeconds(180), ZoneOffset.UTC), null, VulnerabilityPolicyOperation.APPLY); // validFrom in the past - createPolicy.accept("Foo-003", ZonedDateTime.ofInstant(Instant.now().minusSeconds(180), ZoneOffset.UTC), null); + createPolicy("Foo-003", ZonedDateTime.ofInstant(Instant.now().minusSeconds(180), ZoneOffset.UTC), null, VulnerabilityPolicyOperation.APPLY); // validUntil in the future - createPolicy.accept("Foo-004", null, ZonedDateTime.ofInstant(Instant.now().plusSeconds(180), ZoneOffset.UTC)); + createPolicy("Foo-004", null, ZonedDateTime.ofInstant(Instant.now().plusSeconds(180), ZoneOffset.UTC), VulnerabilityPolicyOperation.APPLY); // validUntil in the past - createPolicy.accept("Foo-005", null, ZonedDateTime.ofInstant(Instant.now().minusSeconds(180), ZoneOffset.UTC)); + createPolicy("Foo-005", null, ZonedDateTime.ofInstant(Instant.now().minusSeconds(180), ZoneOffset.UTC), VulnerabilityPolicyOperation.APPLY); - final List validPolicies = vulnPolicyDao.getAllValid(); + // validUntil in the past + createPolicy("Foo-006", null, null, VulnerabilityPolicyOperation.DISABLED); + + // validUntil in the past + createPolicy("Foo-007", null, null, VulnerabilityPolicyOperation.LOG); + + final List validPolicies = vulnPolicyDao.getAllEnabledAndValid(); assertThat(validPolicies).extracting(VulnerabilityPolicy::getName).containsExactly( "Foo-001", // Foo-002 is not yet valid "Foo-003", - "Foo-004" + "Foo-004", // Foo-005 is expired + // Foo-006 is Disabled + "Foo-007" ); } @@ -231,7 +230,7 @@ public void testGetByName() throws Exception { assertThat(rating1.getSeverity()).isEqualTo(VulnerabilityPolicyRating.Severity.HIGH); assertThat(rating1.getVector()).isEqualTo("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"); }); - + assertThat(vulnerabilityPolicy.getOperationMode()).isEqualTo(VulnerabilityPolicyOperation.LOG); } @Test @@ -257,6 +256,7 @@ public void testUpdate() throws Exception { assertNotNull(createdPolicy); createdPolicy.setAuthor("Jon Doe"); + createdPolicy.setOperationMode(VulnerabilityPolicyOperation.DISABLED); VulnerabilityPolicy updatedPolicy = vulnPolicyDao.update(createdPolicy); @@ -279,7 +279,7 @@ public void testUpdate() throws Exception { assertThat(rating1.getSeverity()).isEqualTo(VulnerabilityPolicyRating.Severity.HIGH); assertThat(rating1.getVector()).isEqualTo("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"); }); - + assertThat(updatedPolicy.getOperationMode()).isEqualTo(VulnerabilityPolicyOperation.DISABLED); } @Test @@ -367,4 +367,16 @@ public void testVulnerabilityPolicyIsUnassignedAndDeleted() throws Exception { ); } + private VulnerabilityPolicy createPolicy(String name, ZonedDateTime validFrom, ZonedDateTime validUntil, VulnerabilityPolicyOperation operationMode) { + final var analysis = new VulnerabilityPolicyAnalysis(); + analysis.setState(VulnerabilityPolicyAnalysis.State.FALSE_POSITIVE); + final var policy = new org.dependencytrack.policy.vulnerability.VulnerabilityPolicy(); + policy.setName(name); + policy.setValidFrom(validFrom); + policy.setValidUntil(validUntil); + policy.setConditions(List.of("true")); + policy.setAnalysis(analysis); + policy.setOperationMode(operationMode); + return vulnPolicyDao.create(policy); + } } diff --git a/src/test/java/org/dependencytrack/policy/validation/PolicySchemaValidationTest.java b/src/test/java/org/dependencytrack/policy/validation/PolicySchemaValidationTest.java index f1ac4d6a3..bbbe488ba 100644 --- a/src/test/java/org/dependencytrack/policy/validation/PolicySchemaValidationTest.java +++ b/src/test/java/org/dependencytrack/policy/validation/PolicySchemaValidationTest.java @@ -62,7 +62,8 @@ public void testInvalidPolicyYamlWithSchema() throws IOException { error -> assertThat(error.getMessage()).isEqualTo("$.analysis.justification: does not have a value in the enumeration [CODE_NOT_PRESENT, CODE_NOT_REACHABLE, REQUIRES_CONFIGURATION, REQUIRES_DEPENDENCY, REQUIRES_ENVIRONMENT, PROTECTED_BY_COMPILER, PROTECTED_AT_RUNTIME, PROTECTED_AT_PERIMETER, PROTECTED_BY_MITIGATING_CONTROL]"), error -> assertThat(error.getMessage()).isEqualTo("$.ratings[0].severity: does not have a value in the enumeration [CRITICAL, HIGH, MEDIUM, LOW, INFO, UNASSIGNED]"), error -> assertThat(error.getMessage()).contains("$.ratings[0].vector: does not match the regex pattern"), - error -> assertThat(error.getMessage()).isEqualTo("$.ratings[0].score: string found, number expected") + error -> assertThat(error.getMessage()).isEqualTo("$.ratings[0].score: string found, number expected"), + error -> assertThat(error.getMessage()).isEqualTo("$.operationMode: is missing but it is required") ); } } diff --git a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityPolicyResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityPolicyResourceTest.java index a6c9c4677..64c061d0a 100644 --- a/src/test/java/org/dependencytrack/resources/v1/VulnerabilityPolicyResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/VulnerabilityPolicyResourceTest.java @@ -30,6 +30,7 @@ import org.dependencytrack.persistence.jdbi.VulnerabilityPolicyDao; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyAnalysis; +import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyOperation; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyRating; import org.glassfish.jersey.server.ResourceConfig; import org.junit.ClassRule; @@ -102,7 +103,8 @@ public void testVulnerablePolicies() { "vector": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L", "score": 6.3 } - ] + ], + "operationMode": "APPLY" } ] @@ -282,6 +284,7 @@ private static VulnerabilityPolicy getVulnerabilityPolicyInstance(int i) { rating.setScore(6.3); rating.setVector("CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L"); vulnPolicy.setRatings(List.of(rating)); + vulnPolicy.setOperationMode(VulnerabilityPolicyOperation.APPLY); return vulnPolicy; } diff --git a/src/test/java/org/dependencytrack/tasks/vulnerabilitypolicy/VulnerabilityPolicyFetchTaskTest.java b/src/test/java/org/dependencytrack/tasks/vulnerabilitypolicy/VulnerabilityPolicyFetchTaskTest.java index 3c1cc4d28..12c39bcbf 100644 --- a/src/test/java/org/dependencytrack/tasks/vulnerabilitypolicy/VulnerabilityPolicyFetchTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/vulnerabilitypolicy/VulnerabilityPolicyFetchTaskTest.java @@ -193,6 +193,7 @@ public void testInformWithValidPolicy() throws Exception { - component.name == "foo" analysis: state: IN_TRIAGE + operationMode: LOG """)); WireMock.stubFor(WireMock.head(WireMock.urlPathEqualTo("/bundles/bundle.zip")) @@ -250,6 +251,7 @@ public void testInformWithOneValidAndOneInvalidPolicy() throws Exception { - component.name == "foo" analysis: state: IN_TRIAGE + operationMode: LOG """; final var policyInvalid = """ apiVersion: v1.0 @@ -280,6 +282,7 @@ public void testInformWithOneValidAndOneInvalidPolicy() throws Exception { vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH score: 8.3 + operationMode: LOG """; final Path bundlePath = createPolicyBundle(List.of(policyValid, policyInvalid)); diff --git a/src/test/java/org/dependencytrack/util/VulnerabilityPolicyUtilTest.java b/src/test/java/org/dependencytrack/util/VulnerabilityPolicyUtilTest.java index 8a59446a5..3347bd9ab 100644 --- a/src/test/java/org/dependencytrack/util/VulnerabilityPolicyUtilTest.java +++ b/src/test/java/org/dependencytrack/util/VulnerabilityPolicyUtilTest.java @@ -22,6 +22,7 @@ import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.persistence.jdbi.VulnerabilityPolicyDao; import org.dependencytrack.policy.vulnerability.VulnerabilityPolicy; +import org.dependencytrack.policy.vulnerability.VulnerabilityPolicyOperation; import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.projectnessie.cel.tools.ScriptCreateException; @@ -64,7 +65,9 @@ public void testParseVulnerabilityPolicy() throws ScriptCreateException, IOExcep - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; List createVulnerabilityPolicyList = new ArrayList<>(); List updateVulnerabilityPolicyList = new ArrayList<>(); List policyNames = new ArrayList<>(); @@ -101,7 +104,9 @@ public void testParseVulnerabilityPolicyInvalid() throws ScriptCreateException, - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; List createVulnerabilityPolicyList = new ArrayList<>(); List updateVulnerabilityPolicyList = new ArrayList<>(); List policyNames = new ArrayList<>(); @@ -183,7 +188,9 @@ public void testSaveParsedVulnerabilities() throws ScriptCreateException, IOExce - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; List createVulnerabilityPolicyList = new ArrayList<>(); List updateVulnerabilityPolicyList = new ArrayList<>(); List policyNames = new ArrayList<>(); @@ -221,7 +228,9 @@ public void updateVulnerabilityPolicyTest() throws ScriptCreateException, IOExce - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; var createVulnerabilityPolicyList = new ArrayList(); var updateVulnerabilityPolicyList = new ArrayList(); var policyNames = new ArrayList(); @@ -255,11 +264,15 @@ public void updateVulnerabilityPolicyTest() throws ScriptCreateException, IOExce - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: LOG + """; VulnerabilityPolicyUtil.parseVulnerabilityPolicy(out, createVulnerabilityPolicyList, updateVulnerabilityPolicyList, policyNames); VulnerabilityPolicyUtil.saveParsedVulnerabilities(qm, createVulnerabilityPolicyList, updateVulnerabilityPolicyList, policyNames); assertEquals(1, updateVulnerabilityPolicyList.size()); - assertEquals(Double.valueOf(7.5), jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.getByName("Example2")).getRatings().get(0).getScore()); + var updatedPolicy = jdbi(qm).withExtension(VulnerabilityPolicyDao.class, dao -> dao.getByName("Example2")); + assertEquals(Double.valueOf(7.5), updatedPolicy.getRatings().get(0).getScore()); + assertEquals(VulnerabilityPolicyOperation.LOG, updatedPolicy.getOperationMode()); } @Test @@ -290,7 +303,9 @@ public void deleteVulnerabilityPolicyTest() throws ScriptCreateException, IOExce - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; String out1 = """ apiVersion: v1.0 type: Vulnerability Policy @@ -317,7 +332,9 @@ public void deleteVulnerabilityPolicyTest() throws ScriptCreateException, IOExce - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; var createVulnerabilityPolicyList = new ArrayList(); var updateVulnerabilityPolicyList = new ArrayList(); var policyNames = new ArrayList(); @@ -354,7 +371,9 @@ public void deleteVulnerabilityPolicyTest() throws ScriptCreateException, IOExce - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3"""; + score: 8.3 + operationMode: APPLY + """; createVulnerabilityPolicyList = new ArrayList<>(); VulnerabilityPolicyUtil.parseVulnerabilityPolicy(out, createVulnerabilityPolicyList, updateVulnerabilityPolicyList, policyNames); VulnerabilityPolicyUtil.saveParsedVulnerabilities(qm, createVulnerabilityPolicyList, updateVulnerabilityPolicyList, policyNames); diff --git a/src/test/resources/unit/policy/vulnerability-policy-v1-valid.yaml b/src/test/resources/unit/policy/vulnerability-policy-v1-valid.yaml index bcfd1eb02..0ec28b791 100644 --- a/src/test/resources/unit/policy/vulnerability-policy-v1-valid.yaml +++ b/src/test/resources/unit/policy/vulnerability-policy-v1-valid.yaml @@ -22,4 +22,5 @@ ratings: - method: CVSSV3 vector: CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: MEDIUM - score: 6.3 \ No newline at end of file + score: 6.3 +operationMode: APPLY \ No newline at end of file diff --git a/src/test/resources/unit/tasks/vulnerabilitypolicy/sample 2.yaml b/src/test/resources/unit/tasks/vulnerabilitypolicy/sample 2.yaml index a4aafeb4c..11de84dcc 100644 --- a/src/test/resources/unit/tasks/vulnerabilitypolicy/sample 2.yaml +++ b/src/test/resources/unit/tasks/vulnerabilitypolicy/sample 2.yaml @@ -24,3 +24,4 @@ ratings: vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH score: 8.3 +operationMode: APPLY diff --git a/src/test/resources/unit/tasks/vulnerabilitypolicy/sample.yaml b/src/test/resources/unit/tasks/vulnerabilitypolicy/sample.yaml index 427bed91d..99d35fd4e 100644 --- a/src/test/resources/unit/tasks/vulnerabilitypolicy/sample.yaml +++ b/src/test/resources/unit/tasks/vulnerabilitypolicy/sample.yaml @@ -24,3 +24,4 @@ ratings: vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH score: 8.3 +operationMode: APPLY diff --git a/src/test/resources/unit/tasks/vulnerabilitypolicy/sampleInvalid.yaml b/src/test/resources/unit/tasks/vulnerabilitypolicy/sampleInvalid.yaml index 113bb62c3..644c6d4ac 100644 --- a/src/test/resources/unit/tasks/vulnerabilitypolicy/sampleInvalid.yaml +++ b/src/test/resources/unit/tasks/vulnerabilitypolicy/sampleInvalid.yaml @@ -25,4 +25,5 @@ ratings: - method: CVSSV2 vector: CVSS:2.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L severity: HIGH - score: 8.3 \ No newline at end of file + score: 8.3 +operationMode: NONE \ No newline at end of file