Skip to content

Commit

Permalink
Merge pull request #580 from DependencyTrack/use-proto-instead-of-jso…
Browse files Browse the repository at this point in the history
…n-string

Replace json string with proto in Version Distance Cel Policy

Closes DependencyTrack/hyades#931
  • Loading branch information
nscuro authored Feb 26, 2024
2 parents 417142f + 503a0aa commit 55f9850
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) Steve Springett. All Rights Reserved.
*/
package org.dependencytrack.util;
package org.dependencytrack.model;

import alpine.common.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.dependencytrack.model.PolicyCondition;
import org.json.JSONObject;

import java.io.Serializable;
import java.util.ArrayList;
Expand Down Expand Up @@ -190,7 +188,7 @@ public static List<VersionDistance> parse(String combinedDistances) throws Numbe
result.add(new VersionDistance(0, 0, 0, patch));
}
} else {
throw new NumberFormatException("Invallid version distance: " + combinedDistances);
throw new NumberFormatException("Invalid version distance: " + combinedDistances);
}
return result;
}
Expand Down Expand Up @@ -380,17 +378,15 @@ public static boolean matches(final PolicyCondition.Operator operator, final Ver
* @param versionDistance the {@link VersionDistance} to evalue
* @return true if the condition is true for the components versionDistance, false otherwise
*/
public static boolean evaluate(final String policyConditionValue, final String policyConditionOperator, final VersionDistance versionDistance) {
public static boolean evaluate(final org.dependencytrack.proto.policy.v1.VersionDistance policyConditionValue, final String policyConditionOperator, final VersionDistance versionDistance) {
final var operator = PolicyCondition.Operator.valueOf(policyConditionOperator);
final var value = policyConditionValue;

if (!StringUtils.isEmpty(value)) {
final var json = new JSONObject(value);
final var epoch = json.optString("epoch", "0");
final var major = json.optString("major", "?");
final var minor = json.optString("minor", "?");
final var patch = json.optString("patch", "?");

if (policyConditionValue != null) {
var epoch = policyConditionValue.getEpoch().equals("") ? "0" : policyConditionValue.getEpoch();
var major = policyConditionValue.getMajor().equals("") ? "?" : policyConditionValue.getMajor();
var minor = policyConditionValue.getMinor().equals("") ? "?" : policyConditionValue.getMinor();
var patch = policyConditionValue.getPatch().equals("") ? "?" : policyConditionValue.getPatch();
final List<VersionDistance> versionDistanceList;
try {
versionDistanceList = VersionDistance.parse(epoch + ":" + major + "." + minor + "." + patch);
Expand All @@ -409,8 +405,5 @@ public static boolean evaluate(final String policyConditionValue, final String p
}
return false;


}


}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import org.dependencytrack.proto.policy.v1.Component;
import org.dependencytrack.proto.policy.v1.License;
import org.dependencytrack.proto.policy.v1.Project;
import org.dependencytrack.proto.policy.v1.VersionDistance;
import org.dependencytrack.proto.policy.v1.Vulnerability;
import org.dependencytrack.util.VersionDistance;
import org.jdbi.v3.core.Handle;
import org.jdbi.v3.core.mapper.reflect.ConstructorMapper;
import org.jdbi.v3.core.statement.Query;
Expand Down Expand Up @@ -46,6 +46,7 @@
import static org.dependencytrack.persistence.jdbi.JdbiFactory.jdbi;
import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_COMPONENT;
import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_PROJECT;
import static org.dependencytrack.policy.cel.definition.CelPolicyTypes.TYPE_VERSION_DISTANCE;

public class CelCommonPolicyLibrary implements Library {

Expand Down Expand Up @@ -117,7 +118,7 @@ public List<EnvOption> getCompileOptions() {
FUNC_COMPARE_VERSION_DISTANCE,
Decls.newInstanceOverload(
"matches_version_distance_bool",
List.of(TYPE_COMPONENT, Decls.String, Decls.String),
List.of(TYPE_COMPONENT, Decls.String, TYPE_VERSION_DISTANCE),
Decls.Bool
)
)
Expand All @@ -129,7 +130,8 @@ public List<EnvOption> getCompileOptions() {
Project.getDefaultInstance(),
Project.Property.getDefaultInstance(),
Vulnerability.getDefaultInstance(),
Vulnerability.Alias.getDefaultInstance()
Vulnerability.Alias.getDefaultInstance(),
VersionDistance.getDefaultInstance()
)
);
}
Expand Down Expand Up @@ -163,20 +165,27 @@ public List<ProgramOption> getProgramOptions() {
}

private static Val matchesVersionDistanceFunc(Val... vals) {
var basicCheckResult = basicCheck(vals);
if ((basicCheckResult instanceof BoolT && basicCheckResult.value() == Types.boolOf(false)) || basicCheckResult instanceof Err) {
return basicCheckResult;
}
var component = (Component) vals[0].value();
var value = (String) vals[2].value();
var comparator = (String) vals[1].value();
if (!component.hasLatestVersion()) {
return Err.newErr("Requested component does not have latest version information", component);
try {
var basicCheckResult = basicVersionDistanceCheck(vals);
if ((basicCheckResult instanceof BoolT && basicCheckResult.value() == Types.boolOf(false)) || basicCheckResult instanceof Err) {
return basicCheckResult;
}
var component = (Component) vals[0].value();
var comparator = (String) vals[1].value();
var value = (VersionDistance) vals[2].value();
if (!component.hasLatestVersion()) {
return Err.newErr("Requested component does not have latest version information", component);
}
return Types.boolOf(matchesVersionDistance(component, comparator, value));
}catch (Exception ex) {
LOGGER.warn("""
%s: Was unable to parse dynamic message for version distance policy;
Unable to resolve, returning false""".formatted(FUNC_COMPARE_VERSION_DISTANCE));
return Types.boolOf(false);
}
return Types.boolOf(matchesVersionDistance(component, comparator, value));
}

private static boolean matchesVersionDistance(Component component, String comparator, String value) {
private static boolean matchesVersionDistance(Component component, String comparator, VersionDistance value) {
String comparatorComputed = switch (comparator) {
case "NUMERIC_GREATER_THAN", ">" -> "NUMERIC_GREATER_THAN";
case "NUMERIC_GREATER_THAN_OR_EQUAL", ">=" -> "NUMERIC_GREATER_THAN_OR_EQUAL";
Expand All @@ -192,9 +201,9 @@ private static boolean matchesVersionDistance(Component component, String compar
Unable to resolve, returning false""".formatted(FUNC_COMPARE_VERSION_DISTANCE, comparator));
return false;
}
final VersionDistance versionDistance;
final org.dependencytrack.model.VersionDistance versionDistance;
try {
versionDistance = VersionDistance.getVersionDistance(component.getVersion(), component.getLatestVersion());
versionDistance = org.dependencytrack.model.VersionDistance.getVersionDistance(component.getVersion(), component.getLatestVersion());
} catch (RuntimeException e) {
LOGGER.warn("""
%s: Failed to compute version distance for component %s (UUID: %s), \
Expand All @@ -207,7 +216,7 @@ private static boolean matchesVersionDistance(Component component, String compar
final var celQm = new CelPolicyQueryManager(qm)) {
isDirectDependency = celQm.isDirectDependency(component);
}
return isDirectDependency && VersionDistance.evaluate(value, comparatorComputed, versionDistance);
return isDirectDependency && org.dependencytrack.model.VersionDistance.evaluate(value, comparatorComputed, versionDistance);
}

private static Val dependsOnFunc(final Val lhs, final Val rhs) {
Expand Down Expand Up @@ -289,11 +298,28 @@ private static Val isComponentOldFunc(Val... vals) {
return Types.boolOf(isComponentOld(component, comparator, dateValue));
}

private static Val basicCheck(Val... vals) {
private static Val basicVersionDistanceCheck(Val... vals) {

if (vals.length != 3) {
return Types.boolOf(false);
}
if (vals[0].value() == null || vals[1].value() == null || vals[2].value() == null) {
Val[] subVals = {vals[0], vals[1]};
Val basicCheckResult = basicPartsCheck(subVals);
if ((basicCheckResult instanceof BoolT && basicCheckResult.value().equals(Types.boolOf(false))) || basicCheckResult instanceof Err) {
return basicCheckResult;
}

if (!(vals[2].value() instanceof VersionDistance)) {
return Err.maybeNoSuchOverloadErr(vals[2]);
}
return Types.boolOf(true);
}

private static Val basicPartsCheck(Val... vals) {
if (vals.length != 2) {
return Types.boolOf(false);
}
if (vals[0].value() == null || vals[1].value() == null) {
return Types.boolOf(false);
}

Expand All @@ -304,10 +330,6 @@ private static Val basicCheck(Val... vals) {
return Err.maybeNoSuchOverloadErr(vals[1]);
}

if (!(vals[2].value() instanceof String)) {
return Err.maybeNoSuchOverloadErr(vals[2]);
}

if (!component.hasPurl()) {
return Err.newErr("Provided component does not have a purl", vals[0]);
}
Expand All @@ -321,6 +343,23 @@ private static Val basicCheck(Val... vals) {
return Types.boolOf(true);
}

private static Val basicCheck(Val... vals) {
if (vals.length != 3) {
return Types.boolOf(false);
}
Val[] subVals = {vals[0], vals[1]};
Val basicCheckResult = basicPartsCheck(subVals);
if ((basicCheckResult instanceof BoolT && basicCheckResult.value().equals(Types.boolOf(false))) || basicCheckResult instanceof Err) {
return basicCheckResult;
}

if (!(vals[2].value() instanceof String)) {
return Err.maybeNoSuchOverloadErr(vals[2]);
}

return Types.boolOf(true);
}

private static boolean dependsOn(final Project project, final Component component) {
if (project.getUuid().isBlank()) {
// Need a UUID for our starting point.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
package org.dependencytrack.policy.cel.compat;

import alpine.common.logging.Logger;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import org.apache.commons.lang3.StringUtils;
import org.dependencytrack.model.PolicyCondition;
import org.dependencytrack.proto.policy.v1.VersionDistance;

public class VersionDistanceCelScriptBuilder implements CelPolicyScriptSourceBuilder {

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

@Override
public String apply(PolicyCondition policyCondition) {
return """
component.version_distance("%s", "%s")
""".formatted(policyCondition.getOperator(), CelPolicyScriptSourceBuilder.escapeQuotes(policyCondition.getValue()));
component.version_distance("%s", %s)
""".formatted(comparator(policyCondition.getOperator()), toProtoString(policyCondition.getValue()));
}


private String toProtoString(String conditionValue) {
try {
VersionDistance.Builder structBuilder = VersionDistance.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(conditionValue, structBuilder);
return convertToString(structBuilder.build());
} catch (InvalidProtocolBufferException e) {
LOGGER.error("Invalid version distance proto " + e);
return convertToString(VersionDistance.newBuilder().build());
}
}

private String convertToString(VersionDistance versionDistance) {
StringBuilder sbf = new StringBuilder();
if (!StringUtils.isEmpty(versionDistance.getEpoch())) {
sbf.append("epoch:").append("\"").append(versionDistance.getEpoch()).append("\"").append(",");
}
sbf.append("major:").append("\"").append(versionDistance.getMajor()).append("\"").append(",");
sbf.append("minor:").append("\"").append(versionDistance.getMinor()).append("\"").append(",");
sbf.append("patch:").append("\"").append(versionDistance.getPatch()).append("\"");
return "v1.VersionDistance{" + sbf + "}";
}

private String comparator(PolicyCondition.Operator operator) {
return switch (operator) {
case NUMERIC_GREATER_THAN -> ">";
case NUMERIC_GREATER_THAN_OR_EQUAL -> ">=";
case NUMERIC_EQUAL -> "==";
case NUMERIC_NOT_EQUAL -> "!=";
case NUMERIC_LESSER_THAN_OR_EQUAL -> "<=";
case NUMERIC_LESS_THAN -> "<";
default -> "";
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.dependencytrack.proto.policy.v1.License;
import org.dependencytrack.proto.policy.v1.Project;
import org.dependencytrack.proto.policy.v1.Vulnerability;
import org.dependencytrack.proto.policy.v1.VersionDistance;
import org.projectnessie.cel.checker.Decls;

public class CelPolicyTypes {
Expand All @@ -17,5 +18,6 @@ public class CelPolicyTypes {
public static final Type TYPE_VULNERABILITY = Decls.newObjectType(Vulnerability.getDescriptor().getFullName());
public static final Type TYPE_VULNERABILITIES = Decls.newListType(TYPE_VULNERABILITY);
public static final Type TYPE_VULNERABILITY_ALIAS = Decls.newObjectType(Vulnerability.Alias.getDescriptor().getFullName());
public static final Type TYPE_VERSION_DISTANCE = Decls.newObjectType(VersionDistance.getDescriptor().getFullName());

}
7 changes: 7 additions & 0 deletions src/main/proto/org/dependencytrack/policy/v1/policy.proto
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,10 @@ message Vulnerability {
string source = 2;
}
}

message VersionDistance {
optional string epoch = 1;
optional string major = 2;
optional string minor = 3;
optional string patch = 4;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

package org.dependencytrack.util;
package org.dependencytrack.model;

import org.junit.Test;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public void testEvaluateProjectWithPolicyOperatorForComponentAgeLessThan() throw
public void testEvaluateProjectWithPolicyOperatorForVersionDistance() {
final var policy = qm.createPolicy("policy", Policy.Operator.ANY, Policy.ViolationState.FAIL);
qm.createPolicyCondition(policy, PolicyCondition.Subject.EXPRESSION, PolicyCondition.Operator.MATCHES, """
component.version_distance("NUMERIC_GREATER_THAN_OR_EQUAL", "{ \\"major\\": \\"0\\", \\"minor\\": \\"1\\", \\"patch\\": \\"?\\" }")
component.version_distance(">=", v1.VersionDistance{ major: \"0\", minor: \"1\", patch: \"?\" })
""", PolicyViolation.Type.OPERATIONAL);

final var project = new Project();
Expand Down Expand Up @@ -371,7 +371,7 @@ public void testEvaluateProjectWithPolicyOperatorForVersionDistance() {
new CelPolicyEngine().evaluateProject(project.getUuid());
assertThat(qm.getAllPolicyViolations(component)).hasSize(1);
assertThat(qm.getAllPolicyViolations(component).get(0).getPolicyCondition().getValue()).isEqualTo("""
component.version_distance("NUMERIC_GREATER_THAN_OR_EQUAL", "{ \\"major\\": \\"0\\", \\"minor\\": \\"1\\", \\"patch\\": \\"?\\" }")
component.version_distance(">=", v1.VersionDistance{ major: \"0\", minor: \"1\", patch: \"?\" })
""");
}

Expand Down

0 comments on commit 55f9850

Please sign in to comment.