com.metaeffekt.artifact.analysis.diffmerge.VulnerabilityDiffer Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021-2024 the original author or authors.
*
* 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.
*/
package com.metaeffekt.artifact.analysis.diffmerge;
import com.google.common.collect.Maps;
import com.metaeffekt.artifact.analysis.vulnerability.CommonEnumerationUtil;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.metaeffekt.core.inventory.processor.reader.InventoryReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import us.springett.parsers.cpe.Cpe;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
public class VulnerabilityDiffer {
private final static Logger LOG = LoggerFactory.getLogger(VulnerabilityDiffer.class);
private static final String NA = "n/a";
private final Inventory inventory1;
private final Inventory inventory2;
private VulnerabilityDiffer(Inventory inventory1, Inventory inventory2) {
this.inventory1 = inventory1;
this.inventory2 = inventory2;
}
public static VulnerabilityDiffer fromInventories(Inventory inventory1, Inventory inventory2) {
return new VulnerabilityDiffer(inventory1, inventory2);
}
public static VulnerabilityDiffer fromFiles(Collection inventories1, Collection inventories2) throws IOException {
final Map parsed1 = readInventoriesIntoContext(inventories1);
final Map parsed2 = readInventoriesIntoContext(inventories2);
final Inventory merged1 = mergeInventories(parsed1);
final Inventory merged2 = mergeInventories(parsed2);
return new VulnerabilityDiffer(merged1, merged2);
}
public static VulnerabilityDiffer fromMultipleInventories(Collection inventories1, Collection inventories2) {
final Map inventoryContextMap1 = Maps.newHashMap();
for (Inventory inventory : inventories1) {
inventoryContextMap1.put(inventory, "inventory-" + inventoryContextMap1.size());
}
final Map inventoryContextMap2 = Maps.newHashMap();
for (Inventory inventory : inventories2) {
inventoryContextMap2.put(inventory, "inventory-" + inventoryContextMap2.size());
}
final Inventory mergedInventory1 = mergeInventories(inventoryContextMap1);
final Inventory mergedInventory2 = mergeInventories(inventoryContextMap2);
return new VulnerabilityDiffer(mergedInventory1, mergedInventory2);
}
public static VulnerabilityDiffer empty() {
return new VulnerabilityDiffer(null, null);
}
public DiffResult createDiffFromMergedInventories() {
final Inventory containedIn1MissingIn2 = findAndMergeDiffVulnerabilities(this.inventory1, this.inventory2);
final Inventory containedIn2MissingIn1 = findAndMergeDiffVulnerabilities(this.inventory2, this.inventory1);
return createResult(containedIn1MissingIn2, containedIn2MissingIn1);
}
private Inventory findAndMergeDiffVulnerabilities(Inventory before, Inventory after) {
final Inventory mergedInventory = new Inventory();
// build a superset of all vulnerability identifiers from both inventories
final Set vulnerabilitySuperset = new HashSet<>();
appendVulnerabilityIdentifiers(before, vulnerabilitySuperset);
appendVulnerabilityIdentifiers(after, vulnerabilitySuperset);
for (String vulnerability : vulnerabilitySuperset) {
final VulnerabilityMetaData foundBeforeVmd = findVulnerability(before, vulnerability);
final VulnerabilityMetaData foundAfterVmd = findVulnerability(after, vulnerability);
final VulnerabilityMetaData combinedVmd = new VulnerabilityMetaData();
combinedVmd.set(VulnerabilityMetaData.Attribute.NAME, vulnerability);
if (this.appendStatusDifference(foundBeforeVmd, foundAfterVmd, combinedVmd)) {
continue;
}
// the below code failed once (and only once so far) in a test case, so, a try-catch is used here:
try {
this.appendCvssScores(foundBeforeVmd, foundAfterVmd, combinedVmd);
this.appendMatchingCpes(foundBeforeVmd, foundAfterVmd, combinedVmd);
} catch (Exception e) {
LOG.error("Error while processing vulnerability: {}", vulnerability, e);
}
mergedInventory.getVulnerabilityMetaData().add(combinedVmd);
}
return mergedInventory;
}
private boolean appendStatusDifference(VulnerabilityMetaData beforeVmd, VulnerabilityMetaData afterVmd, VulnerabilityMetaData combinedVmd) {
final boolean hasBefore = beforeVmd != null;
final boolean hasAfter = afterVmd != null;
final String beforeStatus = hasBefore ? beforeVmd.get(VulnerabilityMetaData.Attribute.STATUS) : null;
final String afterStatus = hasAfter ? afterVmd.get(VulnerabilityMetaData.Attribute.STATUS) : null;
if (beforeStatus == null && hasBefore) {
combinedVmd.set(VulnerabilityMetaData.Attribute.STATUS, "in review");
} else {
combinedVmd.set(VulnerabilityMetaData.Attribute.STATUS, beforeStatus);
}
if (afterStatus == null && hasAfter) {
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_NEW_STATUS.getKey(), "in review");
} else {
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_NEW_STATUS.getKey(), afterStatus);
}
if (!hasBefore && !hasAfter) {
throw new IllegalStateException("Vulnerability must be contained in at least one reference inventory: " + combinedVmd.get(VulnerabilityMetaData.Attribute.NAME));
} else if (hasBefore && !hasAfter) {
if (beforeStatus == null) {
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), VulnerabilityStatusDiff.REMOVED.getKey());
} else {
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), VulnerabilityStatusDiff.REMOVED_EXPECTED_VOID.getKey());
}
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_NEW_STATUS.getKey(), NA);
} else if (!hasBefore) { // && hasAfter
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), VulnerabilityStatusDiff.NEW.getKey());
combinedVmd.set(VulnerabilityMetaData.Attribute.STATUS, NA);
} else { // hasBefore && hasAfter
final VulnerabilityStatusDiff statusChange = VulnerabilityStatusDiff.deriveStatusChangeIdentifier(beforeStatus, afterStatus);
combinedVmd.set(InventoryAttribute.VULNERABILITY_DIFF_STATUS_CHANGE.getKey(), statusChange.getKey());
if (statusChange.equals(VulnerabilityStatusDiff.NO_CHANGE)) {
return true;
}
}
return false;
}
/**
* Include resulting metrics:
*
* - resulting score (modified CVSSv3.1 with fallback)
* - resulting severity (from above CVSS)
*
*
* Up to two vectors (and their score, severity) should be included in the resulting inventory (modified,
* unmodified):
*
* - After V3 Modified
* - Before V3 Modified
* - After V2 Modified
* - Before V2 Modified
*
*
*
* - After V3
* - Before V3
* - After V2
* - Before V2
*
*
* @param beforeVmd vulnerability meta data from the "before" inventory
* @param afterVmd vulnerability meta data from the "after" inventory
* @param combinedVmd vulnerability meta data to append to
*/
private void appendCvssScores(VulnerabilityMetaData beforeVmd, VulnerabilityMetaData afterVmd, VulnerabilityMetaData combinedVmd) {
final Vulnerability beforeVulnerability = Vulnerability.fromVulnerabilityMetaData(beforeVmd);
final Vulnerability afterVulnerability = Vulnerability.fromVulnerabilityMetaData(afterVmd);
if (beforeVulnerability == null && afterVulnerability == null) {
LOG.warn("Vulnerability {} is not defined in either before or after inventories", combinedVmd.get(VulnerabilityMetaData.Attribute.NAME));
return;
}
final Vulnerability combinedVulnerability = Vulnerability.fromVulnerabilityMetaData(combinedVmd);
if (beforeVulnerability != null) {
combinedVulnerability.getCvssVectors().addAllCvssVectors(beforeVulnerability.getCvssVectors());
}
if (afterVulnerability != null) {
combinedVulnerability.getCvssVectors().addAllCvssVectors(afterVulnerability.getCvssVectors());
}
combinedVmd.getAttributes().clear();
combinedVulnerability.appendToBaseModel(combinedVmd);
}
private void appendMatchingCpes(VulnerabilityMetaData beforeVmd, VulnerabilityMetaData afterVmd, VulnerabilityMetaData combinedVmd) {
final List beforeCpe = CommonEnumerationUtil.parseEffectiveCpe(beforeVmd);
final List afterCpe = CommonEnumerationUtil.parseEffectiveCpe(afterVmd);
final List merged = CommonEnumerationUtil.distinctAndSortedWithWildcards(beforeCpe, afterCpe);
combinedVmd.set(VulnerabilityMetaData.Attribute.PRODUCT_URIS.getKey(), CommonEnumerationUtil.toCpe22UriOrFallbackToCpe23FS(merged));
}
private void appendVulnerabilityIdentifiers(Inventory inventory, Collection identifiers) {
inventory.getVulnerabilityMetaData().stream()
.map(v -> v.get(VulnerabilityMetaData.Attribute.NAME))
.forEach(identifiers::add);
}
public static VulnerabilityMetaData findVulnerability(Inventory inventory, String name) {
return inventory.getVulnerabilityMetaData().stream()
.filter(v -> v.get(VulnerabilityMetaData.Attribute.NAME).equals(name))
.findFirst()
.orElse(null);
}
private DiffResult createResult(Inventory containedIn1MissingIn2, Inventory containedIn2MissingIn1) {
return new DiffResult(inventory1, inventory2, containedIn1MissingIn2, containedIn2MissingIn1);
}
private static Map readInventoriesIntoContext(Collection inventoryFiles) throws IOException {
final Map inventoryContextMap = Maps.newHashMap();
for (File inventoryFile : inventoryFiles) {
if (inventoryFile == null || !inventoryFile.exists()) {
throw new FileNotFoundException("Inventory file does not exist: " + inventoryFile);
}
final Inventory inventory = new InventoryReader().readInventory(inventoryFile);
inventoryContextMap.put(inventory, inventoryFile.getName().replace(".xls", ""));
}
return inventoryContextMap;
}
private static Inventory mergeInventories(Map inventories) {
final Inventory mergedInventory1 = new Inventory();
final InventoryMerger merger1 = new InventoryMerger(mergedInventory1);
for (Map.Entry inventoryEntry : inventories.entrySet()) {
final Inventory inventory = inventoryEntry.getKey();
final String context = inventoryEntry.getValue();
merger1.addReferenceInventory(inventory, context);
}
merger1.includeVulnerabilities();
merger1.includeAdvisories();
return mergedInventory1;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy