Skip to content

Commit

Permalink
Merge pull request #914 from DependencyTrack/port-nclusion/exclusion-…
Browse files Browse the repository at this point in the history
…of-projects-from-BOM-validation-with-tags

Port : Support inclusion/exclusion of projects from BOM validation with tags
  • Loading branch information
nscuro authored Sep 19, 2024
2 parents 37351e4 + cdcdac4 commit 87903c3
Show file tree
Hide file tree
Showing 8 changed files with 718 additions and 32 deletions.
30 changes: 30 additions & 0 deletions src/main/java/org/dependencytrack/model/BomValidationMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.model;

/**
* @since 4.12.0
*/
public enum BomValidationMode {

ENABLED,
DISABLED,
ENABLED_FOR_TAGS,
DISABLED_FOR_TAGS
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ public enum ConfigPropertyConstants {
VULNERABILITY_SOURCE_EPSS_ENABLED("vuln-source", "epss.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Exploit Prediction Scoring System", ConfigPropertyAccessMode.READ_WRITE),
VULNERABILITY_SOURCE_EPSS_FEEDS_URL("vuln-source", "epss.feeds.url", "https://epss.cyentia.com", PropertyType.URL, "A base URL pointing to the hostname and path of the EPSS feeds", ConfigPropertyAccessMode.READ_WRITE),
ACCEPT_ARTIFACT_CYCLONEDX("artifact", "cyclonedx.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable the systems ability to accept CycloneDX uploads", ConfigPropertyAccessMode.READ_WRITE),
BOM_VALIDATION_ENABLED("artifact", "bom.validation.enabled", "true", PropertyType.BOOLEAN, "Flag to control bom validation", ConfigPropertyAccessMode.READ_WRITE),
BOM_VALIDATION_MODE("artifact", "bom.validation.mode", BomValidationMode.ENABLED.name(), PropertyType.STRING, "Flag to control the BOM validation mode", ConfigPropertyAccessMode.READ_WRITE),
BOM_VALIDATION_TAGS_INCLUSIVE("artifact", "bom.validation.tags.inclusive", "[]", PropertyType.STRING, "JSON array of tags for which BOM validation shall be performed", ConfigPropertyAccessMode.READ_WRITE),
BOM_VALIDATION_TAGS_EXCLUSIVE("artifact", "bom.validation.tags.exclusive", "[]", PropertyType.STRING, "JSON array of tags for which BOM validation shall NOT be performed", ConfigPropertyAccessMode.READ_WRITE),
FORTIFY_SSC_ENABLED("integrations", "fortify.ssc.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable Fortify SSC integration", ConfigPropertyAccessMode.READ_WRITE),
FORTIFY_SSC_SYNC_CADENCE("integrations", "fortify.ssc.sync.cadence", "60", PropertyType.INTEGER, "The cadence (in minutes) to upload to Fortify SSC", ConfigPropertyAccessMode.READ_WRITE),
FORTIFY_SSC_URL("integrations", "fortify.ssc.url", null, PropertyType.URL, "Base URL to Fortify SSC", ConfigPropertyAccessMode.READ_WRITE),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,23 @@
import alpine.model.IConfigProperty;
import alpine.security.crypto.DataEncryption;
import alpine.server.resources.AlpineResource;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonReader;
import jakarta.json.JsonString;
import jakarta.ws.rs.core.Response;
import org.dependencytrack.model.BomValidationMode;
import org.dependencytrack.model.ConfigPropertyAccessMode;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.persistence.QueryManager;
import org.owasp.security.logging.SecurityMarkers;

import java.io.StringReader;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.stream.Collectors;

abstract class AbstractConfigPropertyResource extends AlpineResource {

Expand Down Expand Up @@ -135,6 +143,32 @@ private Response updatePropertyValueInternal(IConfigProperty json, IConfigProper
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("An error occurred while encrypting property value. Check log for details.").build();
}
}
} else if (ConfigPropertyConstants.BOM_VALIDATION_MODE.getPropertyName().equals(json.getPropertyName())) {
try {
BomValidationMode.valueOf(json.getPropertyValue());
property.setPropertyValue(json.getPropertyValue());
} catch (IllegalArgumentException e) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Value must be any of: %s".formatted(Arrays.stream(BomValidationMode.values()).map(Enum::name).collect(Collectors.joining(", "))))
.build();
}
} else if (ConfigPropertyConstants.BOM_VALIDATION_TAGS_INCLUSIVE.getPropertyName().equals(json.getPropertyName())
|| ConfigPropertyConstants.BOM_VALIDATION_TAGS_EXCLUSIVE.getPropertyName().equals(json.getPropertyName())) {
try {
final JsonReader jsonReader = Json.createReader(new StringReader(json.getPropertyValue()));
final JsonArray jsonArray = jsonReader.readArray();
jsonArray.getValuesAs(JsonString::getString);

// NB: Storing the string representation of the parsed array instead of the original value,
// since this removes any unnecessary whitespace.
property.setPropertyValue(jsonArray.toString());
} catch (RuntimeException e) {
return Response
.status(Response.Status.BAD_REQUEST)
.entity("Value must be a valid JSON array of strings")
.build();
}
} else {
property.setPropertyValue(json.getPropertyValue());
}
Expand Down
75 changes: 70 additions & 5 deletions src/main/java/org/dependencytrack/resources/v1/BomResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import alpine.common.logging.Logger;
import alpine.event.framework.Event;
import alpine.model.ConfigProperty;
import alpine.notification.Notification;
import alpine.notification.NotificationLevel;
import alpine.server.auth.PermissionRequired;
Expand All @@ -33,6 +34,10 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.security.SecurityRequirements;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonReader;
import jakarta.json.JsonString;
import jakarta.validation.Validator;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
Expand All @@ -54,7 +59,9 @@
import org.dependencytrack.auth.Permissions;
import org.dependencytrack.event.BomUploadEvent;
import org.dependencytrack.event.kafka.KafkaEventDispatcher;
import org.dependencytrack.model.BomValidationMode;
import org.dependencytrack.model.Component;
import org.dependencytrack.model.ConfigPropertyConstants;
import org.dependencytrack.model.Project;
import org.dependencytrack.model.validation.ValidUuid;
import org.dependencytrack.notification.NotificationConstants;
Expand All @@ -76,16 +83,20 @@
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.Principal;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Set;

import static java.util.function.Predicate.not;
import static org.dependencytrack.model.ConfigPropertyConstants.BOM_VALIDATION_ENABLED;
import static org.dependencytrack.model.ConfigPropertyConstants.BOM_VALIDATION_MODE;
import static org.dependencytrack.model.ConfigPropertyConstants.BOM_VALIDATION_TAGS_EXCLUSIVE;
import static org.dependencytrack.model.ConfigPropertyConstants.BOM_VALIDATION_TAGS_INCLUSIVE;

/**
* JAX-RS resources for processing bill-of-material (bom) documents.
Expand Down Expand Up @@ -512,10 +523,8 @@ private File validateAndStoreBom(final byte[] bomBytes, final Project project) t
}

static void validate(final byte[] bomBytes, final Project project) {
try (final var qm = new QueryManager()) {
if (!qm.isEnabled(BOM_VALIDATION_ENABLED)) {
return;
}
if (!shouldValidate(project)) {
return;
}

try {
Expand Down Expand Up @@ -556,4 +565,60 @@ private static void dispatchBomValidationFailedNotification(Project project, Str
.content("An error occurred while validating a BOM")
.subject(new BomValidationFailed(project, bom, errors)));
}

private static boolean shouldValidate(final Project project) {
try (final var qm = new QueryManager()) {
final ConfigProperty validationModeProperty = qm.getConfigProperty(
BOM_VALIDATION_MODE.getGroupName(),
BOM_VALIDATION_MODE.getPropertyName()
);

var validationMode = BomValidationMode.valueOf(BOM_VALIDATION_MODE.getDefaultPropertyValue());
try {
validationMode = BomValidationMode.valueOf(validationModeProperty.getPropertyValue());
} catch (RuntimeException e) {
LOGGER.warn("""
No BOM validation mode configured, or configured value is invalid; \
Assuming default mode %s""".formatted(validationMode), e);
}

if (validationMode == BomValidationMode.ENABLED) {
LOGGER.debug("Validating BOM because validation is enabled globally");
return true;
} else if (validationMode == BomValidationMode.DISABLED) {
LOGGER.debug("Not validating BOM because validation is disabled globally");
return false;
}

// Other modes depend on tags. Does the project even have tags?
if (project.getTags() == null || project.getTags().isEmpty()) {
return validationMode == BomValidationMode.DISABLED_FOR_TAGS;
}

final ConfigPropertyConstants tagsPropertyConstant = validationMode == BomValidationMode.ENABLED_FOR_TAGS
? BOM_VALIDATION_TAGS_INCLUSIVE
: BOM_VALIDATION_TAGS_EXCLUSIVE;
final ConfigProperty tagsProperty = qm.getConfigProperty(
tagsPropertyConstant.getGroupName(),
tagsPropertyConstant.getPropertyName()
);

final Set<String> validationModeTags;
try {
final JsonReader jsonParser = Json.createReader(new StringReader(tagsProperty.getPropertyValue()));
final JsonArray jsonArray = jsonParser.readArray();
validationModeTags = Set.copyOf(jsonArray.getValuesAs(JsonString::getString));
} catch (RuntimeException e) {
LOGGER.warn("Tags of property %s:%s could not be parsed as JSON array"
.formatted(tagsPropertyConstant.getGroupName(), tagsPropertyConstant.getPropertyName()), e);
return validationMode == BomValidationMode.DISABLED_FOR_TAGS;
}

final boolean doTagsMatch = project.getTags().stream()
.map(org.dependencytrack.model.Tag::getName)
.anyMatch(validationModeTags::contains);
return (validationMode == BomValidationMode.ENABLED_FOR_TAGS && doTagsMatch)
|| (validationMode == BomValidationMode.DISABLED_FOR_TAGS && !doTagsMatch);
}
}
}
13 changes: 9 additions & 4 deletions src/main/resources/migration/changelog-v5.6.0.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
<databaseChangeLog
objectQuotingStrategy="QUOTE_ALL_OBJECTS"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/dbchangelog
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

<changeSet id="v5.6.0-1" author="sahibamittal">
Expand Down Expand Up @@ -76,4 +73,12 @@
USING GIN("DIRECT_DEPENDENCIES" JSONB_PATH_OPS);
</sql>
</changeSet>

<changeSet id="v5.6.0-5" author="sahibamittal">
<sql>
DELETE FROM "CONFIGPROPERTY"
WHERE "GROUPNAME" = 'artifact'
AND "PROPERTYNAME" = 'bom.validation.enabled';
</sql>
</changeSet>
</databaseChangeLog>
Loading

0 comments on commit 87903c3

Please sign in to comment.