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

Replace json string with proto in Version Distance Cel Policy #580

Merged
merged 5 commits into from
Feb 26, 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
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
Loading