Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port: Add EPSS conditions to policies #834

Merged
merged 1 commit into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {

Check warning on line 50 in src/test/java/org/dependencytrack/policy/cel/compat/EpssConditionTest.java

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

src/test/java/org/dependencytrack/policy/cel/compat/EpssConditionTest.java#L50

Avoid unused private methods such as '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();
}
}

}