cves =
definition.getMetadata().getAdvisory().map(Advisory::getCveList)
.orElse(Collections.emptyList())
- .stream().map(AdvisoryCveType::getCve).collect(Collectors.toList());
+ .stream().map(AdvisoryCveType::getCve)
+ .map(cve -> {
+ Matcher matcher = EXTRACT_CVE_REGEX.matcher(cve);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+
+ return "";
+ }).filter(StringUtils::isNotBlank).collect(Collectors.toList());
definition.setCves(cves);
break;
case DEBIAN:
@@ -141,10 +155,11 @@ private static void doCleanupObject(ObjectType object, OsFamily osFamily, String
}
/**
- * Debian Ids are not unique among different versions, so it's possible to have OVAL constructs that have the
- * same id but different content for different versions of Debian.
- *
- * To work around this, we insert the codename of the version into the id string
+ * Debian Ids are not unique among different versions. For example, it is possible to find an OVAL test with
+ * {@code id = "1"} in the OVAL file of debian buster and debian bullseye. This would create a conflict between
+ * the two tests.
+ * To work around this and to have globally unique IDs for OVAL tests, we insert the codename of the version into
+ * the id string.
*/
private static void convertDebianTestRefs(BaseCriteria root, String osVersion) {
if (root instanceof CriteriaType) {
diff --git a/java/code/src/com/suse/oval/OsFamily.java b/java/code/src/com/suse/oval/OsFamily.java
index db4b4282ea7c..91438d4ae3f2 100644
--- a/java/code/src/com/suse/oval/OsFamily.java
+++ b/java/code/src/com/suse/oval/OsFamily.java
@@ -15,25 +15,86 @@
package com.suse.oval;
+import com.redhat.rhn.domain.server.Server;
+
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * This enum defines the operating system families for which we can retrieve OVAL data.
+ * */
public enum OsFamily {
- LEAP("openSUSE Leap", "leap", "opensuse"),
- SUSE_LINUX_ENTERPRISE_SERVER("SUSE Linux Enterprise Server", "sles", "suse"),
- SUSE_LINUX_ENTERPRISE_DESKTOP("SUSE Linux Enterprise Desktop", "sled", "suse"),
- REDHAT_ENTERPRISE_LINUX("Red Hat Enterprise Linux", "enterprise_linux", "redhat"),
- UBUNTU("Ubuntu", "ubuntu", "canonical"),
- DEBIAN("Debian", "debian", "debian");
+ LEAP("openSUSE Leap", "Leap", "opensuse",
+ oneOf("15.2", "15.3", "15.4", "15.5", "15.6")),
+ LEAP_MICRO("openSUSELeap Micro", "openSUE Leap Micro", "opensuse",
+ oneOf("5.2", "5.3", "5.4", "5.5", "6.0")),
+ SUSE_LINUX_ENTERPRISE_SERVER("SUSE Linux Enterprise Server", "SLES", "suse",
+ oneOf("11", "12", "15")),
+ SUSE_LINUX_ENTERPRISE_DESKTOP("SUSE Linux Enterprise Desktop", "SLED", "suse",
+ oneOf("10", "11", "12", "15")),
+ SUSE_LINUX_ENTERPRISE_MICRO("SUSE Linux Enterprise Micro", "SLE Micro", "suse",
+ oneOf("5.0", "5.1", "5.2", "5.3", "5.4", "5.5", "6.0")),
+ REDHAT_ENTERPRISE_LINUX("Red Hat Enterprise Linux", "Red Hat Enterprise Linux", "redhat",
+ withPrefix("7.", "8.", "9.")),
+ DEBIAN("Debian", "Debian", "debian", oneOf("10", "11", "12"));
private final String vendor;
private final String fullname;
- // Should consist of all lower case characters
- private final String shortname;
+ /**
+ * Should consist of the same values as {@link Server#getOs()}
+ * */
+ private final String os;
+ private final Pattern legalReleasePattern;
- OsFamily(String fullnameIn, String shortnameIn, String vendorIn) {
+ OsFamily(String fullnameIn, String osIn, String vendorIn, String legalReleaseRegex) {
this.fullname = fullnameIn;
- this.shortname = shortnameIn;
+ this.os = osIn;
this.vendor = vendorIn;
+ legalReleasePattern = Pattern.compile(legalReleaseRegex);
+ }
+
+ /**
+ * Returns the full name of the OS family.
+ *
+ * @return OS family fullname
+ * */
+ public String fullname() {
+ return fullname;
}
- OsFamily(String fullnameIn, String vendorIn) {
- this(fullnameIn, fullnameIn.toLowerCase(), vendorIn);
+
+ /**
+ * Checks if the given OS release version is valid for this OS family.
+ *
+ * @param release the release version to check
+ * @return weather the given OS release version is valid for this OS family.
+ * */
+ public boolean isSupportedRelease(String release) {
+ return legalReleasePattern.matcher(release).matches();
+ }
+
+ private static String oneOf(String ... legalReleases) {
+ return "(" + String.join("|", escapePeriods(legalReleases)) + ")";
+ }
+
+ private static String withPrefix(String ... legalReleasePrefixes) {
+ return "(" + Arrays.stream(escapePeriods(legalReleasePrefixes))
+ .map(prefix -> prefix + ".*")
+ .collect(Collectors.joining("|")) + ")";
+ }
+
+ private static String[] escapePeriods(String ... strings) {
+ return Arrays.stream(strings).map(str -> str.replace(".", "\\.")).toArray(String[]::new);
+ }
+
+ /**
+ * Creates an {@code OsFamily} object from the given os name.
+ *
+ * @param osName the os name to convert (it should consist of the same values as {@link Server#getOs()})
+ * @return the os family that correspond to the given os name.
+ * */
+ public static Optional fromOsName(String osName) {
+ return Arrays.stream(values()).filter(osFamily -> osFamily.os.equalsIgnoreCase(osName)).findFirst();
}
}
diff --git a/java/code/src/com/suse/oval/config/OVALConfig.java b/java/code/src/com/suse/oval/config/OVALConfig.java
new file mode 100644
index 000000000000..f030188eaf74
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/OVALConfig.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.config;
+
+import com.suse.oval.OsFamily;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Map;
+import java.util.Optional;
+
+public class OVALConfig {
+ @SerializedName("sources")
+ private Map sources;
+
+ public Map getSources() {
+ return sources;
+ }
+
+ /**
+ * Finds the location or URL of the OVAL vulnerability and/or patch files for the given OVAL product
+ * identified by the given os family and version.
+ *
+ * @param osFamily the os family of the OS to find OVAL data for
+ * @param version the version of the OS to find OVAL data for
+ * @return the locations of OVAL vulnerability and/or patch files for the given OS
+ * */
+ public Optional lookupSourceInfo(OsFamily osFamily, String version) {
+ OVALDistributionSourceInfo distributionSources = sources.get(osFamily);
+ if (distributionSources != null) {
+ return distributionSources.getVersionSourceInfo(version);
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public String toString() {
+ return "OVALConfig{" +
+ "sources=" + sources +
+ '}';
+ }
+}
diff --git a/java/code/src/com/suse/oval/config/OVALConfigLoader.java b/java/code/src/com/suse/oval/config/OVALConfigLoader.java
new file mode 100644
index 000000000000..c4e195bfe261
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/OVALConfigLoader.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.config;
+
+import static com.suse.utils.Json.GSON;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Objects;
+
+public class OVALConfigLoader {
+ private static final String DEFAULT_CONFIG_PATH = "/usr/share/susemanager/oval/oval.config.json";
+ private final String configPath;
+
+ /**
+ * Default constructor
+ * @param configPathIn the path of oval.config.json
+ * */
+ public OVALConfigLoader(String configPathIn) {
+ Objects.requireNonNull(configPathIn);
+
+ this.configPath = configPathIn;
+ }
+
+ /**
+ * Empty constructor
+ * */
+ public OVALConfigLoader() {
+ this(DEFAULT_CONFIG_PATH);
+ }
+
+ /**
+ * Reads {@code oval.config.json} from the given path, parses it and return it as a {@link OVALConfig} object.
+ *
+ * @return A configuration object that corresponds to {@code oval.config.json}
+ * */
+ public OVALConfig load() {
+ File jsonConfigFile;
+ try {
+ jsonConfigFile = new File(configPath);
+ return GSON.fromJson(new FileReader(jsonConfigFile), OVALConfig.class);
+ }
+ catch (IOException e) {
+ throw new RuntimeException("Failed to load OVAL config file", e);
+ }
+ }
+
+ /**
+ * Loads the OVAL configuration file from the default path.
+ *
+ * @return the default OVAL config object.
+ * */
+ public static OVALConfig loadDefaultConfig() {
+ return new OVALConfigLoader().load();
+ }
+}
diff --git a/java/code/src/com/suse/oval/config/OVALDistributionSourceInfo.java b/java/code/src/com/suse/oval/config/OVALDistributionSourceInfo.java
new file mode 100644
index 000000000000..666c47473d6e
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/OVALDistributionSourceInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.config;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Encapsulates information about the supported Linux distributions and their corresponding sources of OVAL data.
+ * */
+public class OVALDistributionSourceInfo {
+ @SerializedName("content")
+ private Map content;
+
+ public Map getContent() {
+ return content == null ? Collections.emptyMap() : content;
+ }
+
+ /**
+ * Returns the version source info
+ *
+ * @param version the OS version
+ *
+ * @return version source info
+ * */
+ public Optional getVersionSourceInfo(String version) {
+ return Optional.ofNullable(getContent().get(version));
+ }
+
+ @Override
+ public String toString() {
+ return "OVALDistributionSourceInfo{" +
+ "content=" + content +
+ '}';
+ }
+}
diff --git a/java/code/src/com/suse/oval/config/OVALSourceInfo.java b/java/code/src/com/suse/oval/config/OVALSourceInfo.java
new file mode 100644
index 000000000000..214a76285a30
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/OVALSourceInfo.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.config;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * OVALSourceInfo
+ * */
+public class OVALSourceInfo {
+ @SerializedName("vulnerability")
+ private String vulnerabilitiesInfoSource;
+ @SerializedName("patch")
+ private String patchInfoSource;
+
+ public String getVulnerabilitiesInfoSource() {
+ return vulnerabilitiesInfoSource;
+ }
+
+ public String getPatchInfoSource() {
+ return patchInfoSource;
+ }
+
+ @Override
+ public String toString() {
+ return "OVALSourceInfo{" +
+ "vulnerabilitiesInfoSource='" + vulnerabilitiesInfoSource + '\'' +
+ ", patchInfoSource='" + patchInfoSource + '\'' +
+ '}';
+ }
+}
diff --git a/java/code/src/com/suse/oval/config/test/OVALConfigTest.java b/java/code/src/com/suse/oval/config/test/OVALConfigTest.java
new file mode 100644
index 000000000000..443aeef33893
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/test/OVALConfigTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.config.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import com.redhat.rhn.testing.TestUtils;
+
+import com.suse.oval.OsFamily;
+import com.suse.oval.config.OVALConfig;
+import com.suse.oval.config.OVALConfigLoader;
+import com.suse.oval.config.OVALDistributionSourceInfo;
+import com.suse.oval.config.OVALSourceInfo;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import wiremock.com.google.common.io.Files;
+
+public class OVALConfigTest {
+
+ private static OVALConfig config;
+
+
+ @BeforeAll
+ public static void setUp() throws IOException, ClassNotFoundException {
+ File tempDir = Files.createTempDir();
+ File configFile = tempDir.toPath().resolve("oval.config.json").toFile();
+ FileUtils.copyURLToFile(TestUtils.findTestData("oval.config.json"),
+ configFile);
+
+ config = new OVALConfigLoader(configFile.getAbsolutePath()).load();
+ }
+
+ @Test
+ public void testAllSources() throws IOException {
+ Map sources = config.getSources();
+
+ List allSources = sources.keySet().stream().flatMap(osFamily -> {
+ OVALDistributionSourceInfo ovalDistributionSourceInfo = sources.get(osFamily);
+ return ovalDistributionSourceInfo.getContent().keySet().stream()
+ .flatMap(version -> {
+ Optional ovalSourceInfoOpt =
+ ovalDistributionSourceInfo.getVersionSourceInfo(version);
+
+ OVALSourceInfo ovalSourceInfo = ovalSourceInfoOpt.get();
+
+ String ovalVulnerabilitiesUrl = ovalSourceInfo.getVulnerabilitiesInfoSource();
+ String ovalPatchesUrl = ovalSourceInfo.getPatchInfoSource();
+
+ List urls = new ArrayList<>();
+ if (StringUtils.isNotBlank(ovalVulnerabilitiesUrl)) {
+ urls.add(ovalVulnerabilitiesUrl);
+ }
+
+ if (StringUtils.isNotBlank(ovalPatchesUrl)) {
+ urls.add(ovalPatchesUrl);
+ }
+
+ return urls.stream();
+ });
+ }).collect(Collectors.toList());
+
+ assertFalse(allSources.isEmpty());
+
+ for (String sourceURL : allSources) {
+ HttpURLConnection huc = (HttpURLConnection) new URL(sourceURL).openConnection();
+
+ huc.setRequestMethod("HEAD");
+
+ int responseCode = huc.getResponseCode();
+
+ assertEquals(HttpURLConnection.HTTP_OK, responseCode, () -> "Can't connect to URL: " + sourceURL);
+ }
+ }
+
+ public void testOsFamilies() {
+ // TODO: Test that all os families in the config file are in OsFamily enum
+ }
+}
diff --git a/java/code/src/com/suse/oval/config/test/oval.config.json b/java/code/src/com/suse/oval/config/test/oval.config.json
new file mode 100644
index 000000000000..1dd42f1a7756
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/test/oval.config.json
@@ -0,0 +1,152 @@
+{
+ "sources": {
+ "LEAP": {
+ "content": {
+ "15.2": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.15.2-affected.xml.gz",
+ "patch": ""
+ },
+ "15.3": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.15.3-affected.xml.gz",
+ "patch": ""
+ },
+ "15.4": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.15.4-affected.xml.gz",
+ "patch": ""
+ },
+ "15.5": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.15.5-affected.xml.gz",
+ "patch": ""
+ },
+ "15.6": {
+ "vulnerability": "",
+ "patch": ""
+ }
+ }
+ },
+ "LEAP_MICRO": {
+ "content": {
+ "5.2": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.micro.5.2-affected.xml.gz",
+ "patch": ""
+ },
+ "5.3": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.micro.5.3-affected.xml.gz",
+ "patch": ""
+ },
+ "5.4": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.micro.5.4-affected.xml.gz",
+ "patch": ""
+ },
+ "5.5": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/opensuse.leap.micro.5.5-affected.xml.gz",
+ "patch": ""
+ },
+ "6.0": {
+ "vulnerability": "",
+ "patch": ""
+ }
+ }
+ },
+ "SUSE_LINUX_ENTERPRISE_SERVER": {
+ "content": {
+ "11": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.server.11-affected.xml.gz",
+ "patch": ""
+ },
+ "12": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.server.12-affected.xml.gz",
+ "patch": ""
+ },
+ "15": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.server.15-affected.xml.gz",
+ "patch": ""
+ }
+ }
+ },
+ "SUSE_LINUX_ENTERPRISE_DESKTOP": {
+ "content": {
+ "10": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.desktop.10.xml.gz",
+ "patch": ""
+ },
+ "11": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.desktop.11.xml.gz",
+ "patch": ""
+ },
+ "12": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.desktop.12-affected.xml.gz",
+ "patch": ""
+ },
+ "15": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.desktop.15-affected.xml.gz",
+ "patch": ""
+ }
+ }
+ },
+ "SUSE_LINUX_ENTERPRISE_MICRO": {
+ "content": {
+ "5.0": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.micro.5.0-affected.xml.gz",
+ "patch": ""
+ },
+ "5.1": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.micro.5.1-affected.xml.gz",
+ "patch": ""
+ },
+ "5.2": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.micro.5.2-affected.xml.gz",
+ "patch": ""
+ },
+ "5.3": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.micro.5.3-affected.xml.gz",
+ "patch": ""
+ },
+ "5.4": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.micro.5.4-affected.xml.gz",
+ "patch": ""
+ },
+ "5.5": {
+ "vulnerability": "https://ftp.suse.com/pub/projects/security/oval/suse.linux.enterprise.micro.5.5-affected.xml.gz",
+ "patch": ""
+ },
+ "6.0": {
+ "vulnerability": "",
+ "patch": ""
+ }
+ }
+ },
+ "REDHAT_ENTERPRISE_LINUX": {
+ "content": {
+ "7": {
+ "vulnerability": "https://www.redhat.com/security/data/oval/v2/RHEL7/rhel-7-including-unpatched.oval.xml.bz2",
+ "patch": ""
+ },
+ "8": {
+ "vulnerability": "https://www.redhat.com/security/data/oval/v2/RHEL8/rhel-8-including-unpatched.oval.xml.bz2",
+ "patch": ""
+ },
+ "9": {
+ "vulnerability": "https://www.redhat.com/security/data/oval/v2/RHEL9/rhel-9-including-unpatched.oval.xml.bz2",
+ "patch": ""
+ }
+ }
+ },
+ "DEBIAN": {
+ "content": {
+ "10": {
+ "vulnerability": "https://www.debian.org/security/oval/oval-definitions-buster.xml.bz2",
+ "patch": ""
+ },
+ "11": {
+ "vulnerability": "https://www.debian.org/security/oval/oval-definitions-bullseye.xml.bz2",
+ "patch": ""
+ },
+ "12": {
+ "vulnerability": "https://www.debian.org/security/oval/oval-definitions-bookworm.xml.bz2",
+ "patch": ""
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/code/src/com/suse/oval/config/test/update_oval_config_json.sh b/java/code/src/com/suse/oval/config/test/update_oval_config_json.sh
new file mode 100755
index 000000000000..ac538de523fa
--- /dev/null
+++ b/java/code/src/com/suse/oval/config/test/update_oval_config_json.sh
@@ -0,0 +1,18 @@
+#
+# Copyright (c) 2024 SUSE LLC
+#
+# This software is licensed to you under the GNU General Public License,
+# version 2 (GPLv2). There is NO WARRANTY for this software, express or
+# implied, including the implied warranties of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+# along with this software; if not, see
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+#
+# Red Hat trademarks are not licensed under GPLv2. No permission is
+# granted to use or replicate Red Hat trademarks that are incorporated
+# in this software or its documentation.
+#
+
+pushd $(dirname $0)
+cp ../../../../../../../../susemanager-sync-data/oval.config.json .
+popd
\ No newline at end of file
diff --git a/java/code/src/com/suse/oval/ovaldownloader/OVALDownloadResult.java b/java/code/src/com/suse/oval/ovaldownloader/OVALDownloadResult.java
new file mode 100644
index 000000000000..b9a3349df198
--- /dev/null
+++ b/java/code/src/com/suse/oval/ovaldownloader/OVALDownloadResult.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.ovaldownloader;
+
+import java.io.File;
+import java.util.Optional;
+
+/**
+ * This class encapsulates the OVAL (XML) files produced by the {@link OVALDownloader} class.
+ * Usually, OVAL data of Linux distributions is organized in a repository that contains vulnerability and patch
+ * definitions. The vulnerability definitions are grouped together in the same OVAL (XML) file, and the same thing
+ * for patch definitions.
+ * */
+public class OVALDownloadResult {
+ private File vulnerabilityFile;
+ private File patchFile;
+
+
+ public Optional getVulnerabilityFile() {
+ return Optional.ofNullable(vulnerabilityFile);
+ }
+
+ public Optional getPatchFile() {
+ return Optional.ofNullable(patchFile);
+ }
+
+ public void setVulnerabilityFile(File vulnerabilityFileIn) {
+ this.vulnerabilityFile = vulnerabilityFileIn;
+ }
+
+ public void setPatchFile(File patchFileIn) {
+ this.patchFile = patchFileIn;
+ }
+}
diff --git a/java/code/src/com/suse/oval/ovaldownloader/OVALDownloader.java b/java/code/src/com/suse/oval/ovaldownloader/OVALDownloader.java
new file mode 100644
index 000000000000..01942678a4c7
--- /dev/null
+++ b/java/code/src/com/suse/oval/ovaldownloader/OVALDownloader.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2024 SUSE LLC
+ *
+ * This software is licensed to you under the GNU General Public License,
+ * version 2 (GPLv2). There is NO WARRANTY for this software, express or
+ * implied, including the implied warranties of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
+ * along with this software; if not, see
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
+ *
+ * Red Hat trademarks are not licensed under GPLv2. No permission is
+ * granted to use or replicate Red Hat trademarks that are incorporated
+ * in this software or its documentation.
+ */
+
+package com.suse.oval.ovaldownloader;
+
+import com.redhat.rhn.common.conf.Config;
+import com.redhat.rhn.common.conf.ConfigDefaults;
+
+import com.suse.oval.OsFamily;
+import com.suse.oval.config.OVALConfig;
+import com.suse.oval.config.OVALSourceInfo;
+
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.zip.GZIPInputStream;
+/**
+ * The OVAL Downloader is responsible for finding OVAL data online, downloading them, and caching them for easy
+ * access. Each supported distribution maintains an online server containing their OVAL data organized into XML files,
+ * known as an OVAL repository. The links in these repositories can be adjusted by editing the {@code oval.config.json}
+ * file. By default, {@code oval.config.json} directs to the official repositories supplied by the distribution
+ * maintainer to ensure the integrity and consistency of the consumed OVAL data.
+ * */
+public class OVALDownloader {
+ /**
+ * The path where downloaded OVAL files will be stored.
+ * */
+ private final String ovalCacheDir;
+ /**
+ * A configuration object that corresponds to {@code oval.config.json}
+ * */
+ private final OVALConfig config;
+
+ /**
+ * Default constructor
+ *
+ * @param configIn A configuration object that corresponds to {@code oval.config.json}
+ * */
+ public OVALDownloader(OVALConfig configIn) {
+ this.config = configIn;
+ String mountPoint = Config.get().getString(ConfigDefaults.REPOMD_CACHE_MOUNT_POINT, "/var/cache");
+ Path ovalCacheDirPath = Path.of(mountPoint, "rhn", "ovals").toAbsolutePath();
+
+ try {
+ Files.createDirectories(ovalCacheDirPath);
+ ovalCacheDir = ovalCacheDirPath.toAbsolutePath().toString();
+ }
+ catch (IOException eIn) {
+ throw new RuntimeException("Couldn't create OVAL cache directory", eIn);
+ }
+
+ }
+
+ /**
+ * Download the OVAL data that correspond to the given {@code osFamily} and {@code osVersion} and cache
+ * them for future access.
+ *
+ * @param osFamily the family of the Operating system (OS) to download OVAL data for.
+ * @param osVersion the version of the Operating system (OS) to download OVAL data for.
+ * @return {@link OVALDownloadResult}
+ * */
+ public OVALDownloadResult download(OsFamily osFamily, String osVersion) throws IOException {
+ Optional sourceInfoOpt = config.lookupSourceInfo(osFamily, osVersion);
+ if (sourceInfoOpt.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "OVAL sources for '%s' '%s' is not configured. Please ensure OVAL is configured " +
+ "correctly oval.config.json",
+ osFamily, osVersion));
+ }
+
+ return doDownload(sourceInfoOpt.get());
+ }
+
+ /**
+ * A helper method for download OVAL data.
+ * */
+ private OVALDownloadResult doDownload(OVALSourceInfo sourceInfo)
+ throws IOException {
+ OVALDownloadResult result = new OVALDownloadResult();
+
+ String vulnerabilityInfoSource = sourceInfo.getVulnerabilitiesInfoSource();
+ String patchInfoSource = sourceInfo.getPatchInfoSource();
+
+ if (StringUtils.isNotBlank(vulnerabilityInfoSource)) {
+ File vulnerabilityFile = downloadOVALFile(vulnerabilityInfoSource);
+ result.setVulnerabilityFile(vulnerabilityFile);
+ }
+
+ if (StringUtils.isNotBlank(patchInfoSource)) {
+ File patchFile = downloadOVALFile(patchInfoSource);
+ result.setPatchFile(patchFile);
+ }
+
+ return result;
+ }
+
+ private File downloadOVALFile(String vulnerabilityInfoSource) throws IOException {
+ URL vulnerabilityInfoURL = new URL(vulnerabilityInfoSource);
+ String vulnerabilityInfoOVALFilename = FilenameUtils.getName(vulnerabilityInfoURL.getPath());
+ File vulnerabilityFile = Path.of(ovalCacheDir, vulnerabilityInfoOVALFilename).toFile();
+ // Start downloading
+ FileUtils.copyURLToFile(vulnerabilityInfoURL, vulnerabilityFile, 15_000, 15_000);
+
+ return decompressIfNeeded(vulnerabilityFile);
+ }
+
+ private File decompressIfNeeded(File file) {
+ String filename = file.getName();
+
+ File uncompressedOVALFile;
+ if (filename.endsWith(".bz2")) {
+ uncompressedOVALFile = new File(FilenameUtils.removeExtension(file.getPath()));
+ decompressBzip2(file, uncompressedOVALFile);
+ }
+ else if (filename.endsWith(".gz")) {
+ uncompressedOVALFile = new File(FilenameUtils.removeExtension(file.getPath()));
+ decompressGzip(file, uncompressedOVALFile);
+ }
+ else if (filename.endsWith(".xml")) {
+ uncompressedOVALFile = file;
+ }
+ else {
+ throw new IllegalStateException("Unable to decompress file: " + file.getPath());
+ }
+
+ return uncompressedOVALFile;
+ }
+
+ /**
+ * Decompress the GZIP {@code archive} into {@code target} file.
+ */
+ private static void decompressGzip(File archive, File target) {
+ try (GZIPInputStream gis = new GZIPInputStream(new FileInputStream(archive))) {
+ FileUtils.copyToFile(gis, target);
+ }
+ catch (IOException e) {
+ throw new RuntimeException("Failed to decompress GZIP archive", e);
+ }
+ }
+
+ /**
+ * Decompress the BZIP2 {@code archive} into {@code target} file.
+ */
+ private static void decompressBzip2(File archive, File target) {
+ try (InputStream inputStream = new BZip2CompressorInputStream(new FileInputStream(archive))) {
+ FileUtils.copyToFile(inputStream, target);
+ }
+ catch (IOException e) {
+ throw new RuntimeException("Failed to decompress BZIP2 archive", e);
+ }
+ }
+}
diff --git a/java/code/src/com/suse/oval/vulnerablepkgextractor/SUSEVulnerablePackageExtractor.java b/java/code/src/com/suse/oval/vulnerablepkgextractor/SUSEVulnerablePackageExtractor.java
index 2f6d9c015d5a..757043c39c70 100644
--- a/java/code/src/com/suse/oval/vulnerablepkgextractor/SUSEVulnerablePackageExtractor.java
+++ b/java/code/src/com/suse/oval/vulnerablepkgextractor/SUSEVulnerablePackageExtractor.java
@@ -42,7 +42,7 @@
*/
public class SUSEVulnerablePackageExtractor extends CriteriaTreeBasedExtractor {
private static final Pattern RELEASE_PACKAGE_REGEX = Pattern.compile(
- "^\\s*(?[-a-zA-Z_0-9]+) is\\s*==\\s*(?[0-9.]+)\\s*$");
+ "^\\s*(?[-a-zA-Z_0-9]+) is\\s*(==|>=)\\s*(?[0-9.]+)\\s*$");
private final OVALLookupHelper ovalLookupHelper;
/**
@@ -173,11 +173,33 @@ private Cpe deriveCpe(TestType productTest) {
if (osProduct == OsFamily.LEAP) {
return deriveOpenSUSELeapCpe();
}
+ else if (osProduct == OsFamily.LEAP_MICRO) {
+ return deriveOpenSUSELeapMicroCpe();
+ }
+ else if (osProduct == OsFamily.SUSE_LINUX_ENTERPRISE_MICRO) {
+ return deriveSUSEMicroCpe();
+ }
else {
- return deriveSUSEProductCpe(productTest);
+ return deriveFromProductOVALTest(productTest);
}
}
+ private Cpe deriveOpenSUSELeapMicroCpe() {
+ return new CpeBuilder()
+ .withVendor("opensuse")
+ .withProduct("leap-micro")
+ .withVersion(definition.getOsVersion())
+ .build();
+ }
+
+ private Cpe deriveSUSEMicroCpe() {
+ return new CpeBuilder()
+ .withVendor("suse")
+ .withProduct("sle-micro")
+ .withVersion(definition.getOsVersion())
+ .build();
+ }
+
private Cpe deriveOpenSUSELeapCpe() {
return new CpeBuilder()
.withVendor("opensuse")
@@ -186,7 +208,10 @@ private Cpe deriveOpenSUSELeapCpe() {
.build();
}
- private Cpe deriveSUSEProductCpe(TestType productTest) {
+ private Cpe deriveFromProductOVALTest(TestType productTest) {
+ // Example of the content of an OVAL product test:
+ // {
});
};
+ getPatchStatusAccuracyWarning = (row) => {
+ const dataSources: string[] = row.scanDataSources;
+ if (!dataSources) {
+ Loggerhead.error("CVE audit data sources were not supplied by server.");
+ return t("Error, see console");
+ }
+
+ if (dataSources.length === 0) {
+ return t("Unknown patch status");
+ } else if (dataSources.indexOf("OVAL") === -1) {
+ return t("OVAL data out of sync. Potential missed vulnerabilities");
+ } else if (dataSources.indexOf("CHANNELS") === -1) {
+ return t("Server product channels out of sync; no patch information available");
+ }
+
+ Loggerhead.error(`Invalid scan data sources: ${dataSources}`);
+
+ return t("Error, see console");
+ };
+
render() {
return (
@@ -346,6 +375,12 @@ class CVEAudit extends React.Component {
className={"fa fa-big " + PATCH_STATUS_LABEL[row.patchStatus].className}
title={PATCH_STATUS_LABEL[row.patchStatus].description}
/>
+ {row.patchStatus !== UNKNOWN && row.scanDataSources.length < 2 && (
+
+ )}
)}
/>
@@ -378,7 +413,8 @@ class CVEAudit extends React.Component {
row.patchStatus === PATCHED ||
row.patchStatus === AFFECTED_PATCH_UNAVAILABLE ||
row.patchStatus === AFFECTED_PATCH_UNAVAILABLE_IN_UYUNI ||
- row.patchStatus === AFFECTED_PATCH_UNAVAILABLE
+ row.patchStatus === AFFECTED_PATCH_UNAVAILABLE ||
+ row.patchStatus === UNKNOWN
) {
return t("No action required");
} else if (row.patchStatus === AFFECTED_FULL_PATCH_APPLICABLE) {
diff --git a/web/spacewalk-web.changes.HoussemNasri.oval-downloader b/web/spacewalk-web.changes.HoussemNasri.oval-downloader
new file mode 100644
index 000000000000..0847510d51d8
--- /dev/null
+++ b/web/spacewalk-web.changes.HoussemNasri.oval-downloader
@@ -0,0 +1,2 @@
+- Add a column to the CVE auditing result table to show the data
+ source (OVAL or channels) used for auditing the system
\ No newline at end of file