Skip to content

Commit

Permalink
Add EPSS conditions to policies (#834)
Browse files Browse the repository at this point in the history
  • Loading branch information
nscuro authored Aug 8, 2024
1 parent 62bd2e5 commit 1419fe7
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/main/java/org/dependencytrack/model/PolicyCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public enum Subject {
COMPONENT_HASH(PolicyViolation.Type.OPERATIONAL),
CWE(PolicyViolation.Type.SECURITY),
VULNERABILITY_ID(PolicyViolation.Type.SECURITY),
VERSION_DISTANCE(PolicyViolation.Type.OPERATIONAL);
VERSION_DISTANCE(PolicyViolation.Type.OPERATIONAL),
EPSS(PolicyViolation.Type.SECURITY);

private final PolicyViolation.Type violationType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.dependencytrack.policy.cel.compat.CoordinatesCelPolicyScriptSourceBuilder;
import org.dependencytrack.policy.cel.compat.CpeCelPolicyScriptSourceBuilder;
import org.dependencytrack.policy.cel.compat.CweCelPolicyScriptSourceBuilder;
import org.dependencytrack.policy.cel.compat.EpssCelPolicyScriptSourceBuilder;
import org.dependencytrack.policy.cel.compat.LicenseCelPolicyScriptSourceBuilder;
import org.dependencytrack.policy.cel.compat.LicenseGroupCelPolicyScriptSourceBuilder;
import org.dependencytrack.policy.cel.compat.PackageUrlCelPolicyScriptSourceBuilder;
Expand Down Expand Up @@ -120,6 +121,7 @@ public class CelPolicyEngine {
SCRIPT_BUILDERS.put(Subject.VERSION, new VersionCelPolicyScriptSourceBuilder());
SCRIPT_BUILDERS.put(Subject.AGE, new ComponentAgeCelPolicyScriptSourceBuilder());
SCRIPT_BUILDERS.put(Subject.VERSION_DISTANCE, new VersionDistanceCelScriptBuilder());
SCRIPT_BUILDERS.put(Subject.EPSS, new EpssCelPolicyScriptSourceBuilder());
}

private final CelPolicyScriptHost scriptHost;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* 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.cel.compat;

import alpine.common.logging.Logger;
import org.dependencytrack.model.PolicyCondition;

/**
* @since 5.6.0
*/
public class EpssCelPolicyScriptSourceBuilder implements CelPolicyScriptSourceBuilder {

private static final Logger LOGGER = Logger.getLogger(EpssCelPolicyScriptSourceBuilder.class);

@Override
public String apply(final PolicyCondition policyCondition) {
final double conditionValue;
try {
conditionValue = Double.parseDouble(policyCondition.getValue());
} catch (RuntimeException e) {
LOGGER.warn("Invalid value for EPSS condition: %s".formatted(policyCondition.getValue()), e);
return null;
}

final String scriptSrcTemplate = switch (policyCondition.getOperator()) {
case NUMERIC_GREATER_THAN -> "vulns.exists(vuln, vuln.epss_score > %s)";
case NUMERIC_GREATER_THAN_OR_EQUAL -> "vulns.exists(vuln, vuln.epss_score >= %s)";
case NUMERIC_EQUAL -> "vulns.exists(vuln, vuln.epss_score == %s)";
case NUMERIC_NOT_EQUAL -> "vulns.exists(vuln, vuln.epss_score != %s)";
case NUMERIC_LESSER_THAN_OR_EQUAL -> "vulns.exists(vuln, vuln.epss_score <= %s)";
case NUMERIC_LESS_THAN -> "vulns.exists(vuln, vuln.epss_score < %s)";
default -> null;
};
if (scriptSrcTemplate == null) {
LOGGER.warn("Operator %s is not supported for EPSS conditions".formatted(policyCondition.getOperator()));
return null;
}

return scriptSrcTemplate.formatted(conditionValue);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* 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.cel.compat;

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.dependencytrack.PersistenceCapableTest;
import org.dependencytrack.model.AnalyzerIdentity;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.Epss;
import org.dependencytrack.model.Policy;
import org.dependencytrack.model.PolicyCondition;
import org.dependencytrack.model.PolicyCondition.Operator;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.policy.cel.CelPolicyEngine;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.math.BigDecimal;

import static org.assertj.core.api.Assertions.assertThat;
import static org.dependencytrack.model.PolicyCondition.Operator.MATCHES;
import static org.dependencytrack.model.PolicyCondition.Operator.NUMERIC_EQUAL;
import static org.dependencytrack.model.PolicyCondition.Operator.NUMERIC_GREATER_THAN;
import static org.dependencytrack.model.PolicyCondition.Operator.NUMERIC_GREATER_THAN_OR_EQUAL;
import static org.dependencytrack.model.PolicyCondition.Operator.NUMERIC_LESSER_THAN_OR_EQUAL;
import static org.dependencytrack.model.PolicyCondition.Operator.NUMERIC_LESS_THAN;
import static org.dependencytrack.model.PolicyCondition.Operator.NUMERIC_NOT_EQUAL;

@RunWith(JUnitParamsRunner.class)
public class EpssConditionTest extends PersistenceCapableTest {

private Object[] parameters() {
return new Object[]{
// NUMERIC_GREATER_THAN with match.
new Object[]{NUMERIC_GREATER_THAN, "0.666", 0.667, true},
// NUMERIC_GREATER_THAN with no match.
new Object[]{NUMERIC_GREATER_THAN, "0.666", 0.665, false},
// NUMERIC_GREATER_THAN_OR_EQUAL with match.
new Object[]{NUMERIC_GREATER_THAN_OR_EQUAL, "0.666", 0.666, true},
new Object[]{NUMERIC_GREATER_THAN_OR_EQUAL, "0.666", 0.667, true},
// NUMERIC_GREATER_THAN_OR_EQUAL with no match.
new Object[]{NUMERIC_GREATER_THAN_OR_EQUAL, "0.666", 0.665, false},
// NUMERIC_EQUAL with match.
new Object[]{NUMERIC_EQUAL, "0.666", 0.666, true},
// NUMERIC_EQUAL with no match.
new Object[]{NUMERIC_EQUAL, "0.666", 0.667, false},
// NUMERIC_NOT_EQUAL with match.
new Object[]{NUMERIC_NOT_EQUAL, "0.666", 0.667, true},
// NUMERIC_NOT_EQUAL with no match.
new Object[]{NUMERIC_NOT_EQUAL, "0.666", 0.666, false},
// NUMERIC_LESSER_THAN_OR_EQUAL with match.
new Object[]{NUMERIC_LESSER_THAN_OR_EQUAL, "0.666", 0.666, true},
new Object[]{NUMERIC_LESSER_THAN_OR_EQUAL, "0.666", 0.665, true},
// NUMERIC_LESSER_THAN_OR_EQUAL with no match.
new Object[]{NUMERIC_LESSER_THAN_OR_EQUAL, "0.666", 0.667, false},
// NUMERIC_LESS_THAN with match.
new Object[]{NUMERIC_LESS_THAN, "0.666", 0.665, true},
// NUMERIC_LESS_THAN with no match.
new Object[]{NUMERIC_LESS_THAN, "0.666", 0.667, false},
// Invalid operator.
new Object[]{MATCHES, "0.666", 0.666, false},
// Vulnerability without EPSS score.
new Object[]{NUMERIC_EQUAL, "0.666", null, false},
// No condition value.
new Object[]{NUMERIC_EQUAL, "", 0.666, false},
// Invalid condition value.
new Object[]{NUMERIC_EQUAL, "foo", 0.666, false},
};
}

@Test
@Parameters(method = "parameters")
public void evaluateTest(
final Operator operator,
final String conditionValue,
final Double vulnEpssScore,
final boolean expectViolation
) {
final Policy policy = qm.createPolicy("policy", Policy.Operator.ANY, Policy.ViolationState.INFO);
qm.createPolicyCondition(policy, PolicyCondition.Subject.EPSS, operator, conditionValue);

final var project = new Project();
project.setName("acme-app");
qm.persist(project);

final var component = new Component();
component.setProject(project);
component.setName("acme-lib");
qm.persist(component);

final var vuln = new Vulnerability();
vuln.setVulnId("CVE-123");
vuln.setSource(Vulnerability.Source.NVD);
qm.persist(vuln);

qm.addVulnerability(vuln, component, AnalyzerIdentity.INTERNAL_ANALYZER);

final var epss = new Epss();
epss.setCve("CVE-123");
epss.setScore(vulnEpssScore != null ? BigDecimal.valueOf(vulnEpssScore) : null);
qm.persist(epss);

new CelPolicyEngine().evaluateProject(project.getUuid());
if (expectViolation) {
assertThat(qm.getAllPolicyViolations(component)).hasSize(1);
} else {
assertThat(qm.getAllPolicyViolations(component)).isEmpty();
}
}

}

0 comments on commit 1419fe7

Please sign in to comment.