All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metaeffekt.mirror.contents.base.VulnerabilityContextInventory 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.mirror.contents.base;

import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.VulnerabilityStatus;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.VulnerabilityStatusHistoryEntry;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.warnings.InventoryWarnings;
import com.metaeffekt.mirror.contents.advisory.AdvisoryEntry;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeIdentifier;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeStore;
import com.metaeffekt.mirror.contents.store.VulnerabilityTypeStore;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import lombok.Getter;
import lombok.Setter;
import org.metaeffekt.core.inventory.processor.model.*;
import org.metaeffekt.core.inventory.processor.reader.InventoryReader;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration;
import org.metaeffekt.core.inventory.processor.writer.InventoryWriter;
import org.metaeffekt.core.security.cvss.CvssVector;
import org.metaeffekt.core.security.cvss.processor.CvssSelectionResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;

public class VulnerabilityContextInventory {

    private static final Logger LOG = LoggerFactory.getLogger(VulnerabilityContextInventory.class);

    @Getter
    private final Inventory inventory;
    private final String vulnerabilityContextName;

    @Getter
    private final Set vulnerabilities = new LinkedHashSet<>();
    @Getter
    private final Set securityAdvisories = new LinkedHashSet<>();

    private final Map vulnerabilityByName = new HashMap<>();
    private final Map advisoryByName = new HashMap<>();

    // data management settings
    /**
     * Using the attributes {@link VulnerabilityContextInventory#reAssociateAdvisories}
     * and {@link VulnerabilityContextInventory#reAssociateVulnerabilities} gives you higher control over the
     * re-association process, which automatically manages the cross-references between vulnerabilities and advisories.
     * 

This may be useful when you want to add a large number of vulnerabilities or advisories at once and want to * avoid the overhead of re-associating them after each addition, like with updating an index of a database.

*

By default, the re-association process is enabled. Make sure to call the re-association methods after you * have added all vulnerabilities or advisories to the inventory.

*

This property in particular is used to control the re-association of advisories.

*/ @Setter private boolean reAssociateAdvisories = true; /** * Using the attributes {@link VulnerabilityContextInventory#reAssociateAdvisories} * and {@link VulnerabilityContextInventory#reAssociateVulnerabilities} gives you higher control over the * re-association process, which automatically manages the cross-references between vulnerabilities and advisories. *

This may be useful when you want to add a large number of vulnerabilities or advisories at once and want to * avoid the overhead of re-associating them after each addition, like with updating an index of a database.

*

By default, the re-association process is enabled. Make sure to call the re-association methods after you * have added all vulnerabilities or advisories to the inventory.

*

This property in particular is used to control the re-association of vulnerabilities.

*/ @Setter private boolean reAssociateVulnerabilities = true; protected VulnerabilityContextInventory(Inventory inventory, String vulnerabilityContextName) { this.inventory = inventory; this.vulnerabilityContextName = vulnerabilityContextName; } protected VulnerabilityContextInventory(Inventory inventory) { this(inventory, VulnerabilityMetaData.VULNERABILITY_ASSESSMENT_CONTEXT_DEFAULT); } public void pauseReAssociation() { this.reAssociateAdvisories = false; this.reAssociateVulnerabilities = false; } public void resumeReAssociation() { this.reAssociateAdvisories = true; this.reAssociateVulnerabilities = true; this.reAssociateAdvisories(); this.reAssociateVulnerabilities(); } public InventoryWarnings getInventoryWarnings() { return InventoryWarnings.fromInventory(inventory); } public Set getShallowCopyVulnerabilities() { return new LinkedHashSet<>(vulnerabilities); } public List getSortedVulnerabilities() { final List sortedVulnerabilities = new ArrayList<>(vulnerabilities); sortedVulnerabilities.sort(Comparator.comparing(Vulnerability::getId)); return sortedVulnerabilities; } public Set getShallowCopySecurityAdvisories() { return new LinkedHashSet<>(securityAdvisories); } /* EFFECTIVE INFORMATION CALCULATION */ public void calculateEffectiveCvssVectorsForVulnerabilities(CentralSecurityPolicyConfiguration securityPolicy) { synchronized (this.vulnerabilities) { for (Vulnerability vulnerability : this.vulnerabilities) { vulnerability.selectEffectiveCvssVectors(securityPolicy); } } } public void applyEffectiveVulnerabilityStatus(CentralSecurityPolicyConfiguration securityPolicy) { synchronized (this.vulnerabilities) { for (Vulnerability vulnerability : this.vulnerabilities) { final boolean isInsignificant = isVulnerabilityInsignificant(securityPolicy, vulnerability); final VulnerabilityStatus vulnerabilityStatus = vulnerability.getOrCreateNewVulnerabilityStatus(); // remove baked information first vulnerabilityStatus.getStatusHistorySet().removeIf(e -> e.equalsTemplate(VulnerabilityStatusHistoryEntry.IN_REVIEW)); vulnerabilityStatus.getStatusHistorySet().removeIf(e -> e.equalsTemplate(VulnerabilityStatusHistoryEntry.INSIGNIFICANT)); vulnerabilityStatus.reorderChronologically(vulnerability, isInsignificant, securityPolicy.getInsignificantThreshold()); if (vulnerabilityStatus.getStatusHistory().stream().noneMatch(VulnerabilityStatusHistoryEntry::isActive)) { vulnerabilityStatus.addHistoryEntry(VulnerabilityStatusHistoryEntry.IN_REVIEW); } } } } /* COPIED METHODS FROM CentralSecurityPolicyConfiguration USING Vulnerability INSTEAD OF AeaaVulnerability */ public boolean isVulnerabilityInsignificant(CentralSecurityPolicyConfiguration securityPolicy, Vulnerability vulnerability) { final double insignificantThreshold = securityPolicy.getInsignificantThreshold(); if (insignificantThreshold == -1.0) return true; final CvssVector vector = vulnerability.getCvssSelectionResult().getSelectedContextIfAvailableOtherwiseInitial(); if (vector == null) { return false; } else { return vector.getOverallScore() < insignificantThreshold; } } public boolean isVulnerabilityIncludedRegardingAdvisoryProviders(CentralSecurityPolicyConfiguration securityPolicy, Vulnerability vulnerability) { if (CentralSecurityPolicyConfiguration.containsAny(securityPolicy.getIncludeVulnerabilitiesWithAdvisoryProviders())) { return true; } final List> filter = AdvisoryTypeStore.get().fromJsonNamesAndImplementations(securityPolicy.getIncludeVulnerabilitiesWithAdvisoryProviders()); return isVulnerabilityIncludedRegardingAdvisoryProviders(vulnerability, filter); } public static boolean isVulnerabilityIncludedRegardingAdvisoryProviders(Vulnerability vulnerability, Collection> filter) { return vulnerability.getSecurityAdvisories().stream().anyMatch(a -> filter.contains(a.getSourceIdentifier())); } public boolean isVulnerabilityAboveIncludeScoreThreshold(CentralSecurityPolicyConfiguration securityPolicy, Vulnerability vulnerability) { if (securityPolicy.getIncludeScoreThreshold() == -1.0 || securityPolicy.getIncludeScoreThreshold() == Double.MIN_VALUE) { return true; } final CvssVector vector = vulnerability.getCvssSelectionResult().getSelectedByCustomMetric(CvssVector::getOverallScore, CvssSelectionResult.CUSTOM_VECTOR_SCORE_SELECTOR_MAX); final double score = vector == null ? 0.0 : vector.getOverallScore(); return score >= securityPolicy.getIncludeScoreThreshold(); } public boolean isVulnerabilityIncludedRegardingAdvisoryReviewStatus(CentralSecurityPolicyConfiguration securityPolicy, Vulnerability vulnerability) { final List includeVulnerabilitiesWithAdvisoryReviewStatus = securityPolicy.getIncludeVulnerabilitiesWithAdvisoryReviewStatus(); if (CentralSecurityPolicyConfiguration.containsAny(includeVulnerabilitiesWithAdvisoryReviewStatus)) { return true; } return vulnerability.getSecurityAdvisories().stream() .map(a -> a.getAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS)) .filter(Objects::nonNull) .anyMatch(includeVulnerabilitiesWithAdvisoryReviewStatus::contains); } /* DATA ACCESS */ public void add(Vulnerability vulnerability) { synchronized (this.vulnerabilities) { this.vulnerabilities.add(vulnerability); } reAssociateVulnerability(vulnerability); updateVulnerabilityByName(vulnerability); } public void add(VulnerabilityMetaData vmd) { final Vulnerability vulnerability = Vulnerability.fromVulnerabilityMetaData(vmd); synchronized (this.vulnerabilities) { this.vulnerabilities.add(vulnerability); } reAssociateVulnerability(vulnerability); updateVulnerabilityByName(vulnerability); } public void addAllVulnerabilities(List vulnerabilities) { synchronized (this.vulnerabilities) { this.vulnerabilities.addAll(vulnerabilities); } this.reAssociateVulnerabilities(); for (Vulnerability vulnerability : vulnerabilities) { this.updateVulnerabilityByName(vulnerability); } } public void add(AdvisoryEntry securityAdvisory) { synchronized (this.securityAdvisories) { this.securityAdvisories.add(securityAdvisory); } this.reAssociateAdvisory(securityAdvisory); this.updateSecurityAdvisoryByName(securityAdvisory); } public void add(AdvisoryMetaData amd) { final AdvisoryEntry securityAdvisory = AdvisoryEntry.fromAdvisoryMetaData(amd); synchronized (this.securityAdvisories) { this.securityAdvisories.add(securityAdvisory); } this.reAssociateAdvisory(securityAdvisory); this.updateSecurityAdvisoryByName(securityAdvisory); } public void addAllAdvisories(Collection securityAdvisories) { synchronized (this.securityAdvisories) { this.securityAdvisories.addAll(securityAdvisories); } this.reAssociateAdvisories(); for (AdvisoryEntry securityAdvisory : securityAdvisories) { this.updateSecurityAdvisoryByName(securityAdvisory); } } public void remove(Vulnerability vulnerability) { synchronized (this.vulnerabilities) { this.vulnerabilities.remove(vulnerability); } synchronized (this.vulnerabilityByName) { this.vulnerabilityByName.remove(vulnerability.getId()); } } public void remove(VulnerabilityMetaData vulnerability) { final Vulnerability removed; synchronized (this.vulnerabilityByName) { removed = this.vulnerabilityByName.remove(vulnerability.get(VulnerabilityMetaData.Attribute.NAME)); } synchronized (this.vulnerabilities) { this.vulnerabilities.remove(removed); } } public void remove(AdvisoryEntry securityAdvisory) { synchronized (this.securityAdvisories) { this.securityAdvisories.remove(securityAdvisory); } synchronized (this.advisoryByName) { this.advisoryByName.remove(securityAdvisory.getId()); } synchronized (this.vulnerabilities) { this.vulnerabilities.forEach(v -> v.removeSecurityAdvisory(securityAdvisory)); } } public void removeIgnoreVulnerabilities(AdvisoryEntry securityAdvisory) { synchronized (this.securityAdvisories) { this.securityAdvisories.remove(securityAdvisory); } synchronized (this.advisoryByName) { this.advisoryByName.remove(securityAdvisory.getId()); } } public void remove(AdvisoryMetaData securityAdvisory) { final AdvisoryEntry removed; synchronized (this.advisoryByName) { removed = this.advisoryByName.remove(securityAdvisory.get(AdvisoryMetaData.Attribute.NAME)); } synchronized (this.securityAdvisories) { this.securityAdvisories.remove(removed); } } public boolean contains(Vulnerability vulnerability) { return this.vulnerabilities.contains(vulnerability); } public boolean contains(AdvisoryEntry securityAdvisory) { return this.securityAdvisories.contains(securityAdvisory); } private void updateVulnerabilityByName(Vulnerability vulnerability) { if (vulnerability == null) return; synchronized (vulnerabilityByName) { vulnerabilityByName.put(vulnerability.getId(), vulnerability); } } private void updateSecurityAdvisoryByName(AdvisoryEntry advisoryEntry) { if (advisoryEntry == null) return; synchronized (this.advisoryByName) { this.advisoryByName.put(advisoryEntry.getId(), advisoryEntry); } } public Optional findVulnerabilityByName(String name) { if (name == null) return Optional.empty(); final Vulnerability vulnerability = this.vulnerabilityByName.get(name); if (vulnerability == null) return Optional.empty(); if (!this.vulnerabilities.contains(vulnerability)) { synchronized (this.vulnerabilities) { this.vulnerabilities.remove(vulnerability); } return Optional.empty(); } return Optional.of(vulnerability); } public Vulnerability findOrCreateVulnerabilityByName(String name) { return findVulnerabilityByName(name).orElseGet(() -> { final Vulnerability vulnerability = new Vulnerability(name); add(vulnerability); return vulnerability; }); } public Vulnerability findOrAppendVulnerabilityByVulnerability(Vulnerability searchVulnerability) { if (searchVulnerability == null) { throw new IllegalArgumentException("Query vulnerability must not be null when searching/creating vulnerabilities"); } if (searchVulnerability.getId() == null) { throw new IllegalArgumentException("Query vulnerability must have an id when searching/creating vulnerabilities, but got: " + searchVulnerability.toJson()); } return findVulnerabilityByName(searchVulnerability.getId()).orElseGet(() -> { add(searchVulnerability); return searchVulnerability; }); } public Vulnerability findOrCreateWithoutAddingVulnerabilityByName(String name) { return findVulnerabilityByName(name).orElseGet(() -> new Vulnerability(name)); } public List findVulnerabilitiesWithSecurityAdvisory(AdvisoryEntry securityAdvisory) { final List vulnerabilitiesWithSecurityAdvisory = new ArrayList<>(); synchronized (this.vulnerabilities) { for (Vulnerability vulnerability : this.vulnerabilities) { if (vulnerability.getSecurityAdvisories().contains(securityAdvisory)) { vulnerabilitiesWithSecurityAdvisory.add(vulnerability); } } } return vulnerabilitiesWithSecurityAdvisory; } public Optional findAdvisoryEntryByName(String name) { if (name == null) return Optional.empty(); final AdvisoryEntry advisoryEntry = this.advisoryByName.get(name); if (advisoryEntry == null) return Optional.empty(); if (!this.securityAdvisories.contains(advisoryEntry)) { synchronized (this.advisoryByName) { this.advisoryByName.remove(name); } synchronized (this.vulnerabilities) { this.vulnerabilities.forEach(v -> v.removeSecurityAdvisory(advisoryEntry)); } return Optional.empty(); } return Optional.of(advisoryEntry); } public AdvisoryEntry findOrCreateAdvisoryEntryByName(String name, Function constructor) { return findAdvisoryEntryByName(name).orElseGet(() -> { final AdvisoryEntry advisoryEntry = constructor.apply(name); add(advisoryEntry); return advisoryEntry; }); } public AdvisoryEntry findOrAppendAdvisoryEntryByAdvisoryEntry(AdvisoryEntry searchAdvisoryEntry) { if (searchAdvisoryEntry == null) { throw new IllegalArgumentException("Query advisory entry must not be null when searching/creating advisory entries"); } if (searchAdvisoryEntry.getId() == null) { throw new IllegalArgumentException("Query advisory entry must have an id when searching/creating advisory entries, but got: " + searchAdvisoryEntry.toJson()); } return findAdvisoryEntryByName(searchAdvisoryEntry.getId()).orElseGet(() -> { add(searchAdvisoryEntry); return searchAdvisoryEntry; }); } @Override public String toString() { return "VulnerabilityContextInventory@" + Integer.toHexString(hashCode()) + "[" + inventory.getArtifacts().size() + ", " + vulnerabilities.size() + ", " + securityAdvisories.size() + ']'; } public void writeBack() { writeBack(false); } public void writeBack(boolean retainUnreferencedVulnerabilities) { final long startTime = System.currentTimeMillis(); this.reAssociateAdvisories(); // affectsArtifacts is a map of: // Artifact Field Names --> Artifact --> List of Vulnerability Names final Map>> affectsArtifacts = new HashMap<>(); // find all artifact - vulnerability / advisory associations synchronized (this.vulnerabilities) { for (Vulnerability vulnerability : this.vulnerabilities) { extractMatchingArtifacts(vulnerability, affectsArtifacts); } } synchronized (this.securityAdvisories) { for (AdvisoryEntry advisoryEntry : this.securityAdvisories) { extractMatchingArtifacts(advisoryEntry, affectsArtifacts); } } // collect previously written vulnerability information from artifacts for (Artifact artifact : inventory.getArtifacts()) { // add all present vulnerabilities if (artifact.get(Artifact.Attribute.VULNERABILITY) != null) { affectsArtifacts .computeIfAbsent(Artifact.Attribute.VULNERABILITY.getKey(), k -> new HashMap<>()) .computeIfAbsent(artifact, k -> new LinkedHashSet<>()) .addAll(Arrays.asList(artifact.get(Artifact.Attribute.VULNERABILITY).split(", "))); } if (artifact.get(InventoryAttribute.ADDON_CVES) != null) { final List addonCves = Arrays.asList(artifact.get(InventoryAttribute.ADDON_CVES).split(", ")); affectsArtifacts .computeIfAbsent(Artifact.Attribute.VULNERABILITY.getKey(), k -> new HashMap<>()) .computeIfAbsent(artifact, k -> new LinkedHashSet<>()) .addAll(addonCves); // register the ADDON_CVES as a source for the vulnerabilities if they do not already contain it synchronized (this.vulnerabilities) { for (String addonCve : addonCves) { final Vulnerability vulnerability = findOrCreateVulnerabilityByName(addonCve); final boolean exactArtifactAndAddonCvesReasonAlreadyExists = vulnerability.getMatchingSources().stream() .filter(source -> source.getDataSource() == VulnerabilityTypeStore.CVE) .filter(source -> source.getMatchReason() instanceof DataSourceIndicator.AnyArtifactOverwriteSourceReason) .map(source -> (DataSourceIndicator.AnyArtifactOverwriteSourceReason) source.getMatchReason()) .anyMatch(reason -> reason.isArtifact(artifact) && Objects.equals(reason.getSource(), InventoryAttribute.ADDON_CVES.getKey())); if (!exactArtifactAndAddonCvesReasonAlreadyExists) { vulnerability.addMatchingSource(DataSourceIndicator.sourcedArtifact(artifact, InventoryAttribute.ADDON_CVES.getKey())); } } } } // remove all that are inapplicable or have been fixed on the artifact, // since certain vulnerabilities should not be matched by the artifact: // - MS vulnerabilities that have been fixed and their KB-ID has been added to the artifact // - inapplicable CVEs as specified in the status files int removeCount = 0; if (artifact.get(InventoryAttribute.INAPPLICABLE_CVE) != null) { // the map modified here is sourced from in the affectsArtifacts map, so no need to put it back final Set vulnList = affectsArtifacts .computeIfAbsent(Artifact.Attribute.VULNERABILITY.getKey(), k -> new HashMap<>()) .computeIfAbsent(artifact, k -> new LinkedHashSet<>()); final int sizeBefore = vulnList.size(); Arrays.asList(artifact.get(InventoryAttribute.INAPPLICABLE_CVE).split(", ")).forEach(vulnList::remove); removeCount += sizeBefore - vulnList.size(); } if (artifact.get(InventoryAttribute.VULNERABILITIES_FIXED_BY_KB) != null) { // same as above final Set vulnList = affectsArtifacts .computeIfAbsent(Artifact.Attribute.VULNERABILITY.getKey(), k -> new HashMap<>()) .computeIfAbsent(artifact, k -> new LinkedHashSet<>()); final int sizeBefore = vulnList.size(); Arrays.asList(artifact.get(InventoryAttribute.VULNERABILITIES_FIXED_BY_KB).split(", ")).forEach(vulnList::remove); removeCount += sizeBefore - vulnList.size(); } if (removeCount > 0) { LOG.warn("Removed [{}] vulnerabilities from artifact [{}:{}:{}] due to inapplicability or fix status", removeCount, artifact.getId(), artifact.getComponent(), artifact.getVersion()); } } // write back all vulnerabilities synchronized (this.vulnerabilities) { // write the effective vulnerability status into the vulnerabilities for (Vulnerability vulnerability : vulnerabilities) { if (vulnerability.getVulnerabilityStatus() != null) { vulnerability.getVulnerabilityStatus().applyToVulnerability(vulnerability); } } final List vmds = this.inventory.getVulnerabilityMetaData(vulnerabilityContextName); vmds.clear(); for (Vulnerability vulnerability : this.vulnerabilities) { vmds.add(vulnerability.toBaseModel()); } } // write back all advisories synchronized (this.securityAdvisories) { final List amds = this.inventory.getAdvisoryMetaData(); amds.clear(); for (AdvisoryEntry advisoryEntry : this.securityAdvisories) { final AdvisoryMetaData constructedAmd = advisoryEntry.toBaseModel(); amds.add(constructedAmd); } } final Set coveredVulnerabilities = new HashSet<>(); // refresh vulnerability references on artifacts for (Map.Entry>> artifactKeyToArtifactVulnerabilitiesEntry : affectsArtifacts.entrySet()) { final String artifactKey = artifactKeyToArtifactVulnerabilitiesEntry.getKey(); final Map> artifactVulnerabilitiesMap = artifactKeyToArtifactVulnerabilitiesEntry.getValue(); for (Map.Entry> artifactsVulnerabilities : artifactVulnerabilitiesMap.entrySet()) { final Artifact artifact = artifactsVulnerabilities.getKey(); final Set vulnerabilities = artifactsVulnerabilities.getValue(); artifact.set(artifactKey, String.join(", ", vulnerabilities)); coveredVulnerabilities.addAll(vulnerabilities); } } if (!retainUnreferencedVulnerabilities) { // remove all vulnerabilities that are not referenced by any artifact final Set unreferencedVulnerabilities = new HashSet<>(); for (Vulnerability vulnerability : this.vulnerabilities) { if (!coveredVulnerabilities.contains(vulnerability.getId())) { if (vulnerability.getVulnerabilityStatus() != null) { final boolean containsVoidStatus = vulnerability.getVulnerabilityStatus().getStatusHistory().stream() .anyMatch(e -> VulnerabilityMetaData.STATUS_VALUE_VOID.equals(e.getStatus())); if (containsVoidStatus) { continue; } } unreferencedVulnerabilities.add(vulnerability.getId()); } } if (!unreferencedVulnerabilities.isEmpty()) { LOG.warn("Removed [{}] vulnerabilities from inventory that are not applicable on any artifact: [{}]", unreferencedVulnerabilities.size(), String.join(", ", unreferencedVulnerabilities)); this.vulnerabilities.removeIf(v -> unreferencedVulnerabilities.contains(v.getId())); inventory.getVulnerabilityMetaData().removeIf(v -> unreferencedVulnerabilities.contains(v.get(VulnerabilityMetaData.Attribute.NAME))); } } // sort vulnerabilities by name inventory.getVulnerabilityMetaData() .sort(Comparator.comparing((VulnerabilityMetaData o) -> o.get(VulnerabilityMetaData.Attribute.NAME) == null ? "" : o.get(VulnerabilityMetaData.Attribute.NAME)).reversed()); LOG.debug("Wrote back [{}] vulnerabilities and [{}] security advisories in [{}] ms", this.vulnerabilities.size(), this.securityAdvisories.size(), System.currentTimeMillis() - startTime); } /** * Usually does not need to be called, since almost all information is already written into the inventory by the * {@link VulnerabilityContextInventory#writeBack()} method. * It requires the {@link VulnerabilityContextInventory#writeBack()} method to have already been called on it. * This method will add the following information that depends on the security policy to be present: * *
    *
  • CVSS Severity information, calculated from the overall scores (initial, context)
  • *
* * @param securityPolicy the security policy to use for additional information */ public void writeAdditionalInformationBack(CentralSecurityPolicyConfiguration securityPolicy) { if (securityPolicy == null) { LOG.error("Security Policy is null. Cannot write additional information back to inventory without a security policy"); return; } for (String context : inventory.getVulnerabilityMetaDataContexts()) { for (VulnerabilityMetaData vmd : inventory.getVulnerabilityMetaData(context)) { if (vmd.get(VulnerabilityMetaData.Attribute.SCORE_INITIAL_OVERALL) != null) { try { final double score = Double.parseDouble(vmd.get(VulnerabilityMetaData.Attribute.SCORE_INITIAL_OVERALL)); final String severity = securityPolicy.getCvssSeverityRanges().getRange(score).getName(); vmd.set(VulnerabilityMetaData.Attribute.SCORE_INITIAL_OVERALL_SEVERITY, severity); } catch (NumberFormatException e) { LOG.error("Could not parse CVSS Initial Overall score for vulnerability [{}]: [{}]", vmd.get(VulnerabilityMetaData.Attribute.NAME), vmd.get(VulnerabilityMetaData.Attribute.SCORE_INITIAL_OVERALL)); } } if (vmd.get(VulnerabilityMetaData.Attribute.SCORE_CONTEXT_OVERALL) != null) { try { final double score = Double.parseDouble(vmd.get(VulnerabilityMetaData.Attribute.SCORE_CONTEXT_OVERALL)); final String severity = securityPolicy.getCvssSeverityRanges().getRange(score).getName(); vmd.set(VulnerabilityMetaData.Attribute.SCORE_CONTEXT_OVERALL_SEVERITY, severity); } catch (NumberFormatException e) { LOG.error("Could not parse CVSS Context Overall score for vulnerability [{}]: [{}]", vmd.get(VulnerabilityMetaData.Attribute.NAME), vmd.get(VulnerabilityMetaData.Attribute.SCORE_CONTEXT_OVERALL)); } } } } } public void writeToFile(File file) throws IOException { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } new InventoryWriter().writeInventory(inventory, file); } public void extractMatchingArtifacts(MatchableDetailsAmbDataClass dc, Map>> affectsArtifacts) { // affectsArtifacts is a map of: // Artifact Field Names --> Artifact --> Set of Vulnerability Names final Map> affectedArtifactsForDataClass = dc.getAffectedArtifacts(); synchronized (affectedArtifactsForDataClass) { for (Map.Entry> keyToArtifacts : affectedArtifactsForDataClass.entrySet()) { final String key = keyToArtifacts.getKey(); final Set artifacts = keyToArtifacts.getValue(); for (Artifact artifact : artifacts) { affectsArtifacts .computeIfAbsent(key, k -> new HashMap<>()) .computeIfAbsent(artifact, k -> new LinkedHashSet<>()).add(dc.getId()); } } } } public void reAssociateVulnerabilities() { if (!reAssociateVulnerabilities) { LOG.debug("Skipping re-association of vulnerabilities due to configuration"); } synchronized (this.vulnerabilities) { for (Vulnerability vulnerability : this.vulnerabilities) { this.reAssociateVulnerability(vulnerability); } } } protected void reAssociateVulnerability(Vulnerability vulnerability) { if (!reAssociateVulnerabilities) { LOG.debug("Skipping re-association of vulnerability [{}] due to configuration", vulnerability.getId()); return; } vulnerability.getSecurityAdvisories().clear(); synchronized (vulnerability.getReferencedSecurityAdvisories()) { for (Map.Entry, Set> refEntry : vulnerability.getReferencedSecurityAdvisories().entrySet()) { final AdvisoryTypeIdentifier source = refEntry.getKey(); final Set ids = refEntry.getValue(); ids.stream() .map(this::findAdvisoryEntryByName) .filter(Optional::isPresent).map(Optional::get) .forEach(securityAdvisory -> { if (securityAdvisory.getSourceIdentifier() != source) { LOG.warn("Overwriting source for advisory [{}] from [{}] to [{}] due to more detailed information on vulnerability [{}]", securityAdvisory.getId(), securityAdvisory.getSourceIdentifier(), source, vulnerability.getId()); securityAdvisory.setSourceIdentifier(source); } vulnerability.addSecurityAdvisory(securityAdvisory); }); } } } public void reAssociateAdvisories() { if (!reAssociateAdvisories) { LOG.debug("Skipping re-association of advisories due to configuration"); return; } final Set unknownAdvisories = new HashSet<>(); synchronized (this.vulnerabilities) { synchronized (this.securityAdvisories) { for (Vulnerability vulnerability : this.vulnerabilities) { for (AdvisoryEntry securityAdvisory : vulnerability.getSecurityAdvisories()) { if (!this.securityAdvisories.contains(securityAdvisory)) { unknownAdvisories.add(securityAdvisory); } } } } } if (!unknownAdvisories.isEmpty()) { this.addAllAdvisories(unknownAdvisories); } synchronized (this.securityAdvisories) { for (AdvisoryEntry advisoryEntry : this.securityAdvisories) { this.reAssociateAdvisory(advisoryEntry); } } } protected void reAssociateAdvisory(AdvisoryEntry advisoryEntry) { if (!reAssociateAdvisories) { LOG.debug("Skipping re-association of advisory [{}] due to configuration", advisoryEntry.getId()); return; } synchronized (this.vulnerabilities) { // find vulnerabilities that reference this advisory and add it to the list of security advisories for (Vulnerability vulnerability : this.vulnerabilities) { if (!vulnerability.getReferencedSecurityAdvisories().isEmpty()) { final boolean hasMatchingAdvisories = vulnerability.deepCopyReferencedSecurityAdvisories().values().stream() .flatMap(Collection::stream) .anyMatch(e -> StringUtils.equals(advisoryEntry.getId(), e)); if (hasMatchingAdvisories) { vulnerability.addSecurityAdvisory(advisoryEntry); } } } } } public void parse() { parse(inventory.getArtifacts(), inventory.getVulnerabilityMetaData(vulnerabilityContextName), inventory.getAdvisoryMetaData()); } protected void parse(List artifacts, List vmds, List amds) { synchronized (this.vulnerabilities) { this.vulnerabilities.clear(); this.vulnerabilityByName.clear(); } synchronized (this.securityAdvisories) { this.securityAdvisories.clear(); this.advisoryByName.clear(); } final long startTime = System.currentTimeMillis(); // find all artifacts that reference vulnerabilities and collect them final Map>> affectsArtifacts = new HashMap<>(); for (Artifact artifact : artifacts) { extractAffectsArtifactsForKey(artifact, affectsArtifacts, Artifact.Attribute.VULNERABILITY); extractAffectsArtifactsForKey(artifact, affectsArtifacts, InventoryAttribute.VULNERABILITIES_FIXED_BY_KB); extractAffectsArtifactsForKey(artifact, affectsArtifacts, InventoryAttribute.INAPPLICABLE_CVE); extractAffectsArtifactsForKey(artifact, affectsArtifacts, InventoryAttribute.ADDON_CVES); } // parse individual vulnerabilities synchronized (this.vulnerabilities) { for (VulnerabilityMetaData vmd : vmds) { final Vulnerability vulnerability = Vulnerability.fromVulnerabilityMetaData(vmd); this.vulnerabilities.add(vulnerability); this.vulnerabilityByName.put(vulnerability.getId(), vulnerability); // add all artifacts that reference this vulnerability final Map> artifactMap = affectsArtifacts.get(vulnerability.getId()); if (artifactMap != null) { for (Map.Entry> artifactEntry : artifactMap.entrySet()) { final String key = artifactEntry.getKey(); final List artifactsForVulnerability = artifactEntry.getValue(); for (Artifact artifact : artifactsForVulnerability) { vulnerability.manuallyAffectsArtifact(key, artifact); } } } } } // parse individual security advisories synchronized (this.securityAdvisories) { for (AdvisoryMetaData amd : amds) { final AdvisoryEntry advisoryEntry = AdvisoryEntry.fromAdvisoryMetaData(amd); this.securityAdvisories.add(advisoryEntry); this.advisoryByName.put(advisoryEntry.getId(), advisoryEntry); // add all artifacts that reference this advisory final Map> artifactMap = affectsArtifacts.get(advisoryEntry.getId()); if (artifactMap != null) { for (Map.Entry> artifactEntry : artifactMap.entrySet()) { final String key = artifactEntry.getKey(); final List artifactsForVulnerability = artifactEntry.getValue(); for (Artifact artifact : artifactsForVulnerability) { advisoryEntry.manuallyAffectsArtifact(key, artifact); } } } } } // add advisory references on vulnerabilities where applicable this.reAssociateVulnerabilities(); LOG.debug("Parsed [{}] vulnerabilities and [{}] security advisories in [{}] ms", this.vulnerabilities.size(), this.securityAdvisories.size(), System.currentTimeMillis() - startTime); } private static void extractAffectsArtifactsForKey(Artifact artifact, Map>> affectsArtifacts, AbstractModelBase.Attribute attribute) { final String vulnerabilityString = artifact.get(attribute); if (StringUtils.hasText(vulnerabilityString)) { final Set vulnerabilityNames = new HashSet<>(Arrays.asList(vulnerabilityString.split(", "))); for (String name : vulnerabilityNames) { affectsArtifacts .computeIfAbsent(name, k -> new HashMap<>()) .computeIfAbsent(attribute.getKey(), k -> new ArrayList<>()) .add(artifact); } } } public static VulnerabilityContextInventory fromFile(File file) throws IOException { return fromInventory(new InventoryReader().readInventory(file)); } public static VulnerabilityContextInventory fromInventory(Inventory inventory) { final VulnerabilityContextInventory vInventory = new VulnerabilityContextInventory(inventory); vInventory.parse(); return vInventory; } public static VulnerabilityContextInventory fromInventory(Inventory inventory, String vulnerabilityContextName) { final VulnerabilityContextInventory vInventory = new VulnerabilityContextInventory(inventory, vulnerabilityContextName); vInventory.parse(); return vInventory; } public static Map fromInventories(Map inventories) { final Map vInventories = new HashMap<>(); for (Map.Entry entry : inventories.entrySet()) { final String vulnerabilityContextName = entry.getKey(); final Inventory inventory = entry.getValue(); final VulnerabilityContextInventory vInventory = new VulnerabilityContextInventory(inventory); vInventory.parse(); vInventories.put(vulnerabilityContextName, vInventory); } return vInventories; } public static VulnerabilityContextInventory empty() { return new VulnerabilityContextInventory(new Inventory()); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy