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

com.metaeffekt.artifact.enrichment.other.periodic.AdvisorPeriodicEnrichment Maven / Gradle / Ivy

There is a newer version: 0.132.0
Show 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.enrichment.other.periodic;

import com.metaeffekt.artifact.analysis.cert.PeriodicDataSourcesOperations;
import com.metaeffekt.artifact.analysis.diffmerge.InventoryMerger;
import com.metaeffekt.artifact.analysis.utils.TimeUtils;
import com.metaeffekt.artifact.enrichment.InventoryEnricher;
import com.metaeffekt.artifact.enrichment.configurations.AdvisorPeriodicEnrichmentConfiguration;
import com.metaeffekt.mirror.contents.advisory.AdvisoryEntry;
import com.metaeffekt.mirror.contents.base.VulnerabilityContextInventory;
import com.metaeffekt.mirror.contents.store.VulnerabilityTypeIdentifier;
import com.metaeffekt.mirror.contents.vulnerability.Vulnerability;
import com.metaeffekt.mirror.download.documentation.EnricherMetadata;
import com.metaeffekt.mirror.download.documentation.InventoryEnrichmentPhase;
import lombok.Setter;
import org.metaeffekt.core.inventory.processor.model.AdvisoryMetaData;
import org.metaeffekt.core.inventory.processor.model.Inventory;
import org.metaeffekt.core.inventory.processor.model.InventoryInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

@EnricherMetadata(
        name = "Advisor periodic", phase = InventoryEnrichmentPhase.STANDALONE,
        intermediateFileSuffix = "advisor-periodic", mavenPropertyName = "advisorPeriodicEnrichment"
)
public class AdvisorPeriodicEnrichment extends InventoryEnricher {

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

    public final static String ADVISOR_PERIODIC_QUERY_KEY = "cert-periodic-query";
    public final static String ADVISOR_PERIODIC_QUERY_RANGE_START_KEY = "Range Start";
    public final static String ADVISOR_PERIODIC_QUERY_RANGE_END_KEY = "Range End";

    @Setter
    private AdvisorPeriodicEnrichmentConfiguration configuration = new AdvisorPeriodicEnrichmentConfiguration();

    private final File baseMirrorDirectory;

    public AdvisorPeriodicEnrichment(File baseMirrorDirectory) {
        this.baseMirrorDirectory = baseMirrorDirectory;
    }

    @Override
    public AdvisorPeriodicEnrichmentConfiguration getConfiguration() {
        return configuration;
    }

    @Override
    protected void performEnrichment(Inventory inventory) {
        final Map parsedReferenceInventories = new LinkedHashMap<>();

        // reference inventory must be first for merging later on
        if (configuration.hasInitialInventoryContextName()) {
            parsedReferenceInventories.put(configuration.getInitialInventoryContextName(), inventory);
        }
        parsedReferenceInventories.putAll(configuration.parseReferenceInventories());

        if (parsedReferenceInventories.isEmpty()) {
            LOG.warn("No reference inventories have been provided.");
        } else if (parsedReferenceInventories.size() == 1) {
            LOG.info("Using [{}] as the only reference inventory", parsedReferenceInventories.keySet().iterator().next());
        } else {
            LOG.info("Using [{}] as the reference inventories", parsedReferenceInventories.keySet());
        }


        final Map vParsedReferenceInventories = VulnerabilityContextInventory.fromInventories(parsedReferenceInventories);

        final Inventory outputInventory = new Inventory();
        final VulnerabilityContextInventory vOutputInventory = VulnerabilityContextInventory.fromInventory(outputInventory);

        final PeriodicDataSourcesOperations.QueryTimePeriod queryTimePeriod = configuration.getAdvisoryQueryPeriod();
        LOG.info("Listing all advisories for changed entries of {} in period {}", configuration.getAdvisoryProviders(), queryTimePeriod);

        final InventoryInfo info = outputInventory.findOrCreateInventoryInfo(ADVISOR_PERIODIC_QUERY_KEY);
        info.set(ADVISOR_PERIODIC_QUERY_RANGE_START_KEY, new SimpleDateFormat("yyyy-MM-dd").format(queryTimePeriod.getStart()));
        info.set(ADVISOR_PERIODIC_QUERY_RANGE_END_KEY, new SimpleDateFormat("yyyy-MM-dd").format(queryTimePeriod.getEnd()));

        // set up the index for the advisory providers
        final PeriodicDataSourcesOperations periodicDataSourcesOperations = new PeriodicDataSourcesOperations();
        periodicDataSourcesOperations.addAdvisorIndexQueriesForContentIdentifiers(baseMirrorDirectory, configuration.getAdvisoryProviders());


        final List securityAdvisoriesUpdatedOrModifiedSince = periodicDataSourcesOperations.findSecurityAdvisoriesUpdatedOrModifiedSince(
                queryTimePeriod,
                configuration.getAdvisoryProviders(),
                configuration.getIncludeAdvisoryTypes()
        );
        vOutputInventory.addAllAdvisories(securityAdvisoriesUpdatedOrModifiedSince);

        // if there are reference inventories, mark the AdvisoryMetaData entries as [unaffected, new, in progress, reviewed] based on whether they are listed as reviewed or not.
        periodicDataSourcesOperations.setSecurityAdvisoryReviewedStatusFromReferenceInventories(
                vOutputInventory.getSecurityAdvisories(),
                vParsedReferenceInventories.values()
        );

        this.filterInventoryDataPreMerge(vOutputInventory, periodicDataSourcesOperations);

        this.mergeReferenceInventoriesToSourceInventory(vOutputInventory, parsedReferenceInventories, outputInventory);

        // mark all the advisories that have been added by the merger as 'unclassified'
        for (AdvisoryEntry securityAdvisory : vOutputInventory.getShallowCopySecurityAdvisories()) {
            if (securityAdvisory.getAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS) == null) {
                securityAdvisory.setAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS, AdvisoryMetaData.STATUS_VALUE_UNCLASSIFIED);
            }
        }

        this.filterInventoryDataPostMerge(vOutputInventory, periodicDataSourcesOperations);

        vOutputInventory.writeBack(true);
        this.logReviewStateCounts(vOutputInventory.getSecurityAdvisories());

        // move all data from the output inventory to the original inventory so that the original inventory is enriched
        super.moveInventoryData(outputInventory, inventory);
    }

    private void mergeReferenceInventoriesToSourceInventory(VulnerabilityContextInventory vOutputInventory, Map parsedReferenceInventories, Inventory outputInventory) {
        vOutputInventory.writeBack(true);

        final int initialAdvisoryCount = vOutputInventory.getSecurityAdvisories().size();

        // merge the reference inventories into the output inventory
        if (!parsedReferenceInventories.isEmpty()) {
            final InventoryMerger merger = new InventoryMerger(outputInventory);

            for (Map.Entry reference : parsedReferenceInventories.entrySet()) {
                merger.addReferenceInventory(reference.getValue(), reference.getKey());
            }

            merger.includeArtifacts();
            merger.includeVulnerabilities();
            merger.includeAdvisories();
        }

        vOutputInventory.parse();

        final int finalAdvisoryCount = vOutputInventory.getSecurityAdvisories().size();
        final int addedAdvisories = finalAdvisoryCount - initialAdvisoryCount;
        LOG.info(" --> Added [{} - {} = {}] security advisories from [{}] reference inventories",
                finalAdvisoryCount, initialAdvisoryCount, addedAdvisories, parsedReferenceInventories.size());
    }

    private void filterInventoryDataPreMerge(VulnerabilityContextInventory vOutputInventory, PeriodicDataSourcesOperations periodicDataSourcesOperations) {
        // remove all security advisories that are marked as 'unaffected'
        if (configuration.isFilterUnaffected()) {
            this.filterSecurityAdvisoriesIfStatus(vOutputInventory, periodicDataSourcesOperations, AdvisoryMetaData.STATUS_VALUE_UNAFFECTED);
        }
    }

    private void filterInventoryDataPostMerge(VulnerabilityContextInventory vOutputInventory, PeriodicDataSourcesOperations periodicDataSourcesOperations) {
        final Set unfilteredVulnerabilities = vOutputInventory.getShallowCopyVulnerabilities();
        final int unfilteredSecurityAdvisoriesCount = vOutputInventory.getSecurityAdvisories().size();

        // remove all security advisories that are marked as 'unclassified'
        if (configuration.isFilterUnclassified()) {
            this.filterSecurityAdvisoriesIfStatus(vOutputInventory, periodicDataSourcesOperations, AdvisoryMetaData.STATUS_VALUE_UNCLASSIFIED);
        }

        // remove all vulnerabilities that do not contain at least one vulnerability with a specified security advisory provider
        if (configuration.isFilterVulnerabilitiesWithoutSpecifiedAdvisory()) {
            if (!configuration.getVulnerabilityAdvisoryFilter().isEmpty()) {
                LOG.info("Removing all vulnerabilities that do not contain at least one vulnerability with a specified advisory provider of {}", configuration.getVulnerabilityAdvisoryFilter());
                final int initialVulnerabilityCount = vOutputInventory.getVulnerabilities().size();
                periodicDataSourcesOperations.filterVulnerabilitiesByAdvisoryFilter(
                        vOutputInventory,
                        configuration.getVulnerabilityAdvisoryFilter()
                );
                final int finalVulnerabilityCount = vOutputInventory.getVulnerabilities().size();
                final int removedVulnerabilities = initialVulnerabilityCount - finalVulnerabilityCount;
                LOG.info(" --> Removed [{} - {} = {}] vulnerabilities that do not contain at least one vulnerability with a specified advisory provider",
                        initialVulnerabilityCount, removedVulnerabilities, finalVulnerabilityCount);

            } else {
                LOG.warn("Filtering vulnerabilities without specified advisory is enabled, but no advisory providers are specified. No vulnerabilities will be removed.");
            }
        }

        // remove all vulnerabilities if not affected by security advisors
        if (configuration.isFilterUnaffectedVulnerabilities()) {
            LOG.info("Removing all vulnerabilities that are not affected by any advisory");

            final Set allAffectedVulnerabilities = new HashSet<>();
            for (AdvisoryEntry securityAdvisory : vOutputInventory.getShallowCopySecurityAdvisories()) {
                final Map, Set> referencedIds = securityAdvisory.getReferencedVulnerabilities();
                for (Map.Entry, Set> referencedId : referencedIds.entrySet()) {
                    for (String referencedVulnerability : referencedId.getValue()) {
                        vOutputInventory.findVulnerabilityByName(referencedVulnerability).ifPresent(allAffectedVulnerabilities::add);
                    }
                }
            }

            LOG.info(" --> Found [{}] vulnerabilities that are affected by at least one advisory", allAffectedVulnerabilities.size());

            final int initialVulnerabilityCount = vOutputInventory.getVulnerabilities().size();
            for (Vulnerability vulnerability : vOutputInventory.getShallowCopyVulnerabilities()) {
                if (!allAffectedVulnerabilities.contains(vulnerability)) {
                    vOutputInventory.remove(vulnerability);
                }
            }

            final int finalVulnerabilityCount = vOutputInventory.getVulnerabilities().size();
            final int removedVulnerabilities = initialVulnerabilityCount - finalVulnerabilityCount;
            LOG.info(" --> Removed [{} - {} = {}] vulnerabilities that are not affected by any advisory",
                    initialVulnerabilityCount, removedVulnerabilities, finalVulnerabilityCount);
        }

        // find what vulnerabilities must remain in the inventory no matter what and re-add them if they were to have been removed
        final long forceVulnerabilitiesToBeIncludedChangedSinceTime = this.configuration.getIncludeVulnerabilitiesChangedSinceTimestamp();
        final Date forceVulnerabilitiesToBeIncludedChangedSinceDate = new Date(forceVulnerabilitiesToBeIncludedChangedSinceTime);

        if (forceVulnerabilitiesToBeIncludedChangedSinceTime != 0) {
            int reAddedVulnerabilities = 0;

            for (Vulnerability vulnerability : unfilteredVulnerabilities) {
                if (vulnerability.isCreatedAfter(forceVulnerabilitiesToBeIncludedChangedSinceDate) || vulnerability.isUpdatedAfter(forceVulnerabilitiesToBeIncludedChangedSinceDate)) {
                    if (!vOutputInventory.contains(vulnerability)) {
                        vOutputInventory.add(vulnerability);
                        reAddedVulnerabilities++;
                    }
                }
            }

            if (reAddedVulnerabilities > 0) {
                LOG.info(" --> Re-added [{}] previously removed vulnerabilities that were created or updated after [{}]",
                        reAddedVulnerabilities, TimeUtils.formatNormalizedDate(forceVulnerabilitiesToBeIncludedChangedSinceDate));
            } else {
                LOG.info(" --> No vulnerabilities were re-added, as none were removed that were created or updated after [{}]",
                        TimeUtils.formatNormalizedDate(forceVulnerabilitiesToBeIncludedChangedSinceDate));
            }
        }

        LOG.info("Counts after applying filtering: [vulnerabilities {} -> {}] [advisories {} -> {}]",
                unfilteredVulnerabilities.size(), vOutputInventory.getVulnerabilities().size(),
                unfilteredSecurityAdvisoriesCount, vOutputInventory.getSecurityAdvisories().size());
    }

    private void filterSecurityAdvisoriesIfStatus(VulnerabilityContextInventory vOutputInventory, PeriodicDataSourcesOperations periodicDataSourcesOperations, String statusValueUnclassified) {
        LOG.info("Removing all security advisories that are marked as [{}]", statusValueUnclassified);
        final int initialAdvisoryCount = vOutputInventory.getSecurityAdvisories().size();
        periodicDataSourcesOperations.removeSecurityAdvisoryIfStatus(vOutputInventory, statusValueUnclassified);
        final int finalAdvisoryCount = vOutputInventory.getSecurityAdvisories().size();
        final int removedAdvisories = initialAdvisoryCount - finalAdvisoryCount;
        LOG.info(" --> Removed [{} - {} = {}] security advisories that are marked as [{}]",
                initialAdvisoryCount, removedAdvisories, finalAdvisoryCount, statusValueUnclassified);
    }

    private void logReviewStateCounts(Collection securityAdvisories) {
        final Map reviewStatusCounts = securityAdvisories.stream()
                .collect(Collectors.groupingBy(
                        a -> a.getAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS) == null ? "null" : a.getAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS),
                        Collectors.counting()
                ));

        LOG.info("");
        LOG.info("Inventory review status state summary:");
        for (Map.Entry reviewStatusCount : reviewStatusCounts.entrySet()) {
            LOG.info("  {}: {}", reviewStatusCount.getKey(), reviewStatusCount.getValue());

            final StringJoiner matchingEntries = new StringJoiner(", ");
            for (AdvisoryEntry securityAdvisory : securityAdvisories) {
                if (reviewStatusCount.getKey().equals(securityAdvisory.getAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS) == null ? "null" : securityAdvisory.getAdditionalAttribute(AdvisoryMetaData.Attribute.REVIEW_STATUS))) {
                    matchingEntries.add(securityAdvisory.getId());
                }
            }
            LOG.info("    {}", matchingEntries);
        }
        LOG.info("");
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy