com.metaeffekt.artifact.enrichment.configurations.VadDetailLevelConfiguration Maven / Gradle / Ivy
/*
* 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.configurations;
import com.metaeffekt.artifact.analysis.vulnerability.enrichment.InventoryAttribute;
import com.metaeffekt.mirror.contents.store.AdvisoryTypeIdentifier;
import org.metaeffekt.core.inventory.processor.configuration.ProcessConfiguration;
import org.metaeffekt.core.inventory.processor.configuration.ProcessMisconfiguration;
import org.metaeffekt.core.inventory.processor.model.Artifact;
import org.metaeffekt.core.inventory.processor.report.configuration.CentralSecurityPolicyConfiguration;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Configures elements to hide from the dashboard, such as:
*
* - timeline
* - advisors
*
* - different types of advisories (notice, ...)
* - by advisory providers (CERT-FR, ...)
*
*
* - references
*
*/
public class VadDetailLevelConfiguration extends ProcessConfiguration {
public final static VadDetailLevelConfiguration DEFAULT_DETAIL_LEVEL = new VadDetailLevelConfiguration();
private VadDetailLevelMatcher matcher = new VadDetailLevelMatcher();
private boolean timeline = true;
private boolean advisoriesGlobal = true;
private boolean advisoriesReferences = true;
private boolean references = true;
private boolean eolDate = true; // FIXME: Implement handler once http://ae-server:7990/projects/AE/repos/metaeffekt-artifact-analysis/pull-requests/82/overview is merged
private List advisoryByTypes = new ArrayList<>();
private List advisoryByProviders = new ArrayList<>();
public VadDetailLevelConfiguration() {
advisoryByTypes.add("any");
advisoryByProviders.add("any");
}
public VadDetailLevelConfiguration setMatcher(VadDetailLevelMatcher matcher) {
this.matcher = matcher;
return this;
}
public VadDetailLevelMatcher getMatcher() {
return matcher;
}
public boolean isTimeline() {
return timeline;
}
public boolean isAdvisoriesGlobal() {
return advisoriesGlobal;
}
public boolean isAdvisoriesReferences() {
return advisoriesReferences;
}
public List getAdvisoryByTypes() {
return advisoryByTypes;
}
public boolean isAdvisoryTypeEnabled(String advisoryType) {
return advisoriesGlobal &&
(advisoryByTypes.contains(advisoryType)
|| CentralSecurityPolicyConfiguration.isAny(advisoryType)
|| CentralSecurityPolicyConfiguration.containsAny(advisoryByTypes));
}
public List getAdvisoryByProviders() {
return advisoryByProviders;
}
public boolean isAdvisoryProviderEnabled(String advisoryProvider) {
return advisoriesGlobal &&
(advisoryByProviders.contains(advisoryProvider)
|| CentralSecurityPolicyConfiguration.isAny(advisoryProvider)
|| CentralSecurityPolicyConfiguration.containsAny(advisoryByProviders));
}
public boolean isAdvisoryProviderEnabled(AdvisoryTypeIdentifier> advisoryProvider) {
return isAdvisoryProviderEnabled(advisoryProvider.name()) || isAdvisoryProviderEnabled(advisoryProvider.getWellFormedName());
}
public boolean isReferences() {
return references;
}
public boolean isEolDate() {
return eolDate;
}
public VadDetailLevelConfiguration setTimeline(boolean timeline) {
this.timeline = timeline;
return this;
}
public VadDetailLevelConfiguration setAdvisoriesGlobal(boolean advisoriesGlobal) {
this.advisoriesGlobal = advisoriesGlobal;
return this;
}
public VadDetailLevelConfiguration setAdvisoriesReferences(boolean advisoriesReferences) {
this.advisoriesReferences = advisoriesReferences;
return this;
}
public VadDetailLevelConfiguration setAdvisoryByTypes(List advisoryByTypes) {
this.advisoryByTypes = advisoryByTypes;
return this;
}
public VadDetailLevelConfiguration setAdvisoryByProviders(List advisoryByProviders) {
this.advisoryByProviders = advisoryByProviders;
return this;
}
public VadDetailLevelConfiguration setReferences(boolean references) {
this.references = references;
return this;
}
public void setEolDate(boolean eolDate) {
this.eolDate = eolDate;
}
@Override
public LinkedHashMap getProperties() {
LinkedHashMap properties = new LinkedHashMap<>();
properties.put("matcher", matcher == null ? null : matcher.getProperties());
properties.put("timeline", timeline);
properties.put("references", references);
properties.put("advisoriesGlobal", advisoriesGlobal);
properties.put("advisoriesReferences", advisoriesReferences);
properties.put("advisoryByTypes", advisoryByTypes);
properties.put("advisoryByProviders", advisoryByProviders);
properties.put("eolDate", eolDate);
return properties;
}
@Override
public void setProperties(LinkedHashMap properties) {
super.loadBooleanProperty(properties, "timeline", this::setTimeline);
super.loadBooleanProperty(properties, "references", this::setReferences);
super.loadBooleanProperty(properties, "advisoriesGlobal", this::setAdvisoriesGlobal);
super.loadBooleanProperty(properties, "advisoriesReferences", this::setAdvisoriesReferences);
super.loadBooleanProperty(properties, "eolDate", this::setEolDate);
super.loadListProperty(properties, "advisoryByTypes", String::valueOf, this::setAdvisoryByTypes);
super.loadListProperty(properties, "advisoryByProviders", String::valueOf, this::setAdvisoryByProviders);
if (properties.containsKey("matcher")) {
matcher = new VadDetailLevelMatcher();
matcher.setProperties((LinkedHashMap) properties.get("matcher"));
}
super.loadSubConfiguration(properties, "matcher", VadDetailLevelMatcher::new, this::setMatcher);
}
@Override
protected void collectMisconfigurations(List misconfigurations) {
if (matcher == null) {
misconfigurations.add(new ProcessMisconfiguration("matcher", "matcher is not set"));
} else {
matcher.collectMisconfigurations(misconfigurations);
}
}
public static List fromArtifacts(Set artifacts) {
return artifacts.stream()
.map(VadDetailLevelConfiguration::fromArtifact)
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public static List fromArtifact(Artifact artifact) {
final String configString = artifact.getComplete(InventoryAttribute.VAD_DETAIL_LEVEL_CONFIGURATIONS.getKey());
if (configString == null) {
return new ArrayList<>();
}
return fromConfigurationString(configString);
}
/**
* These strings may be used in the correlation files. They are used to create {@link VadDetailLevelMatcher} and
* {@link VadDetailLevelConfiguration} instances.
* Every detail level is represented by two lines, the first specifying the matcher and the second specifying the
* configuration.
* Example:
*
* matcher: status = "in review"; anyCpe = "cpe:/a:linux:kernel"
* detail: timeline = "false"; advisoriesGlobal = "false"
* ... further detail levels
*
*
* If a value is not specified, the default value is used.
*
* @param configString The string representation of the configuration.
* @return The list of detail level configurations.
*/
public static List fromConfigurationString(String configString) {
final List lines = Arrays.stream(configString.split("\n"))
.map(String::trim)
.filter(line -> !line.isEmpty())
.collect(Collectors.toList());
final List configurations = new ArrayList<>();
final StringBuilder matcherBuilder = new StringBuilder(" ");
final StringBuilder detailsBuilder = new StringBuilder(" ");
final List matcherLines = new ArrayList<>();
final List detailsLines = new ArrayList<>();
boolean inMatcher = true;
for (int i = 0; i < lines.size(); i++) {
final String line = lines.get(i);
if (line.startsWith("matcher:")) {
inMatcher = true;
matcherBuilder.append(line);
if (detailsBuilder.length() > 1) {
detailsLines.add(detailsBuilder.toString());
detailsBuilder.setLength(0);
}
} else if (line.startsWith("detail:")) {
inMatcher = false;
detailsBuilder.append(line);
if (matcherBuilder.length() > 1) {
matcherLines.add(matcherBuilder.toString());
matcherBuilder.setLength(0);
}
} else {
if (inMatcher) {
matcherBuilder.append(line);
} else {
detailsBuilder.append(line);
}
}
}
if (matcherBuilder.length() > 1) {
matcherLines.add(matcherBuilder.toString());
}
if (detailsBuilder.length() > 1) {
detailsLines.add(detailsBuilder.toString());
}
if (matcherLines.size() != detailsLines.size()) {
throw new IllegalArgumentException("Invalid configuration string, number of matcher lines (" + matcherLines.size() + ") does not match number of detail lines (" + detailsLines.size() + ")");
}
for (int i = 0; i < matcherLines.size(); i++) {
final String matcherLine = matcherLines.get(i).trim();
final String detailsLine = detailsLines.get(i).trim();
if (!detailsLine.startsWith("detail:")) {
throw new IllegalArgumentException("Invalid configuration string, expected 'detail:' but got '" + detailsLine + "'");
}
final String trimmedPropertiesString = detailsLine.substring("detail:".length()).trim();
final Map properties = extractPropertiesFromConfigLine(trimmedPropertiesString);
final VadDetailLevelConfiguration configuration = new VadDetailLevelConfiguration();
final VadDetailLevelMatcher matcher = VadDetailLevelMatcher.fromConfigurationString(matcherLine);
configuration.setMatcher(matcher);
setBooleanProperty(properties, "timeline", configuration::setTimeline);
setBooleanProperty(properties, "references", configuration::setReferences);
setBooleanProperty(properties, "advisoriesGlobal", configuration::setAdvisoriesGlobal);
setBooleanProperty(properties, "advisoriesReferences", configuration::setAdvisoriesReferences);
setBooleanProperty(properties, "eolDate", configuration::setEolDate);
if (properties.containsKey("advisoryByTypes")) {
configuration.setAdvisoryByTypes(Arrays.asList(properties.get("advisoryByTypes").split(", ")));
}
if (properties.containsKey("advisoryByProviders")) {
configuration.setAdvisoryByProviders(Arrays.asList(properties.get("advisoryByProviders").split(", ")));
}
configurations.add(configuration);
}
return configurations;
}
private static void setBooleanProperty(Map properties, String propertyName, Consumer setter) {
if (properties.containsKey(propertyName)) {
setter.accept(Boolean.parseBoolean(properties.get(propertyName)));
}
}
protected static Map extractPropertiesFromConfigLine(String configLine) {
final Map properties = new HashMap<>();
// go character by character to prevent matching ";" inside strings.
// the only escape sequence is \".
final StringBuilder key = new StringBuilder();
final StringBuilder value = new StringBuilder();
boolean inKey = true;
boolean inString = false;
for (int i = 0; i < configLine.length(); i++) {
final char c = configLine.charAt(i);
if (inString) {
if (c == '"') {
inString = false;
} else if (c == '\\' && i + 1 < configLine.length() && configLine.charAt(i + 1) == '"') {
value.append('"');
i++;
} else {
value.append(c);
}
} else {
if (c == '"') {
inString = true;
} else if (c == ' ') {
// ignore
} else if (c == '=') {
inKey = false;
} else if (c == ';') {
properties.put(key.toString().trim(), value.toString().trim());
key.setLength(0);
value.setLength(0);
inKey = true;
} else {
if (inKey) {
key.append(c);
} else {
value.append(c);
}
}
}
}
if (inKey && !configLine.endsWith(";")) {
throw new IllegalArgumentException("Invalid configuration string, string ended with unterminated key '" + configLine + "'");
}
if (inString) {
throw new IllegalArgumentException("Invalid configuration string, string ended with unterminated string value '" + configLine + "'");
}
if (key.length() > 0) {
properties.put(key.toString().trim(), value.toString().trim());
}
return properties;
}
public static VadDetailLevelConfiguration computeEffective(Collection levels) {
if (levels == null || levels.isEmpty()) {
return DEFAULT_DETAIL_LEVEL;
}
final VadDetailLevelConfiguration effective = new VadDetailLevelConfiguration();
for (VadDetailLevelConfiguration level : levels) {
effective.setTimeline(effective.isTimeline() && level.isTimeline());
effective.setReferences(effective.isReferences() && level.isReferences());
effective.setAdvisoriesGlobal(effective.isAdvisoriesGlobal() && level.isAdvisoriesGlobal());
effective.setAdvisoriesReferences(effective.isAdvisoriesReferences() && level.isAdvisoriesReferences());
effective.setEolDate(effective.isEolDate() && level.isEolDate());
effective.getAdvisoryByTypes().retainAll(level.getAdvisoryByTypes());
effective.getAdvisoryByProviders().retainAll(level.getAdvisoryByProviders());
}
return effective;
}
}