com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.VulnerabilityStatusConverter 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.vulnerability.enrichment.vulnerabilitystatus;
import com.metaeffekt.artifact.analysis.utils.FileUtils;
import com.metaeffekt.artifact.analysis.utils.SnakeYamlParser;
import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.utils.TimeUtils;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.filter.FilterAttribute;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.vulnerabilitystatus.validation.VulnerabilityStatusValidation;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.json.JSONArray;
import org.json.JSONObject;
import org.metaeffekt.core.inventory.processor.model.VulnerabilityMetaData;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration.JsonSchemaValidationErrorsHandling;
import org.metaeffekt.core.security.cvss.CvssVector;
import org.metaeffekt.core.security.cvss.v2.Cvss2;
import org.metaeffekt.core.security.cvss.v3.Cvss3P1;
import org.metaeffekt.core.security.cvss.v4P0.Cvss4P0;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Due to the large amount of conversion methods, this class has been separated from the
* {@link VulnerabilityStatus} class.
* It contains methods to convert the status data from YAML files or {@link VulnerabilityMetaData} to
* {@link VulnerabilityStatus} instances and vice versa.
*/
public class VulnerabilityStatusConverter {
private final static Logger LOG = LoggerFactory.getLogger(VulnerabilityStatusConverter.class);
/**
* Restores a VulnerabilityStatus from a VulnerabilityMetaData instance that has status data applied to it.
* The vulnerability status is stored in several fields:
*
* -
* {@link InventoryAttribute#STATUS_HISTORY} contains a JSON Array of JSON Objects that
* represent the individual {@link VulnerabilityStatusHistoryEntry} instances.
*
* - {@link VulnerabilityMetaData.Attribute#NAME} contains the name of the vulnerability, which is affected.
* -
* {@link InventoryAttribute#REVIEWED_ADVISORIES} contains a JSON Array of JSON Objects that
* each represent a reviewed advisory (aka cert) entry with an optional comment.
*
* -
* {@link InventoryAttribute#STATUS_ACCEPTED} contains an author with an optional date
* in parenthesis behind it. (Author (2022-01-10 01:00:00))
*
* -
* {@link InventoryAttribute#STATUS_REPORTED} contains an author with an optional date
* in parenthesis behind it. (Author (2022-01-10 01:00:00))
*
* - {@link InventoryAttribute#STATUS_TITLE} the title of the status entry.
*
*
* @param vmd The VulnerabilityMetaData instance to restore the status from.
* @return The restored VulnerabilityStatus instance.
*/
@Deprecated
public static VulnerabilityStatus fromVulnerabilityMetaData(VulnerabilityMetaData vmd) {
final VulnerabilityStatus parsedStatus = new VulnerabilityStatus();
final String statusHistoryString = vmd.get(InventoryAttribute.STATUS_HISTORY.getKey());
if (statusHistoryString != null && statusHistoryString.startsWith("[")) {
final JSONArray historyJson = new JSONArray(statusHistoryString);
final List entries = VulnerabilityStatusHistoryEntry.parseEntries(historyJson);
parsedStatus.addHistoryEntries(entries);
}
if (StringUtils.hasText(vmd.get(VulnerabilityMetaData.Attribute.NAME))) {
parsedStatus.addAffectedVulnerability(vmd.get(VulnerabilityMetaData.Attribute.NAME));
}
final String reviewedAdvisoriesString = vmd.get(InventoryAttribute.REVIEWED_ADVISORIES.getKey());
if (reviewedAdvisoriesString != null && reviewedAdvisoriesString.startsWith("[")) {
final JSONArray reviewedJson = new JSONArray(reviewedAdvisoriesString);
final List entries = VulnerabilityStatusReviewedEntry.fromMultipleFormattedStringOrMapEntries(reviewedJson.toList());
parsedStatus.addReviewedAdvisoryEntries(entries);
}
final Pattern valueWithOptionalParenthesisPattern = Pattern.compile("^([^(]+)(?: \\(([^)]+)\\))?$");
final String acceptedBy = vmd.get(InventoryAttribute.STATUS_ACCEPTED.getKey());
final String reportedBy = vmd.get(InventoryAttribute.STATUS_REPORTED.getKey());
if (StringUtils.hasText(acceptedBy)) {
final Matcher matcher = valueWithOptionalParenthesisPattern.matcher(acceptedBy);
if (matcher.matches()) {
parsedStatus.setAcceptedBy(matcher.group(1));
parsedStatus.setAcceptedDate(matcher.groupCount() == 2 ? matcher.group(2) : null);
}
}
if (StringUtils.hasText(reportedBy)) {
final Matcher matcher = valueWithOptionalParenthesisPattern.matcher(reportedBy);
if (matcher.matches()) {
parsedStatus.setReportedBy(matcher.group(1));
parsedStatus.setReportedDate(matcher.groupCount() == 2 ? matcher.group(2) : null);
}
}
final String statusTitle = vmd.get(InventoryAttribute.STATUS_TITLE.getKey());
parsedStatus.setTitle(StringUtils.hasText(statusTitle) ? statusTitle : null);
return parsedStatus;
}
public static VulnerabilityStatus fromLegacyFormatFromVulnerabilityMetaData(VulnerabilityMetaData vulnerabilityMetaData) {
VulnerabilityStatus status = new VulnerabilityStatus();
status.addAffectedVulnerability(vulnerabilityMetaData.get(VulnerabilityMetaData.Attribute.NAME));
status.addHistoryEntry(new VulnerabilityStatusHistoryEntry(
vulnerabilityMetaData.get(VulnerabilityMetaData.Attribute.STATUS),
vulnerabilityMetaData.get(VulnerabilityMetaData.Attribute.RATIONALE),
vulnerabilityMetaData.get(VulnerabilityMetaData.Attribute.RISK),
vulnerabilityMetaData.get(InventoryAttribute.MEASURES.getKey()),
"Reference Inventory",
new SimpleDateFormat("yyyy-MM-dd").format(new Date()),
0.0,
null,
null));
return status;
}
public static Set fromStatusFileOrDirectory(File fileOrDir) {
return fromStatusFileOrDirectory(fileOrDir, CentralSecurityPolicyConfiguration.JSON_SCHEMA_VALIDATION_ERRORS_DEFAULT);
}
/**
* Extracts all YAML files from the given directory or if file is already a File
uses this file.
* For every file: Attempts to parse the file and creates a {@link VulnerabilityStatus} instance using the
* {@link VulnerabilityStatusConverter#fromYaml(LinkedHashMap)} method.
*
* @param fileOrDir The File or Directory to parse the YAML file(s) from.
* @param jsonSchemaValidationErrorsHandling Schema validation mode.
*
* @return The parsed YAML files
* @throws RuntimeException If the status file is malformed.
*/
public static Set fromStatusFileOrDirectory(File fileOrDir,
JsonSchemaValidationErrorsHandling jsonSchemaValidationErrorsHandling) {
final Set parsedStatuses = new HashSet<>();
for (File file : extractVulnerabilityStatusFilesFromDirectory(fileOrDir)) {
LOG.debug("Parsing vulnerability status from YAML file {}.", file.getAbsolutePath());
try {
VulnerabilityStatus.assertVulnerabilityStatusFileValid(file, jsonSchemaValidationErrorsHandling);
} catch (Exception e) {
throw new RuntimeException("Failed to parse vulnerability status file from " + file.getAbsolutePath(), e);
}
try {
final Object yamlRoot = SnakeYamlParser.parseYaml(SnakeYamlParser.createNoTimestampYaml(), file);
if (yamlRoot instanceof LinkedHashMap) {
final LinkedHashMap yamlRootMap = (LinkedHashMap) yamlRoot;
final VulnerabilityStatus parsedStatus = fromYaml(yamlRootMap);
parsedStatus.originYamlFile = file;
parsedStatuses.add(parsedStatus);
} else if (yamlRoot instanceof List) {
final List> yamlRootList = (List>) yamlRoot;
for (final LinkedHashMap yamlRootMap : yamlRootList) {
final VulnerabilityStatus parsedStatus = fromYaml(yamlRootMap);
parsedStatus.originYamlFile = file;
parsedStatuses.add(parsedStatus);
}
}
} catch (FileNotFoundException fileNotFoundException) {
throw new RuntimeException("Failed to read status file " + file.getAbsolutePath(), fileNotFoundException);
} catch (Exception exception) {
throw new RuntimeException("Failed to parse status file, even though validation passed previously from " + file.getAbsolutePath(), exception);
}
}
return parsedStatuses;
}
public static Set extractVulnerabilityStatusFilesFromDirectory(File cveStatusDir) {
final Set files = new HashSet<>();
if (cveStatusDir.exists() && cveStatusDir.isDirectory()) {
for (final File file : FileUtils.listFiles(cveStatusDir, TrueFileFilter.INSTANCE, DirectoryFileFilter.DIRECTORY)) {
if (file.isFile() && file.getName().endsWith(".yaml")) {
files.add(file);
}
}
} else if (cveStatusDir.exists() && cveStatusDir.isFile()) {
files.add(cveStatusDir);
} else {
LOG.warn("Status file directory does not exist: [{}]", cveStatusDir.getAbsolutePath());
}
return files;
}
public static VulnerabilityStatus fromYaml(LinkedHashMap yamlRoot) {
final VulnerabilityStatus parsedStatus = new VulnerabilityStatus();
// create VulnerabilityStatusHistoryEntry objects from the history list
if (validateEntryType(yamlRoot, "history", ArrayList.class)) {
ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy